二叉树的三种基础遍历方式— java实现

二叉树的三种基础遍历方式有 前序遍历、中序遍历、后序遍历。

节点结构:

在这里插入图片描述


测试二叉树的结构:

在这里插入图片描述

前序遍历

前序遍历的遍历优先度:根节点> 左儿子节点 > 右儿子节点

在这里插入图片描述
当root 为空节点时 则直接结束函数,是递归的结束条件

由于根节点的遍历优先度最高,接下来直接用根节点,这里执行最简单的 打印操作 代表遍历树时使用的操作

在这里插入图片描述

接着分别递归左子树和右子树即可
在这里插入图片描述

注意一定先左子树然后再 右子树。

示例:

在这里插入图片描述

在这里插入图片描述

前序遍历非递归

前序遍历非递归有很多方式,这些方式的共同特点是都需要用到栈,因为用递归实现前序遍历时会用到系统提供的栈。

方法1:

public void preOrderNor1(TreeNode root) {
        if (root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            cur = top.right;
        }
    }

方法1我把它叫做 不停找左法,大概思路 是 cur 不断的找左子节点放到栈里面 直到左子树为 null,
然后每次弹出栈顶元素,代表栈顶元素的左子树节点和根节点已经 遍历完了,弹出之后 cur = 弹出元素 的右儿子。


该语句在含有递归时是递归的结束条件,在非递归里是 一个特判,这句话其实也可以不写(会跳过while循环),但是由于需要费脑子去判断可以写不写,一般写上绝对没有错。

在这里插入图片描述


在这里插入图片描述

只有当 cur == null 并且 栈为空的时候循环才会停止,栈不为空很好理解,只有当 cur 也为空的时候才结束循环的 原因有两点

刚开始栈没有插入值,所以循环进不去,另一点是因为当把整棵树的根节点从栈弹出的时候,此时如果右子树存在的话,但是栈已经为空了,所以会跳出循环。

方法2:

public void preOrderNor2(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode top = stack.pop();
            System.out.print(top.val + " ");
            if (top.right != null) {
                stack.push(top.right);
            }
            if (top.left != null) {
                stack.push(top.left);
            }
        }
    }

个人觉得方法二比方法一要好理解点,也好记忆。

方法二的思路是 先将整棵树的根节点放到栈里面,每次从栈弹出一个节点并且使用该节点,然后将该节点的右节点、左节点先后放到栈里面

右儿子比左儿子先入栈,那么左儿子节点一定就比右儿子先出栈,也就会先使用该节点。

中序遍历

中序遍历优先度:左儿子节点 > 根节点 > 右儿子节点
在这里插入图片描述
中序遍历递归只需要将 顺序变一下即可。
在这里插入图片描述

中序遍历非递归

中序遍历非递归的写法和 前序遍历非递归方法1的写法非常像,只需要改一处

在这里插入图片描述
那就是把打印操作放到弹出栈顶元素这里。

在每次弹出元素前,cur 一定等于null,栈顶元素一定没有左儿子节点,此时再打印,就保证了左儿子节点一定比根节点要先打印。

后序遍历

后序遍历优先度:左节点 > 右节点 > 根节点

在这里插入图片描述

在这里插入图片描述

后序遍历非递归

方法1:

public void postOrderNor1(TreeNode root) {
        if (root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode prev = null;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode peek = stack.peek();
            if (peek.right == null || peek.right == prev) {
                System.out.print(stack.pop().val + " ");
                prev = peek;
            } else {
                cur = peek.right;
            }
        }
    }

相比于前序和中序遍历,需要多用到一个变量prev,prev记录的是上一次被遍历过的节点

这个prev初始值可以是null,也可以是root,方法3当中的prev是不能为null

在这里插入图片描述
这里是 查看栈顶元素 而不是 弹出栈顶元素 的原因是


弹出栈顶元素代表着以该点为根节点的整棵树已经遍历完了

然而虽然栈顶元素的左子树一定为空,但是由于是后序遍历,右儿子节点的优先度比根节点要高,所以需要先遍历右子树之后,才可以弹出栈顶元素

在这里插入图片描述

peek.right == null 代表没有右子树,peek.right == prev 代表右子树已经遍历过了


只要满足这两种情况,就说明左子树和右子树已经搞定了,此时就可以弹出栈顶元素了,并且记录该节
点作为下次的prev

如果不满足if 里的条件,则证明 右子树没有被遍历,此时就需要让 cur 迈一步先去遍历右子树。


方法2:

public void postOrderNor2(TreeNode root) {
        if (root == null) return;
        Stack<TreeNode> stack1 = new Stack<>();
        Stack<TreeNode> stack2 = new Stack<>();
        stack1.push(root);
        while (!stack1.isEmpty()) {
            TreeNode top = stack1.pop();
            stack2.push(top);
            if (top.left != null) {
                stack1.push(top.left);
            }
            if (top.right != null) {
                stack1.push(top.right);
            }
        }

        while (!stack2.isEmpty()){
            System.out.print(stack2.pop().val + " ");
        }

    }

将前序遍历中的方法2 当中的两个 if 互换 即可将遍历优先度改为(根 > 右 > 左)
在这里插入图片描述
将 (根 > 右 > 左) 倒过来则就是 后序遍历,所以此时我们可以再创建一个 栈。

在这里插入图片描述
将每次弹出的栈顶元素放到另一个栈中,由于栈是先进后出,所以最后将所有元素挨个出栈,那么遍历的顺序就是(左 右 根)


方法3:

public void postOrderNor3(TreeNode root) {
        if (root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode prev = root;
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode peek = stack.peek();
            if (peek.left != null && peek.left != prev && peek.right != prev) {
                stack.push(peek.left);
            } else if (peek.right != null && peek.right != prev) {
                stack.push(peek.right);
            } else {
                System.out.print(stack.pop().val + " ");
                prev = peek;
            }
        }
    }

方法3和方法1的思路很像,prev的含义也是相同的,都代表的是上一次遍历过的节点

由于左儿子节点的优先度最高,所以方法1每一次都“无脑”先往左“走到黑”

方法3的思路是 “一步一步走”

在这里插入图片描述

注意这里的prev初始值不能是null,比如当peek等于下图当中的节点时,因为没有节点左右子树都遍历完的,以prev还是初始值null

因为此时peek的右儿子是null,恰好等于prev,程序会以为peek的右子树已经遍历完了,所以不会将peek的左节点入栈。


每一次查看栈顶元素后,需要判断三种情况,如果左子树节点没有被遍历,那么就将左子树节点入栈

因为左节点的优先度大于右节点,所以第二个判断右节点。

当左右节点都被搞定时,此时才到根节点,所以弹出打印即可,并且一定记得记录prev.

在这里插入图片描述

值得注意的是,这里的判断条件很容易出错,peek.left != null 和 peek.left != prev 很好理解

peek.right != prev 可千万不能落下,因为栈顶元素的右子树遍历过,那么一定意味着 左子树一定也被遍历过了。

如果没有这个判断条件,那么就会无限循环,在遍历完右子树完 会再次将左子树的节点入栈。

在这里插入图片描述

假如 G是上一个遍历完的,那么此时F一定会被提前遍历过(因为if 的顺序),如果没有那个判断条件,那么F 又会被再次 入栈从而无限循环。

当然这棵树会在左边那颗 B 的树时就会陷入死循环,会无限的打印 D H E
在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值