经典面试题--二叉树的前中后序遍历(递归&非递归)

Posted 算法和数据结构

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典面试题--二叉树的前中后序遍历(递归&非递归)相关的知识,希望对你有一定的参考价值。

概念:

        在二叉树中,所谓的前序遍历、中序遍历、后序遍历中的 前、中、后代表的都是根节点相对其左右子节点的位置。

       前序遍历就是先遍历根节点,然后遍历左节点,最后是右节点(根左右)

       中序遍历就是先遍历左节点,然后遍历中间的根节点,最后是右节点(左根右)

       后序遍历就是先遍历左节点,然后遍历是右节点,最后是中间的根节点(左右根)


举个例子:

假设咱们有一颗二叉树如下:

那么前中后续遍历结果如下

前序遍历(根左右):A-B-D-F-G-H-I-E-C

中序遍历(左根右):F-D-H-G-I-B-E-A-C

后序遍历(左右根):F-H-I-G-D-E-B-C-A



需要注意的一点是,这里的根是一个相对的概念,如上图二叉树,对于整颗二叉树来说,根节点是A,但是对于A的左子树B以及B的子节点来说,根节点就是B...


代码:


前序遍历递归实现:

思路:按照根左右,先输出根节点,再依次递推左右节点即可

 1public void preOrderTraverse1(TreeNode root) {
2    if (root == null) {
3        return;
4    }
5    //前序遍历 根左右 先输出根节点,再递归左右子树
6    System.out.print(root.val + "->");
7    //递归左子树
8    preOrderTraverse1(root.left);
9    //递归右子树
10    preOrderTraverse1(root.right);
11}


前序遍历递归非实现:

思路:非递归遍历其实就是用循环代替递归,通过自己创建栈来替换系统栈,分为以下步骤:

  1. 输出根节点

  2. 根节点压栈,目的是用于出栈时找到右节点

  3. 遍历左子树,当指针指向当前节点不为空时,循环1、2操作

  4. 当前节点为空,弹出栈顶元素,找到右子树,循环1、2、3操作

 1public void preOrderTraverse2(TreeNode root) {
2    //非递归遍历其实就是用循环代替递归、用栈来代替系统栈
3    Stack<TreeNode> stack = new Stack<>();
4    //用node代替root 不更改原二叉树结构
5    TreeNode node = root;
6    while (node != null || !stack.empty()) {
7        if (node != null) {
8            //当前根节点不为null 输出该节点
9            System.out.print(node.val + "->");
10            //将该节点压栈 用于后续找到右节点
11            stack.push(node);
12            //遍历左节点
13            node = node.left;
14        } else {
15            //左节点为空时,弹出之前压入栈的根节点
16            TreeNode tem = stack.pop();
17            //通过根节点找到右子节点 继续遍历右子节点
18            node = tem.right;
19        }
20    }
21}


中序遍历递归实现:

思路:按照左根右,先递归左子树,再输出根节点,左右递归右子树即可

 1public void midOrderTraverse(TreeNode root) {
2    if (root == null) {
3        return;
4    }
5    //中序遍历 左根右
6    //先递归左子树,再输出根节点,再递归右子树
7    midOrderTraverse(root.left);
8    //输出根节点
9    System.out.print(root.val + "->");
10    //递归右子树
11    midOrderTraverse(root.right);
12}


中序遍历递归非实现:

思路:和前序遍历的非递归实现类似,分为以下步骤:

  1. 根节点压栈

  2. 遍历左子树,当指针指向当前节点不为空时,循环1、2操作

  3. 当前节点为空,弹出栈顶元素,输出栈顶元素值

  4. 找到栈顶元素右子树,循环1、2、3操作(由于栈先进后出特性,此时该右子树相对的左节点值和根节点已输出)


 1public void midOrderTraverse2(TreeNode root) {
2    //非递归遍历其实就是用循环代替递归、用栈来代替系统栈
3    Stack<TreeNode> stack = new Stack<>();
4    //用node代替root 不更改原二叉树结构
5    TreeNode node = root;
6    while (node != null || !stack.isEmpty()) {
7        if (node != null) {
8            //当前根节点不为null 压栈
9            //用于后续找右子树
10            stack.push(node);
11            //遍历左子树
12            node = node.left;
13        } else {
14            //左节点为空时,弹出之前压入栈的根节点
15            TreeNode tem = stack.pop();
16            //输出该节点值
17            System.out.print(tem.val + "->");
18            //遍历右子树
19            node = tem.right;
20        }
21    }
22}


后序遍历递归实现:

思路:按照左右根,先递归左右节点,最后输出根节点

 1public void lastOrderTraverse(TreeNode root) {
2    if (root == null) {
3        return;
4    }
5    //后序遍历 左右根
6    //先递归左右子树,最后才是根节点
7    lastOrderTraverse(root.left);
8    //递归右子树
9    lastOrderTraverse(root.right);
10    //输出根节点
11    System.out.print(root.val + "->");
12}


后序遍历递归非实现:

思路:

        后序遍历的非递归实现是三种非递归遍历中最难的一种,因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问之后,才能访问根结点,这就为流程的控制带来了难题

        这里同样使用栈来保存节点,同时这里需要使用一个辅助节点来标识最近访问过的节点是啥,具体步骤如下

  1. 根节点压栈

  2. 根节点右左子树压栈,此时栈从下往上的顺序为 根、右、左,根据栈先进后出的特性就能保住该节点的输出顺序为 左右根

  3. 当栈顶元素无子树时,表明已经是叶子节点,直接输出,同时记录该输出的节点

  4. 当已输出的节点为 栈顶元素的左子树或者右子树时,表明以栈顶元素为根节点的子树已遍历完成,此时该节点出栈输出

  5. 重复1、2、3、4操作,直至栈空,所有元素输出完毕

第四步很重要,由于栈里的元素是按根、右、左的形式存放的,如果已输出的节点为根节点的右子树,表明左子树和右子树都已出栈,如果已输出的节点为根节点的左子树,表明右子树为空,此时根节点的所有子树同样遍历完成!!


 1public void lastOrderTraverse2(TreeNode root) {
2    if (root == null) {
3        return;
4    }
5    TreeNode cur, pre = null;
6    //非递归遍历其实就是用循环代替递归、用栈来代替系统栈
7    Stack<TreeNode> stack = new Stack<>();
8    //后序遍历 左右根 先将根节点压栈 在将右节点压栈,左右再将左节点压栈
9    //这样根据栈先进后出的特性 出栈时才能达到左右根的效果
10    stack.push(root);
11    while (!stack.empty()) {
12        //注意 这里栈顶根元素没有真的出栈 
13        // 只是获取了栈顶根的引用
14        cur = stack.peek();
15        if ((cur.left == null && cur.right == null
16                || (pre != null && (pre == cur.left || pre == cur.right))) {
17            //当栈顶元素左右都为空 或者 其左右节点已出栈时 
18            // 表示该根节点左右子树已遍历完毕 根节点可以出栈
19            //输出根节点
20            System.out.print(cur.val + "->");
21            //栈顶元素出栈
22            stack.pop();
23            //替换pre pre含义为未栈内还未出栈元素的左右子节点
24            pre = cur;
25        } else {
26            //将右节点压栈
27            if (cur.right != null) {
28                stack.push(cur.right); 
29            }
30            //最后才是将左节点压栈
31            if (cur.left != null) {
32                stack.push(cur.left);
33            }
34        }
35    }
36}


题外话:这篇文章量有点大,中午需要加个鸡腿补补了经典面试题(四)--二叉树的前中后序遍历(递归&非递归)

以上仅是个人思路解法,觉得还不错欢迎点赞关注分享


往期精彩推荐




扫描下方二维码,关注公众号,更多精彩等你发现


以上是关于经典面试题--二叉树的前中后序遍历(递归&非递归)的主要内容,如果未能解决你的问题,请参考以下文章

二叉树面试题:前中序求后序中后序求前序

必须掌握,二叉树的前中后序遍历(迭代+递归)详细代码与思路

蓝桥Java每日一练————6.二叉树的前中后序遍历(递归与迭代)

二叉树的前序中序后序遍历相互求法

二叉树的前中后序遍历(非递归实现)

二叉树的前中后序遍历简单的递归