二叉树的遍历

93 阅读2分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

二叉树的遍历

深度优先遍历

前序遍历

前序遍历其检索顺序为根节点、左孩子节点、右孩子节点。

所以检索结果为:5 、2 、1、3、6、7

中序遍历

中序遍历其检索顺序为左孩子节点、根节点、右孩子节点。

所以检索结果为:1、2、3、5、6、7

后序遍历

后续遍历其检索顺序为左孩子节点、右孩子节点、根节点。

所以检索结果为:1、3、2、7、6、5

递归代码实现

首先定义二叉树节点对象

/**
 * 二叉树节点对象
 */
class TreeNode{
    Object data;
    TreeNode leftNode;
    TreeNode rightNode;

    public TreeNode(Object data){
        this.data = data;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "data=" + data +
                ", leftNode=" + leftNode +
                ", rightNode=" + rightNode +
                '}';
    }
}

定义二叉树构建方法,这里沿用递归逻辑

    /**
     * 创建二叉树,如果节点不包含左右子树,那么节点置为null占位
     * @param list
     * @return
     */
    public static TreeNode createBinaryTree(LinkedList<Object> list){
        if (list == null || list.isEmpty()){
            return null;
        }

        TreeNode treeNode = null;
        Object data = list.removeFirst();
        if (data != null){
            treeNode = new TreeNode(data);
            // 如果没有遇到节点为null的会一直给左节点赋值(类似递归)
            treeNode.leftNode = createBinaryTree(list);
            treeNode.rightNode = createBinaryTree(list);
        }
        return treeNode;
    }

假如存在二叉树如下

image.png

那么构建节点的list传参应该如下所示

{329nullnull10nullnull8null4}

遍历代码

   /**
     * 前序遍历遍历顺序(根节点、左孩子节点、右孩子节点)
     * @param treeNode
     */
    public static void preOrderTraverse(TreeNode treeNode){
        if (treeNode == null){
            return;
        }
        System.out.print(treeNode.data+"\t");
        preOrderTraverse(treeNode.leftNode);
        preOrderTraverse(treeNode.rightNode);
    }

    /**
     * 中序遍历遍历顺序(左孩子节点、根节点、右孩子节点)
     * @param treeNode
     */
    public static void inOrderTraverse(TreeNode treeNode){
        if (treeNode == null){
            return;
        }
        inOrderTraverse(treeNode.leftNode);
        System.out.print(treeNode.data+"\t");
        inOrderTraverse(treeNode.rightNode);
    }

    /**
     * 后序遍历遍历顺序(左孩子节点、右孩子节点、根节点)
     * @param treeNode
     */
    public static void postOrderTraverse(TreeNode treeNode){
        if (treeNode == null){
            return;
        }
        postOrderTraverse(treeNode.leftNode);
        postOrderTraverse(treeNode.rightNode);
        System.out.print(treeNode.data+"\t");
    }

测试代码

    public static void main(String[] args) {
        List<Object> objectList = Arrays.asList(new Object[]
                       {329nullnull10nullnull8null4});
        LinkedList<Object> list = new LinkedList<>(objectList);
        TreeNode binaryTree = createBinaryTree(list);
        System.out.println(binaryTree);
        // 前序遍历 3 2 9 10 8 4
        preOrderTraverse(binaryTree);
        System.out.println();
        // 中序遍历 9 2 10 3 8 4
        inOrderTraverse(binaryTree);
        System.out.println();
        // 后序遍历 9 10 2 4 8 3
        postOrderTraverse(binaryTree);
    }

栈遍历二叉树代码实现

除了递归其实我们可以拓展一种解决思路,递归的目的是回溯,通过回溯才能完成二叉树的遍历,但其实除了递归有此特性,栈同样可以,栈可以利用其先入后出的特性实现回溯的效果。

以前序遍历为例,存在如下二叉树

image.png

先将节点3入栈,节点3存在左节点2,所以将节点2入栈,节点2存在左节点9所以节点9入队。

image.png

当遍历到节点9发现没有左孩子节点,而且不存在右孩子节点,所以需要回溯到节点9的父节点2,将节点9出队,访问节点2,同时得到节点2的右孩子节点5,这时节点2没有存在价值,会将节点2出队,同时将节点5入队

image.png

遍历到节点5发现又没有左孩子节点以及右孩子节点,这时需要回溯到节点5的父节点3,通过节点3得到其右孩子节点8,节点3同样无意义将节点3出队,同时节点8入队

image.png

遍历节点8发现其只存在右孩子节点4,将节点8出队节点4入队。

image.png

最后遍历节点4因为不存在左孩子节点和右孩子节点,节点4出队,栈为空完成遍历。

按照这个思路得到前序遍历如下代码

    /**
     * 前序遍历,采用栈的回溯性遍历二叉树
     * @param root
     */
    public static void preOrderTraverseByStack(TreeNode root){
        Stack<TreeNode> stack = new Stack<>();
        TreeNode treeNode = root;
        while (treeNode!=null || !stack.isEmpty()){
            while (treeNode!=null){
                stack.push(treeNode);
                System.out.print(treeNode.data+"\t");
                treeNode = treeNode.leftNode;
            }

            if (!stack.isEmpty()){
                TreeNode treeNode1 = stack.pop();
                treeNode = treeNode1.rightNode;
            }
        }
    }

中序遍历只是打印位置不一样,如下

    /**
     * 中序栈遍历  采用栈的回溯性
     * @param root
     */
    public static void inOrderTraverseByStack(TreeNode root){
        Stack<TreeNode> stack = new Stack<>();
        TreeNode treeNode = root;
        while (treeNode!=null || !stack.isEmpty()){
            while (treeNode!=null){
                stack.push(treeNode);
                treeNode = treeNode.leftNode;
            }

            if (!stack.isEmpty()){
                TreeNode treeNode1 = stack.pop();
                System.out.print(treeNode1.data+"\t");
                treeNode = treeNode1.rightNode;
            }
        }
    }

需要注意的是后续遍历及其特殊,后续遍历的顺序是左孩子节点、右孩子节点、父节点,在打印时需要判断当前节点是左孩子节点还是右孩子节点,如果为左孩子节点那么需要先访问右孩子节点再回来访问根节点,如果为右孩子节点就可以直接访问根节点。

    /**
     * 后序遍历 栈回溯
     * @param root
     */
    public static void postOrderTraverseByStack(TreeNode root){
        Stack<TreeNode> stack = new Stack<>();
        TreeNode treeNode = root;

        TreeNode flag = null;
        while (treeNode!=null || !stack.isEmpty()){
            while (treeNode!=null){
                stack.push(treeNode);
                treeNode = treeNode.leftNode;
            }

            // 不出队,只取值
            treeNode = stack.peek();

            /**
             * flag存放上一次访问节点
             * 用于判断上一次访问节点是左孩子节点还是右孩子节点
             * 若是左孩子节点,则需要跳过根节点,先进入右孩子节点,再回头访问根节点
             * 若是右孩子节点,则直接访问根节点
             *
             * 会访问两次根节点,第一次是借助根节点访问右孩子节点,如果右孩子节点已经被访问就打印根节点
             */
            if (treeNode.rightNode==null || flag == treeNode.rightNode){
                flag = stack.pop();
                System.out.print(treeNode.data+"\t");
                treeNode = null;
            }else {
                treeNode = treeNode.rightNode;
            }
        }
    }

广度优先遍历

广度优先遍历就是一层一层访问,例如存在二叉树

image.png

从根节点往下一层一层遍历,那么遍历结果为

3、2、8、9、10、4

这里实现方式同样可以使用递归实现,但这里拓展以下思路,这里采用队列实现

  1. 根节点3入队

  2. 节点3出队,输出节点3,并得到节点3的左右孩子节点2,8入队。

  3. 节点2出队,输出节点2,并得到节点2的左右孩子节点9,10入队。

  4. 节点8出队,输出节点8,并得到节点8的右孩子节点4入队。

  5. 节点9出队,输出节点9,其左右孩子节点都为空,不入队(节点10,节点4类似)。

  6. 队列为空程序结束。

image.png

队列遍历二叉树代码实现

    /**
     * 层序遍历 一层一层遍历采用队列实现
     * @param root
     */
    public static void levelOrderTraversal(TreeNode root){
        Queue<TreeNode> queue = new LinkedBlockingQueue<>();
        queue.offer(root);

        while (!queue.isEmpty()){
            // peek检索但不删除  poll检索并删除  remove删除
            // offer插入        put 插入
            TreeNode treeNode = queue.poll();
            System.out.print(treeNode.data+"\t");
            if (treeNode.leftNode!=null){
                queue.offer(treeNode.leftNode);
            }
            if (treeNode.rightNode!=null){
                queue.offer(treeNode.rightNode);
            }
        }
    }