经典面试题--二叉树的前中后序遍历(递归&非递归)
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操作
当前节点为空,弹出栈顶元素,找到右子树,循环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操作(由于栈先进后出特性,此时该右子树相对的左节点值和根节点已输出)
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操作,直至栈空,所有元素输出完毕
第四步很重要,由于栈里的元素是按根、右、左的形式存放的,如果已输出的节点为根节点的右子树,表明左子树和右子树都已出栈,如果已输出的节点为根节点的左子树,表明右子树为空,此时根节点的所有子树同样遍历完成!!
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}
题外话:这篇文章量有点大,中午需要加个鸡腿补补了
以上仅是个人思路解法,觉得还不错欢迎点赞关注分享
往期精彩推荐
以上是关于经典面试题--二叉树的前中后序遍历(递归&非递归)的主要内容,如果未能解决你的问题,请参考以下文章