登录后台

页面导航

本文编写于 801 天前,最后修改于 352 天前,其中某些信息可能已经过时。

介绍

所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问 题。 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。二叉树的遍历分为:前序遍历、中序遍历和后序遍历。

实现

public class BinaryTreeTraversal {

    /**
     * 二叉树结点类
     * @param <T> 二叉树存储的数据对象类型
     */
    private static class TreeNode<T> {
        /** 节点存储的值 */
        private T data;
        /** 左孩子节点 */
        private TreeNode<T> leftChild;
        /** 右孩子节点 */
        private TreeNode<T> rightChild;

        /**
         * 二叉树节点的构造方法
         * @param data 节点存储的值
         */
        TreeNode(T data) {
            this.data = data;
        }
    }

    /**
     * 标记节点类,用于二叉树的后序遍历时作标记使用
     * @param <T> 二叉树存储的数据对象类型
     */
    private static class TagNode<T> {
        /** 需要标记的节点 */
        private TreeNode<T> treeNode;
        /** 是否是第一次访问 */
        private boolean isFirst;
    }

    /**
     * 构建二叉树,用二叉树的前序遍历线性链表构建一颗二叉树
     * @param list 二叉树的前序遍历线性链表
     * @param <T> 二叉树存储的数据对象类型
     * @return 构建后的二叉树根节点
     */
    public static <T> TreeNode<T> createBinaryNode(LinkedList<T> list) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        TreeNode<T> node = null;
        T data = list.removeFirst();
        if (data != null) {
            node = new TreeNode<>(data);
            node.leftChild = createBinaryNode(list);
            node.rightChild = createBinaryNode(list);
        }
        return node;
    }

    /**
     * 二叉树的前序遍历,用递归实现
     * @param node 需要遍历的节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void preOrderTraversalByRecursion(TreeNode<T> node) {
        if (node == null) {
            return;
        }
        System.out.print(node.data + " ");
        preOrderTraversalByRecursion(node.leftChild);
        preOrderTraversalByRecursion(node.rightChild);
    }

    /**
     * 二叉树的中序遍历,用递归实现
     * @param node 需要遍历的节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void midOrderTraversalByRecursion(TreeNode<T> node) {
        if (node == null) {
            return;
        }
        midOrderTraversalByRecursion(node.leftChild);
        System.out.print(node.data + " ");
        midOrderTraversalByRecursion(node.rightChild);
    }

    /**
     * 二叉树的后序遍历,用递归实现
     * @param node 需要遍历的节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void postOrderTraversalByRecursion(TreeNode<T> node) {
        if (node == null) {
            return;
        }
        postOrderTraversalByRecursion(node.leftChild);
        postOrderTraversalByRecursion(node.rightChild);
        System.out.print(node.data + " ");
    }

    /**
     * 二叉树的前序遍历,用栈实现
     *
     * 这种实现类似于图的深度优先遍历(DFS)。维护一个栈,将根节点入栈,然后只要
     * 栈不为空,出栈并访问,接着依次访问节点的右节点、左节点入栈。
     *
     * 这种方式应该是对前序遍历的一种特殊实现(看上去简单明了),但是不具备很好的
     * 扩展性,在中序遍历和后序遍历方式中不适用
     *
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void preOrderTraversalByStack(TreeNode<T> root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode<T>> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode<T> node = stack.pop();
            System.out.print(node.data + " ");
            if (node.rightChild != null) {
                stack.push(node.rightChild);
            }
            if (node.leftChild != null) {
                stack.push(node.leftChild);
            }
        }

    }

    /**
     * 二叉树的前序遍历,用栈实现
     *
     * 利用栈模拟递归过程实现循环前序遍历二叉树,这种方式具有扩展性,它模拟递归的
     * 过程,将左子树节点不断压入栈,直到 <code>null</code>,然后再处理栈顶节点
     * 的右子树。
     *
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void preOrderTraversalByStack2(TreeNode<T> root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode<T>> stack = new Stack<>();
        TreeNode<T> node = root;
        while (node != null || !stack.isEmpty()) {
            // 访问节点的的左孩子,并入栈
            while (node != null) {
                System.out.print(node.data + " ");
                stack.push(node);
                node = node.leftChild;
            }
            // 如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
            if (!stack.isEmpty()) {
                node = stack.pop();
                node = node.rightChild;
            }
        }
    }

    /**
     * 二叉树的中序遍历,用栈实现
     *
     * 利用栈模拟递归过程实现循环中序遍历二叉树,思想和上面先序非递归2
     * {@link #preOrderTraversalByStack2(TreeNode)} 相同,只是访问的时间是在
     * 左子树都处理完 <code>null</code> 的时候出栈并访问
     *
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void midOrderTraversalByStack(TreeNode<T> root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode<T>> stack = new Stack<>();
        TreeNode<T> node = root;
        while (node != null || !stack.isEmpty()) {
            // 访问节点的的左孩子,并入栈
            while (node != null) {
                stack.push(node);
                node = node.leftChild;
            }
            // 如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
            if (!stack.isEmpty()) {
                node = stack.pop();
                System.out.print(node.data + " ");
                node = node.rightChild;
            }
        }
    }

    /**
     * 二叉树的后序遍历,用栈实现,额外借助一个 {@link TagNode} 类来实现标记。
     *
     * 后续遍历不同于先序和中序,它是要先处理完左子树和右子树,然后再处理根节点(回溯)
     *
     * 对于任一节点p,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子出现
     * 的节点,此时该节点还未出现在栈顶,但是此时不能将其出栈并访问,因为其右孩子还
     * 未被访问。
     *
     * 所以接下来按照相同的规则对其右子树进行相同的处理,当访问完右子树时,该节点又
     * 出现在栈顶,此时可以将其出栈并访问,这样就保证了正确的访问顺序。
     *
     * 可以看出,在这个过程中,每个节点都两次出现在栈顶,只有第二次出现在栈顶时,才能
     * 访问它。因此需要多设置一个变量标识该节点是否第一次出现在栈顶,这里是在树结构里
     * 加一个标记,然后合成一个新的 {@link TagNode} 。
     *
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void postOrderTraversalByStack(TreeNode<T> root) {
        if (root == null) {
            return;
        }
        Stack<TagNode<T>> stack = new Stack<>();
        TreeNode<T> node = root;
        TagNode<T> tagNode;
        while (node != null || !stack.isEmpty()) {
            // 沿着左子树一直往下搜索,直至出现没有左孩子的节点
            while (node != null) {
                tagNode = new TagNode<>();
                tagNode.treeNode = node;
                tagNode.isFirst = true;
                stack.push(tagNode);
                node = node.leftChild;
            }
            if (!stack.isEmpty()) {
                tagNode = stack.pop();
                // 表示第一次出现在栈顶
                if (tagNode.isFirst) {
                    tagNode.isFirst = false;
                    stack.push(tagNode);
                    node = tagNode.treeNode.rightChild;
                } else {
                    //第二次出现在栈顶
                    System.out.print(tagNode.treeNode.data + " ");
                    node = null;
                }
            }
        }
    }

    /**
     * 二叉树的后序遍历,用栈实现,无需额外借助其它类来判断节点是否访问。
     *
     * 要保证根节点在左孩子和右孩子访问之后才能访问,因此对于任一节点p,先将其入栈。
     * 如果p不存在左孩子和右孩子,则可以直接访问它。
     *
     * 或者p存在左孩子和或右孩子,但其左孩子和右孩子都已经被访问过了,则同样可以直接
     * 访问该节点。
     *
     * 若非上述两种情况,则将p的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的
     * 时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根节点前面被访问。
     *
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void postOrderTraversalByStack2(TreeNode<T> root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode<T>> stack = new Stack<>();
        TreeNode<T> currentNode = null, preNode = null;
        stack.push(root);
        while (!stack.isEmpty()) {
            currentNode = stack.peek();
            // 如果当前节点没有孩子节点或者孩子节点都已经被访问过
            boolean isLeafNode = currentNode.leftChild == null && currentNode.rightChild == null;
            boolean isVisited = (preNode != null && (preNode == currentNode.leftChild
                    || preNode == currentNode.rightChild));
            if (isLeafNode || isVisited) {
                System.out.print(currentNode.data + " ");
                stack.pop();
                preNode = currentNode;
            } else {
                if (currentNode.rightChild != null) {
                    stack.push(currentNode.rightChild);
                }
                if (currentNode.leftChild != null) {
                    stack.push(currentNode.leftChild);
                }
            }
        }
    }

    /**
     * 二叉树的层级遍历,使用队列实现
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void levelOrderTraversalByQueue(TreeNode<T> root) {
        Queue<TreeNode<T>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode<T> node = queue.poll();
            System.out.print(node.data + " ");
            if (node.leftChild != null) {
                queue.offer(node.leftChild);
            }
            if (node.rightChild != null) {
                queue.offer(node.rightChild);
            }
        }
    }

    /**
     * 二叉树的层级遍历,使用递归实现,对层级相同的节点进行输出
     * @param root 需要遍历的根节点
     * @param <T> 二叉树存储的数据对象类型
     */
    public static <T> void levelOrderTraversalByRecursion(TreeNode<T> root) {
        int depth = depth(root);
        for (int i = 0; i < depth; i++) {
            doLevelOrderTraversalByRecursion(root, 0, i);
        }
    }

    /**
     * 对二叉树相同层级的节点进行递归输出
     * @param node 需要遍历的根节点
     * @param level 遍历层级
     * @param target 目标层级
     * @param <T> 二叉树存储的数据对象类型
     */
    private static <T> void doLevelOrderTraversalByRecursion(TreeNode<T> node, int level, int target) {
        if (node == null) {
            return;
        }
        if (level == target) {
            System.out.print(node.data + " ");
        }
        doLevelOrderTraversalByRecursion(node.leftChild, level + 1, target);
        doLevelOrderTraversalByRecursion(node.rightChild, level + 1, target);
    }

    /**
     * 求二叉树的深度
     * @param root 需要遍历的根节点
     * @return 二叉树的根节点
     */
    private static int depth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Integer.max(depth(root.leftChild), depth(root.rightChild)) + 1;
    }

    public static void main(String[] args) {
        // 构建二叉树
        LinkedList<Integer> list = new LinkedList<>(Arrays.asList(3, 2, 9, null, null,
                10, null, null, 8, null, 4));
        TreeNode<Integer> root = createBinaryNode(list);
        System.out.print("前序遍历(递归):");
        preOrderTraversalByRecursion(root);
        System.out.println();
        System.out.print("中序遍历(递归):");
        midOrderTraversalByRecursion(root);
        System.out.println();
        System.out.print("后序遍历(递归):");
        postOrderTraversalByRecursion(root);
        System.out.println();
        System.out.print("前序遍历1(栈):");
        preOrderTraversalByStack(root);
        System.out.println();
        System.out.print("前序遍历2(栈):");
        preOrderTraversalByStack2(root);
        System.out.println();
        System.out.print("中序遍历(栈):");
        midOrderTraversalByStack(root);
        System.out.println();
        System.out.print("后序遍历1(栈):");
        postOrderTraversalByStack(root);
        System.out.println();
        System.out.print("后序遍历2(栈):");
        postOrderTraversalByStack2(root);
        System.out.println();
        System.out.print("层级遍历(队列):");
        levelOrderTraversalByQueue(root);
        System.out.println();
        System.out.print("层级遍历(递归):");
        levelOrderTraversalByRecursion(root);
        System.out.println();
    }
}