二叉树之Morris遍历
- 递归理解以及
morris
遍历 - 由
morris
遍历改成前序遍历 - 由
morris
遍历改成中序遍历 - 由
morris
遍历改成后续遍历 - 完整测试代码
递归理解以及morris
遍历
1 | static void rec(Node head) { |
这是我们用递归写的三种遍历方法,如果是下面的例子,上面程序输出如下:
程序输出:
1 | 1 2 4 8 8 8 4 4 2 5 5 9 9 9 5 2 1 3 6 10 10 10 6 6 3 7 11 11 11 7 7 3 1 |
- 可以看到,每一个结点都会被访问三次,也就是说,访问的过程中,每一个结点都会经过三次;
- 一个结点如果没有
parent
指针或者我们在非递归遍历中使用栈,是不能回到它的父节点的; - 而
morris
就是借助叶子结点的空闲指针帮助我们想办法回到父亲结点,省去了递归的空间,或者栈的空间,达到O(1)
空间;
每个结点来到三次:
下面介绍morris
遍历规则(现在和前、中、后序无关,就是morris
序):
流程看下图的举例:
按照上面的解释可以完全写出下面的morris
遍历过程(没有打印):
1 | static void morris(Node head){ |
由morris
遍历改成前序遍历
需要注意几点:
- 首先可以肯定是第一次来到这个结点的时候打印,所以是当
mostRight.right == null
(第一次来的时候,打印当前的cur
); - 其次,如果一个结点没有左子树,相当于只会来到结点一次,就直接打印一遍就可以了;
- 其实这样的做法等同于在递归的时候把打印的行为放在第一次来到这个结点的时候;
1 | //先序遍历 |
由morris
遍历改成中序遍历
中序更加简单:
- 如果一个结点有左子树,那么我打印的时机是我遍历完左子树之后的打印,也就是第二次来到这个结点时候打印,也就是
mostRight.right = cur
的时候打印; - 如果一个结点没有左子树,那么本来也要打印一下,打印完就往右边窜了;
- 本质就是不论怎么样要处理玩左子树才打印根结点;
1 | //中序遍历 |
由morris
遍历改成后续遍历
后续遍历比较麻烦: 因为morris
只会来到一个结点两次,但是递归会来到一个结点三次:
主要看以下几点:
- 我们只关心那些有左子树,也就是会到一个结点两次的结点;
- 逆序打印有左子树结点的左子树的右边界;
- 最后打印整棵树的右边界的逆序;
1 | //morris后续 |
完整测试代码(例子就是上面的例子):
1 | public class Morris { |
测试结果: