-
二叉树的遍历可以选择 递归 或者 自己创建栈空间做
时间复杂度O(N),空间复杂度O(H) -
而Morris遍历可以实现空间复杂度O(1)
01 Morris遍历的过程
首先,令current = head
- (1)当cuurrent没有左子树,current向右(
current = current.right
) - (2)当cuurrent有左子树,找左子树最右mostRight
- (2.1)
当MostRight的右指针为空(MostRight.right == null
),
则该右指针指向current(MostRight.right = current
),current向左移动(current = current.left
) - (2.2)
当MostRight的右指针指向current(MostRight.right == current
),
则右指针指向空(MostRight.right == null
),current向右移动(current = current.right
) - (2.3)
当current指向空(current == null
),
则停止
- (2.1)
具体移动过程可以参考:
- 二叉树的Morris遍历 (1) https://www.bilibili.com/video/BV1P34y1S7QJ【13min~结束】
02 Morris序的特点
Morris序的特点:
- (1)节点无左树,节点来一次
- (2)节点有左树,节点来两次。且第1次到来和第2次到来之间,完成其的左子树遍历。
Morris序列是利用底层节点右指针的指向,方便完成回到上级。(可以不利用栈结构)
怎么知道是第一次来到这个节点,还是第二次来到?
如果左子树的最右节点为空(MostRight.right == null
),那一定是第一次来到;
如果左子树的最右节点指向自己,那一定是第二次来到(MostRight.right == current
)
如果是第一次来到自己,则会去左子树完成同样的操作;
如果是第二次来到自己,那就会开始遍历右子树。
证明时间复杂度:
每一个左子树是访问了2遍的,是常数次,因此依然是O(N)
- 参考:Morris遍历改后序遍历https://www.bilibili.com/video/BV1q3411176M/?spm_id_from=333.788.recommend_more_video.2【0~6min】
03 Morris遍历的过程(Java实现)
public static void morris(Node head) {
if (head== null) {
return;
}
Node Cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left; // mostRight是左树的节点
if (mostRight != null) {
// 找到左树的最右节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {// 第一次来到current
mostRight.right = cur;
cur = cur.left;
continue;
} else { // mostRight.right == cur 第二次来到current
mostRight.right = null;
}
}
cur = cur.right;
}
}
04 利用Morris完成先序遍历和中序遍历
类比与递归方法完成二叉树的先序遍历、中序遍历和后续遍历,递归方法是一个节点访问了3次,分别在第一次、第二次和第三次到达时打印,从而可以实现先序遍历、中序遍历和后续遍历。
对于Morris遍历:
- 先序遍历: 第一次到达的时候就打印
- 中序遍历: 只能到达一次的节点,第一次打印;能到达两次的节点,第二次打印(如何知道自己能到达两次?有左子树的节点,必然会到达2次)
4.1 先序遍历
public static void morrisPre(Node head) {
if (head== null) {
return;
}
Node Cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left; // mostRight是左树的节点
if (mostRight != null) {// 可以到达自己两次的节点
// 找到左树的最右节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {// 第一次来到current
System.out.println(cur.value); // 中序遍历新加行
mostRight.right = cur;
cur = cur.left;
continue;
} else { // mostRight.right == cur 第二次来到current
mostRight.right = null;
}
}else{// 只能到达自己一次的节点 // 中序遍历新加行
System.out.println(cur.value);// 中序遍历新加行
}// 中序遍历新加行
cur = cur.right;
}
}
4.2 中序遍历
public static void morrisIn(Node head) {
if (head== null) {
return;
}
Node Cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left; // mostRight是左树的节点
if (mostRight != null) {// 可以到达自己两次的节点
// 找到左树的最右节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {// 第一次来到current
mostRight.right = cur;
cur = cur.left;
continue;
} else { // mostRight.right == cur 第二次来到current
mostRight.right = null;
}
}
// 对于能到达两次的节点,第一次到达的时候,会有个continue,所以无法运行到这来
System.out.println(cur.value); // 中序遍历新加行
cur = cur.right;
}
}
05 后续遍历
-
第二次到达时,逆序打印左树的右边界(什么是处理时机?第二次到达某节点时)
-
逆序打印,如果还想保证空间复杂度O(1),就需要不额外申请数据结构
-
怎么做?单链表结构,让每个节点的右指针指向上一个节点
public static void morrisPos(Node head) {
if (head== null) {
return;
}
Node Cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left; // mostRight是左树的节点
if (mostRight != null) {// 可以到达自己两次的节点
// 找到左树的最右节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {// 第一次来到current
mostRight.right = cur;
cur = cur.left;
continue;
} else { // mostRight.right == cur 第二次来到current
mostRight.right = null;
printEdge(cur.left) // 后序遍历新加行
}
}
cur = cur.right;
}
printEdge(head) // 后序遍历新加行
}
//以head为头的树,请逆序打印该树的右边界
public static void printEdge(Node head) {
Node tail = reverseEdge(head);
Node cur = tail;
while (cur != null) {
System.out.print(cur.value +”");
cur = cur.right;
}
reverseEdge(tail);
}
// 从from开始,只关心right指针,并且认为right指针就是单链表指针,请把这个单链表逆序
public static Node reverseEdge(Node from) {
Node pre = null;
Node next = null;
while (from != null) {
next = from.right;
from.right = pre;
pre = from;
from = next;
}
return pre;
}
06 参考
是左程云的讲解,感觉讲的非常好
这个up的虽然没有做成合集不方便找,但是只截出了讲课的部分,没有聊天的部分,看起来还是很顺畅的
- 二叉树的Morris遍历 (1)(Morris过程和Morris序列):https://www.bilibili.com/video/BV1P34y1S7QJ
- 二叉树的Morris遍历 (2)(Java代码实现):https://www.bilibili.com/video/BV1Bg411F7oo
- Morris遍历改成先序和中序遍历:https://www.bilibili.com/video/BV1zL411s7ov
- Morris遍历改成先序和中序遍历:https://www.bilibili.com/video/BV1q3411176M
06 leetcode相关题目
- 99题
import java.util.Scanner; import java.util.*; public class Main { public static class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {} TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } } public static void main(String[] args) { // [10,5,15,0,8,13,20,2,-5,6,9,12,14,18,25] TreeNode head = new TreeNode(10); head.left = new TreeNode(5); head.right = new TreeNode(15); head.left.left = new TreeNode(0); head.left.right = new TreeNode(8); head.right.left = new TreeNode(13); head.right.right = new TreeNode(20); head.left.left.left = new TreeNode(2); head.left.left.right = new TreeNode(-5); head.left.right.left = new TreeNode(6); head.left.right.right = new TreeNode(9); head.right.left.left = new TreeNode(12); head.right.left.right = new TreeNode(14); head.right.right.left = new TreeNode(18); head.right.right.right = new TreeNode(25); process(head);// 按层输出 System.out.println(""); recoverTree(head); process(head);// 按层输出 } public static void recoverTree(TreeNode root) { TreeNode head = root; TreeNode x = null, y = null, pred = null, mR = null; while(root != null){ mR = root.left; if(mR != null){ while(mR.right != null && mR.right != root){ mR = mR.right; } if(mR.right == null){ // 搭桥 mR.right = root; root = root.left; continue; }else{ // 拆桥 mR.right = null; } } if(pred != null){ if(pred.val > root.val){ y = root; if(x == null){ x = pred; }else{ // break; } } } pred = root; // 中序遍历 // System.out.print(root.val+" "); root = root.right; } swap(x,y); } public static void swap(TreeNode x, TreeNode y){ int temp = x.val; x.val = y.val; y.val = temp; } // 按层输出 public static void process(TreeNode head){ if(head == null){ return; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(head); while(!queue.isEmpty()){ TreeNode cur = queue.poll(); System.out.print(cur.val + " "); if(cur.left!=null){ queue.add(cur.left); } if(cur.right!=null){ queue.add(cur.right); } } } }