Morris遍历用于遍历二叉树,时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1),主要利用树中空闲指针来节省空间的
1 遍历过程
遍历步骤
假设来到当前节点cur
,开始时cur
来到头节点位置
- 如果
cur
没有左孩子,cur
向右移动 - 如果
cur
有左孩子,找到左子树上最右的节点mostRight
:- 如果
mostRight
的右指针指向空,让其指向cur
,然后cur
向左移动 - 如果
mostRight
的右指针指向cur
,让其指向null
,然后cur
向右移动
- 如果
cur
为空时遍历停止
图解Morris
1.初始cur
来到4节点,cur
有左子树,找到左子树最右节点,节点3右指针为空,指向cur
,cur
向左移动来到节点2
2.cur
来到节点2,cur
有左子树,找到左子树最右节点,节点1右孩子空,指向cur
,cur
向左移动来到1节点
3.cur
来到1节点,cur
此时没有左子树,cur
向右指针方向移动,回到了节点2
4.cur
来到节点2,cur
此时有左子树,找到左子树的最右节点,发现节点1的右指针指向cur
,将右指针调回null,然后cur
向右指针移动,cur
来到3
5.cur
来到3节点,cur
此时没有左子树,cur
向右方向移动,cur
回到4节点
6.cur
来到4节点,cur
此时有左子树,找到cur
左子树最右侧节点,发现节点3的右指针指向cur
,将其调回null,cur
向右来到6节点
7.cur
来到节点6,cur
此时有左子树,找到cur
左子树最右节点,发现节点5指针指向null,让其指向cur
,cur
向左移动到节点5
8.cur
来到5节点,cur
此时没有左子树,cur
向右指针方向移动,cur
回到了6节点
9.cur
来到6节点,此时cur
有左子树,找到左子树最右节点,发现节点5右指针指向cur
,将节点5右指针调回null,cur
向右来到节点7
10.cur
来到节点7,cur
此时没有左子树,cur
向右指针方向移动,cur
来到null
cur
为空,过程停止
cur
依次到达的节点为4、2、1、2、3、4、6、5、6、7
2 遍历实质
建立一种机制,对于没有左子树的节点只到达一次,对于有左子树的节点到达两次
避免使用栈结构,通过让底层节点指向null
的空闲指针指回上层的某个节点,从而完成下层到上层的移动
在一棵二叉树中,对于有左子树的节点可以到达两次(4、2、6),对于没有左子树的节点都只会到达一次
对于任意一个只能到达一次的节点x
,接下来cur
跑到x
的右子树上,要么返回上级
对于任意一个能到达两次的节点Y
,在第一次到达Y
之后的,cur
都会先去Y
的左子树上转一圈,第二次来到Y
,接下来cur
要么跑到Y
的右子树上,要么就返回上级
如果Y
的左子树上的最右节点的指针指向null
的,那么此时cur
第一次到达Y
,如果指向Y
,cur
是第二次到达Y
3 Morris遍历实现
public void morris(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) { // 如果当前cur有左子树
// 找到cur左子树最右的节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}//while结束后mostRight就是左子树最右节点
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue; // 回到最外层的while继续判断cur情况
} else { // mostRight指向cur
mostRight.right = null; // 调回null
}
}
// cur如果没有左子树,cur向右移动
// 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
cur = cur.right;
}
}
4 应用
4.1 先序遍历
- 对于
cur
只能到达一次的节点(无左子树的节点),cur
到达直接打印 - 对于
cur
到达两次的节点(有左子树的节点),cur
第一次到达时打印,第二次到达时不打印
public void morrisPre(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) { // 如果当前cur有左子树
// 找到cur左子树最右的节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}// while结束后mostRight就是左子树最右节点
if (mostRight.right == null) { // 第一次到达
mostRight.right = cur;
// visit
System.out.print(cur.value + " ");
cur = cur.left;
continue; // 回到最外层的while继续判断cur情况
} else { // mostRight指向cur //第二次到达
mostRight.right = null; // 调回null
}
} else { // cur没有左子树
System.out.println(cur.value + " ");
}
// cur如果没有左子树,cur向右移动
// 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
cur = cur.right;
}
}
4.2 中序遍历
- 对于
cur
只能到达一次的节点(无左子树的节点),cur到达直接打印 - 对于
cur
到达两次的节点(有左子树的节点),cur
第一次到达时不打印,第二次到达时打印
public void morrisIn(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) { // 如果当前cur有左子树
// 找到cur左子树最右的节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}// while结束后mostRight就是左子树最右节点
if (mostRight.right == null) { // 第一次到达
mostRight.right = cur;
cur = cur.left;
continue; // 回到最外层的while继续判断cur情况
} else { //mostRight指向cur // 第二次到达
mostRight.right = null; // 调回null
}
}
// cur如果没有左子树,cur向右移动
// 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
System.out.print(cur.value + " ");
cur = cur.right;
}
}
4.3 后序遍历
- 对于
cur
只能到达一次的节点(无左子树的节点),直接跳过,没有打印行为 - 对于
cur
到达两次的节点(有左子树的节点),cur
第一次到达时没有打印行为,第二次到达X
时,逆序打印X
左子树的右边界 cur
遍历完成后,逆序打印整个树的右边界
打印一棵树的右边界相当于在一条单链表进行操作
假设cur
第二次到达A,并且要逆序打印cur
左子树的右边界
- 首先将
E.R
指向null,然后将右边界逆序调整,类似于单链表逆序操作
从节点E开始,依次通过每个节点的right
指针逆序打印整个左边界,打印完B后把右边界逆序一次
逆序树的边界
public Node reverseEdge(Node from) {
Node pre = null;
Node next = null;
while (from != null) {
next = from.right;
// 右指针指向上面的边
from.right = pre;
from = next;
}
return pre;
}
打印逆转后的边
public void printEdge(Node head) {
// 逆转前边界的最右节点
Node tail = reverseEdge(head);
Node cur = tail;
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.right;
}
reverseEdge(tail);
}
后序遍历
public void morrisPos(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) { // 如果当前cur有左子树
// 找到cur左子树最右的节点
while (mostRight.right != null && mostRight != cur) {
mostRight = mostRight.right;
}// while结束后mostRight就是左子树最右节点
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue; // 回到最外层的while继续判断cur情况
} else { // mostRight指向cur
mostRight.right = null; //调回null
printEdge(cur.left);
}
}
// cur如果没有左子树,cur向右移动
// 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
cur = cur.right;
}
// cur遍历完成后,逆序打印整棵树的最右边界
printEdge(head);
}