遍历二叉树

在二叉树的一些应用中,常常要求在树中查找具有某些特征的结点,或者对树中全部结点逐一进行某种处理。这就提出了一个遍历二叉树的问题,即如果按某条搜索路径巡访树中的每个结点,使得每个结点均被访问一次,而且仅被访问一次。“访问”的含义很广,可以是对结点作各种处理,如输出结点的信息等。遍历对于线性结构来说是一个很容易解决的问题,而对二叉树则不然,因为二叉树是一种非线性结构。

回顾二叉树的定义,我们知道二叉树可以看成是由三个部分组成的:一个根结点,根的左子树和根的右子树。因此如果能够遍历这三部分,则可以遍历整棵二叉树。如果用L、D、R分别表示遍历左子树、访问根结点、遍历右子树。那么对于二叉树的遍历次序就可以有6中方案,即L、D、R的排列组合,那么如果限制对左子树的遍历要先于对右子树的遍历,这就剩下3中情况,分别是:

  • (1)访问根,遍历左子树,遍历右子树(DLR)
  • (2)遍历左子树,访问根,遍历右子树(LDR)
  • (3)遍历左子树,遍历右子树,访问根(LRD)

根据对根访问的不同顺序,分别成DLR为先根(序)遍历,LDR为中根(序)遍历,LRD为后根(序)遍历。

需要注意的是,这里的先序遍历、中序遍历和后序遍历是递归定义的,即在左右子树中也是按相应的规律进行遍历。

《遍历二叉树》

例如,如上图所示的二叉树表示下述表达式:

4 + (6 - 8 + 2 * 2)*2

若先序遍历此二叉树,按访问结点的先后次序将结点排列起来,可得二叉树的先序序列为:

+ 4 * + - 6 8 * 2 2 2   (式1)

类似的,中序遍历此二叉树,可得此二叉树的中序序列为:

4 + 6 - 8 + 2 * 2 * 2   (式2)

后序遍历此二叉树,可得此二叉树的后序序列为:

4 6 8 - 2 2 * + 2 * +    (式3)

从表达式上看,式1、2、3恰好是表达式的前缀表示(波兰式)、中缀表示和后缀表示(逆波兰式)。

我们可以很简单地写出遍历二叉树的递归算法,同时,根据递归时工作栈的状态变化情况,我们也可以很简单地写出遍历二叉树的非递归算法。

每一个遍历的非递归算法都有两种思路,如果借助访问标记的话,我们可以根据递归的特点,手动地用一个栈来保存每一个结点,举例来说,先序遍历时先访问根,再访问左子树,接着访问右子树,即DLR,那么我们对于每一个结点,都按照RLD的顺序入栈,如果一个结点的左右子树已经入过栈,那么该结点视为已访问过的,下一次再遇到的话直接从栈中弹出,并且访问即可;而如果一个结点是未访问过的,则先将其弹出,再按照RLD的顺序,将其右孩子、左孩子入栈,接着自己再入栈。重复这个过程直到栈为空。这种方法实现和理解起来都比较简单。

另一种思路不需要借助访问标记,但是也要借助一个栈来实现,这种方式理解起来比上一种方法稍微有点复杂。

那么下述代码,实现了二叉树的递归与非递归的遍历方法。并且,每一种非递归方法都采用至少两种思路实现。

另外,我们是通过二叉树的顺序存储结构(即数组)来创建二叉树,与此同时,也实现了二叉树的层序遍历。

import java.util.HashMap;
import java.util.Map;

import com.gavin.datastructure.queue.ArrayQueue;
import com.gavin.datastructure.stack.ArrayStack;

/** * 实现二叉树的构建以及各种遍历 * * @author Gavin * * @param <T> */
public class BinaryTree<T> {

    /** * 从顺序存储结构,即数组中构建二叉树 * * @param array * 数组 * @return 返回二叉树根结点 */
    public TripleLinkedNode<T> createTreeFromArray(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        Map<Integer, TripleLinkedNode<T>> nodeMap = new HashMap<>();
        for (int i = 0; i < array.length; i++) {
            // 如果值为空,说明当前结点不存在
            if (array[i] == null) {
                continue;
            }
            TripleLinkedNode<T> currentNode = null;
            if (nodeMap.containsKey(i)) {
                // 当前结点已存在
                currentNode = nodeMap.get(i);
            } else {
                // 当前结点不存在
                currentNode = new TripleLinkedNode<T>(array[i]);
                // 加入
                nodeMap.put(i, currentNode);
            }
            // 设置当前结点的左孩子
            if (2 * i + 1 < array.length && array[2 * i + 1] != null) {
                TripleLinkedNode<T> leftNode = new TripleLinkedNode<T>(array[2 * i + 1]);
                currentNode.setLeft(leftNode);
                nodeMap.put(2 * i + 1, leftNode);
            }
            // 设置当前结点的右孩子
            if (2 * i + 2 < array.length && array[2 * i + 2] != null) {
                TripleLinkedNode<T> rightNode = new TripleLinkedNode<T>(array[2 * i + 2]);
                currentNode.setRight(rightNode);
                nodeMap.put(2 * i + 2, rightNode);
            }
        }
        // 返回根结点root
        return nodeMap.get(0);

    }

    /** * 访问一个结点 * * @param node */
    public void visit(TripleLinkedNode<T> node) {
        System.out.print(node.getData() + " ");
    }

    /** * 将访问标记复位为false * * @param root */
    public void resetVisited(TripleLinkedNode<?> root) {
        if (root == null) {
            return;
        }
        root.setVisited(false);
        resetVisited(root.getLeft());
        resetVisited(root.getRight());
    }

    /** * 递归实现前序遍历 * * @param root */
    public void preOrderTraverse(TripleLinkedNode<T> root) {
        if (root == null) {
            return;
        }
        visit(root);
        preOrderTraverse(root.getLeft());
        preOrderTraverse(root.getRight());
    }

    /** * 递归实现后续遍历 * * @param root */
    public void postOrderTraverse(TripleLinkedNode<T> root) {
        if (root == null) {
            return;
        }
        postOrderTraverse(root.getLeft());
        postOrderTraverse(root.getRight());
        visit(root);
    }

    /** * 递归实现中序遍历 * * @param root */
    public void inOrderTraverse(TripleLinkedNode<T> root) {
        if (root == null) {
            return;
        }
        inOrderTraverse(root.getLeft());
        visit(root);
        inOrderTraverse(root.getRight());
    }

    /** * 层序遍历 * * @param root */
    public void levelOrderTraverse(TripleLinkedNode<T> root) {
        // 要使用一个队列
        ArrayQueue<TripleLinkedNode<T>> queue = new ArrayQueue<>();
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            TripleLinkedNode<T> currentNode = queue.dequeue();
            // 访问队头结点
            visit(currentNode);
            // 左孩子入队
            if (currentNode.hasLeft()) {
                queue.enqueue(currentNode.getLeft());
            }
            // 右孩子入队
            if (currentNode.hasRight()) {
                queue.enqueue(currentNode.getRight());
            }
        }
    }

    /** * 使用访问标记的非递归前序遍历 * * @param root */
    public void preOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.peek();
            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
                // 已被访问过或者是叶子结点,则直接访问
                visit(node);
                stack.pop();
                continue;
            }
            // 先弹出
            stack.pop();
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
            // 再加入
            node.setVisited(true);
            stack.push(node);
        }
    }

    /** * 不使用访问标记的非递归实现前序遍历 * * @param root */
    public void preOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
        // 要用到一个栈
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        while (node != null) {
            // 向左走到底
            while (node != null) {
                visit(node); // 访问根结点
                // 将该根结点的右孩子入栈
                if (node.hasRight()) {
                    stack.push(node.getRight());
                }
                node = node.getLeft();
            }
            // 右子树根退栈遍历右子树
            if (!stack.isEmpty()) {
                node = stack.pop();
            }
        }
    }

    /** * 不使用访问标记的非递归实现前序遍历2:简单好理解 * * @param root */
    public void preOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {
        // 要用到一个栈
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.pop();
            visit(node);
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
        }
    }

    /** * 使用访问标记的非递归中序遍历 * * @param root */
    public void inOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.peek();
            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
                visit(node);
                stack.pop();
                continue;
            }
            // 先弹出
            stack.pop();
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            // 再加入
            node.setVisited(true);
            stack.push(node);
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
        }
    }

    /** * 不使用访问标记的非递归实现中序遍历 * * @param root */
    public void inOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
        // 要用到一个栈
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        while (node != null || !stack.isEmpty()) {
            // 向左走到尽头
            while (node != null) {
                stack.push(node);
                node = node.getLeft();
            }
            if (!stack.isEmpty()) {
                // 取出根结点并访问
                node = stack.pop();
                visit(node);
                // 转向根的右子树进行遍历
                node = node.getRight();
            }
        }
    }

    /** * 使用访问标记的非递归后序遍历 * * @param root */
    public void postOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
        // 要用到一个栈
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.peek();
            // 如果曾经已经访问过其孩子,这里直接访问即可。或者是叶子结点的话,这里也直接访问
            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
                // 叶子结点
                visit(node);
                stack.pop();
                continue;
            }
            // 先加入右孩子
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            // 再加入左孩子
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
            // 其子结点加入栈,这里设置其访问标记为true
            node.setVisited(true);
        }
    }

    /** * 不使用访问标记的非递归后序遍历 * * @param root */
    public void postOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        while (node != null || !stack.isEmpty()) {
            // 先左后右不断深入
            while (node != null) {
                // 将根结点入栈
                stack.push(node);
                if (node.hasLeft()) {
                    node = node.getLeft();
                } else {
                    node = node.getRight();
                }
            }
            if (!stack.isEmpty()) {
                // 取出栈顶元素访问
                node = stack.pop();
                visit(node);
            }
            // 满足条件时,说明栈顶根结点右子树已经访问过,应出栈访问之
            while (!stack.isEmpty() && stack.peek().getRight() == node) {
                node = stack.pop();
                visit(node);
            }
            // 转向栈顶根结点的右子树继续后续遍历
            if (!stack.isEmpty()) {
                node = stack.peek().getRight();
            } else {
                node = null;
            }
        }
    }

    /** * 不使用访问标记的非递归后序遍历的第二种解法:使用两个栈,简单也好理解 * * @param root */
    public void postOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        ArrayStack<TripleLinkedNode<T>> output = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.pop();
            output.push(node);
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
        }
        while (!output.isEmpty()) {
            visit(output.pop());
        }
    }
}

下面代码对当前的二叉树遍历进行测试:

import static org.junit.Assert.*;

import org.junit.Test;

public class BinaryTreeTest {

    @Test
    public void testCreateTreeFromArray() {
        BinaryTree<String> bt = new BinaryTree<>();
        String[] array = { "+", "4", "*", null, null, "+", "2", null, null, null, null, "-", "*", null, null, null,
                null, null, null, null, null, null, null, "6", "8", "2", "2", null, null, null, null };
        TripleLinkedNode<String> root = bt.createTreeFromArray(array);
        assertEquals(11, root.getSize());
        assertEquals(5, root.getHeight());
        // 遍历
        System.out.println("递归先序遍历:");
        bt.preOrderTraverse(root);
        System.out.println();
        System.out.println("使用访问标记的非递归先序遍历:");
        bt.preOrderTraverseNoRecursionWithVisited(root);
        System.out.println();
        System.out.println("不使用访问标记的非递归先序遍历1:");
        bt.preOrderTraverseNoRecursionWithoutVisited(root);
        // 标记复位
        bt.resetVisited(root);
        System.out.println();
        System.out.println("不使用访问标记的非递归先序遍历2:");
        bt.preOrderTraverseNoRecursionWithoutVisited2(root);

        System.out.println();
        System.out.println();
        System.out.println("递归中序遍历:");
        bt.inOrderTraverse(root);
        System.out.println();
        System.out.println("使用访问标记的非递归中序遍历:");
        bt.inOrderTraverseNoRecursionWithVisited(root);
        System.out.println();
        System.out.println("不使用访问标记的非递归中序遍历:");
        bt.inOrderTraverseNoRecursionWithoutVisited(root);
        //标记复位
        bt.resetVisited(root);


        System.out.println();
        System.out.println();
        System.out.println("递归后序遍历:");
        bt.postOrderTraverse(root);
        System.out.println();
        System.out.println("使用访问标记的非递归后序遍历:");
        bt.postOrderTraverseNoRecursionWithVisited(root);
        System.out.println();
        System.out.println("不使用访问标记的非递归后序遍历1:");
        bt.postOrderTraverseNoRecursionWithoutVisited(root);
        bt.resetVisited(root);
        System.out.println();
        System.out.println("不使用访问标记的非递归后序遍历2:");
        bt.postOrderTraverseNoRecursionWithoutVisited2(root);

        System.out.println();
        System.out.println();
        System.out.println("层序遍历:");
        bt.levelOrderTraverse(root);
    }

}

输出如下:

递归先序遍历:
+ 4 * + - 6 8 * 2 2 2 
使用访问标记的非递归先序遍历:
+ 4 * + - 6 8 * 2 2 2 
不使用访问标记的非递归先序遍历1:
+ 4 * + - 6 8 * 2 2 2 
不使用访问标记的非递归先序遍历2:
+ 4 * + - 6 8 * 2 2 2 

递归中序遍历:
4 + 6 - 8 + 2 * 2 * 2 
使用访问标记的非递归中序遍历:
4 + 6 - 8 + 2 * 2 * 2 
不使用访问标记的非递归中序遍历:
4 + 6 - 8 + 2 * 2 * 2 

递归后序遍历:
4 6 8 - 2 2 * + 2 * + 
使用访问标记的非递归后序遍历:
4 6 8 - 2 2 * + 2 * + 
不使用访问标记的非递归后序遍历1:
4 6 8 - 2 2 * + 2 * + 
不使用访问标记的非递归后序遍历2:
4 6 8 - 2 2 * + 2 * + 

层序遍历:
+ 4 * + 2 - * 6 8 2 2

可见,每个算法的结果都完全正确。

    原文作者:KLeonard
    原文地址: https://blog.csdn.net/gavin_john/article/details/72629463
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞