Morris遍历
程序流程:
假设指针cur指向当前节点,cur从头结点开始。
- 如果cur无左孩子,则cur = cur.right;
- 如果cur右左孩子,则找到cur左子树上最右的节点,记为mostRight,分为以下两种情况:
- 若mostRight的right指针为null,则让其指向cur,且cur = cur.left;
- 若mostRight的right指针指向cur,则让其指回null,且cur = cur.right。
空间复杂度O(1),时间复杂度O(n)。
贴代码:
static class Node {
int val;
Node left;
Node right;
public Node(int val) {
this.val = val;
left = null;
right = null;
}
}
public static void morris(Node head) {
if (head == null) return;
Node cur = head;
while (cur != null) {
System.out.println(cur.val);
if (cur.left == null) {
cur = cur.right;
} else {
Node mostRight = cur.left;
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
} else {
mostRight.right = null;
cur = cur.right;
}
}
}
}
特点
- 当某个节点有左子树,则会打印该节点两次;
- 当第二次回到某个节点时,它的左子树已遍历完。
实质
Morris遍历是利用左子树最右节点的指针指向null或指向自己来标记是第一次来到该节点还是第二次来到该节点。
将Morris遍历改前序遍历
在以下两种情况下打印节点:
- 当节点没有左子树时,打印当前节点;
- 当节点有左子树时并且第一次访问时打印该节点。那么何时是第一次访问呢?就是当mosrRight的右指针为null时。
贴代码:
public static void morrisPre(Node head) {
if (head == null) return;
Node cur = head;
while (cur != null) {
if (cur.left == null) {
System.out.println(cur.val);//情况一:如果一个节点没有左子树时打印
cur = cur.right;
} else {
Node mostRight = cur.left;
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
System.out.println(cur.val);//情况二:如果有左子树且第一次来到左子树时打印
mostRight.right = cur;
cur = cur.left;
} else {
mostRight.right = null;
cur = cur.right;
}
}
}
}
将Morris遍历改中序遍历
在以下两种情况下打印节点:
- 当节点没有左子树时,打印当前节点;
- 当节点有左子树时并且第二次访问时打印该节点。那么何时是第二次访问呢?就是当mosrRight的右指针不为null时。
贴代码:
public static void morrisIn(Node head) {
if (head == null) return;
Node cur = head;
while (cur != null) {
if (cur.left == null) {
System.out.println(cur.val);//情况一:如果一个节点没有左子树时打印
cur = cur.right;
} else {
Node mostRight = cur.left;
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
} else {
System.out.println(cur.val);//情况二:如果有左子树且第二次来到左子树时打印
mostRight.right = null;
cur = cur.right;
}
}
}
}
将Morris遍历改后序遍历
在以下两种情况下打印节点:
- 只有当到达某个节点两次时逆序打印该节点左子树的右边界;
- 在代码的最后逆序打印整棵树的右边界,而逆序的过程就和单向链表的反转过程类似。
贴代码:
public static void morrisPost(Node head) {
if (head == null) return;
Node cur = head;
while (cur != null) {
if (cur.left == null) {
cur = cur.right;
} else {
Node mostRight = cur.left;
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
} else {
mostRight.right = null;
printEdge(cur.left);//情况一:逆序打印左子树的右边界
cur = cur.right;
}
}
}
printEdge(head);//情况二:逆序打印整棵树的右边界
}
public static void printEdge(Node node) {
Node tail = reverseNode(node);
Node p = tail;
while (p != null) {
System.out.println(p.val);
p = p.right;
}
reverseNode(tail);
}
public static Node reverseNode(Node from) {
Node pre = null;
Node next = null;
while (from != null) {
next = from.right;
from.right = pre;
pre = from;
from = next;
}
return pre;
}
到此神级算法Morris遍历已讲完了。睡一觉估计就忘了。。