zl程序教程

您现在的位置是:首页 >  后端

当前栏目

6.2 二叉树遍历

2023-09-14 09:06:54 时间

三大遍历概念

  在实际的编程中,二叉树是一种非常常见的数据结构。常见的二叉树有AVL树、红黑树和哈夫曼树。其中AVL树和红黑树是排序二叉树。排序二叉树的特征就是对于每个节点,左子节点比自己小,右子节点比自己大。对于一般的树,有用栈实现的深度优先遍历和用FIFO队列实现的广度优先遍历。但是因为二叉树一般用作排序树,所以有了从“顺序”的维度进行遍历的三种方法:前序遍历、中序遍历和后序遍历,其中中序遍历是二叉树独有的。
  以下是一个排序二叉树的例子。
在这里插入图片描述
  先看看前序遍历,前序遍历Preorder Traversal,与深度优先搜索DFS效果一致,这里就不多说了。
  中序遍历Inorder Traversal对排序二叉树特别重要,为什么呢?中序遍历是先访问左子树,再访问自己,再访问右子树。其效果呢,恰好形成了从小到大的遍历。请看下图:
在这里插入图片描述
  所以中序遍历的意义在于排序。但是后序遍历Postorder Traversal,后序遍历是先访问左子节点,再访问右子节点,最后访问自己。这么做最大的用途是用来做删除操作,删除肯定是最后处理自己的。

递归实现方法

  递归实现的代码比较容易,以下是三种遍历的递归实现

    /**
     * middle order search
     *
     * @param visitor
     */
    public void preOrder(Predicate<Node<T>> visitor) {
        final Deque<BinaryNode<T>> nodes = new ArrayDeque<>();
        if (!visitor.test(this)) {
            return;
        }

        if (this.getLeft() != null) {
            getLeft().preOrder(visitor);
        }

        if (this.getRight() != null) {
            getRight().preOrder(visitor);
        }
    }

    /**
     * middle order search
     *
     * @param visitor
     */
    public void inOrder(Predicate<Node<T>> visitor) {
        final Deque<BinaryNode<T>> nodes = new ArrayDeque<>();

        if (this.getLeft() != null) {
            getLeft().inOrder(visitor);
        }

        if (!visitor.test(this)) {
            return;
        }

        if (this.getRight() != null) {
            getRight().inOrder(visitor);
        }
    }

    /**
     * middle order search
     *
     * @param visitor
     */
    public void postOrder(Predicate<Node<T>> visitor) {
        final Deque<BinaryNode<T>> nodes = new ArrayDeque<>();

        if (this.getLeft() != null) {
            getLeft().postOrder(visitor);
        }

        if (this.getRight() != null) {
            getRight().postOrder(visitor);
        }
        if (!visitor.test(this)) {
            return;
        }
    }

非递归实现方法

  对于二叉树的前序遍历,直接使用栈进行DFS就行了。可以参考本专栏前一篇文章,这里不再赘述。但是中序遍历和后序遍历如何不通过递归实现呢?
  中序遍历也是用栈,不过是方法不一样。初始化栈时,先把整个左子树放入栈中。然后出栈的时候,把右节点的整个左子树放进去。代码稍微复杂了一点,如下:

/**
     * middle order search
     *
     * @param visitor
     */
    public void inOrderWithStack(Predicate<Node<T>> visitor) {
        final Deque<BinaryNode<T>> stack = new ArrayDeque<>();

        BinaryNode<T> left = (BinaryNode<T>) getRoot();
        while (left != null) {
            stack.push(left);
            left = left.left;
        }
        while (!stack.isEmpty()) {
            BinaryNode<T> node = stack.pop();
            if (!visitor.test(node)) {
                break;
            }
            if (node.right != null) {
                BinaryNode<T> l = node.right;
                while (l != null) {
                    stack.push(l);
                    l = l.left;
                }
            }
        }
    }

  后序遍历,因为是用来删除的,根结点最后访问,对于这种最后访问的场景,我们自然想到了栈。所以我们自然想到的是使用双栈算法,第一个栈用于DFS,然后将遍历出来的元素压入第二个栈。再从第二个栈访问。代码我是复用了DFS的代码,如下:

/**
     * middle order search
     *
     * @param visitor
     */
    public void postOrderTwoStacks(Predicate<Node<T>> visitor) {
        Deque<Node<T>> stack = new LinkedList<>();
        dfs(x->{
            stack.push(x);
            return true;
        }, false);
        while (!stack.isEmpty()){
            Node<T> node = stack.poll();
            if (!visitor.test(node)) {
                break;
            }
        }
    }