原本二叉树算法中的痛点:
二叉树之中,无论时间复杂度如何,空间复杂度为O(h),h为二叉树的高度。
morris算法将二叉树的空间复杂度变为O(1),同时时间复杂度不变,依旧保持O(n)。
算法流程:
- 现在的当前节点记为cur,如果cur无左孩子,则cur向右移动,cur=cur.right
- 如果cur有左孩子,则找到左子树上最右的节点,记为mostright
1)如果mostright的右指针指向空,则让其指向cur,此时cur向左移动,cur = cur.left
2)如果mostright的左指针指向空,则让其指向空,cur向右移动
利用右子树,可以将整个二叉树都划分为右子树,每个节点都过两遍,此时可以直到总共遍历两次,2N,所以总的时间复杂度去除常数,依旧为O(N)。
代码
public static void morris(Node head){
if(head == null){
return ;
}
Node cur = head;
Node mostright = null;
while(cur != null){
mostright = cur.left;
if(mostright != null){
//寻找左子树的最右的孩子
while(mostright.right != null && mostright.right != cur){
mostright = mostright.right;
}
if(mostright.right == null){
mostright.right = cur;
cur = cur.left;
continue;
}else{
mostright.right = null;
}
}
cur = cur.right;
}
}
morris遍历本质:
对于递归版本的二叉树,来到一个节点会遍历左子树和右子树,遍历完左子树之后,会重新回到当前节点,遍历完右子树之后,同样需要返回当前节点,所以总共遍历了当前节点三次。递归是利用行号,利用二叉树中的信息进行判断第一次还是第二次第三次来到当前节点。
如果这个树有左子树,则遍历这个节点两次,否则遍历这个节点1次。morris是利用左子树的最右孩子来进行判断是第一次来到这个节点还是第二次来到这个节点。
一个节点在第一次来到这个节点其实就是先序遍历。
morris遍历改成先序,中序和后序
先序:
public static void morrisPre(Node head){
if(head == null){
return ;
}
Node cur = head;
Node mostRight = null;
while(cur != null){
mostRight cur.left;
if(mostRight != null){
while(mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
System.out.print(cur.value+" ");
cur = cur.left;
continue;
}else{
mostRight.right = null;
}
}else{
//一个节点如果没有左子树,此时两次遍历非叶节点的时候重合,此时还是在第一次的时候就打印。
System.out.print(cur.value + " ");
}
cur = cur.right;
}
System.out.println();
}
中序:
public static void morris(Node head){
if(head == null){
return ;
}
Node cur = head;
Node mostRight = null;
while(cur != null){
mostRight = cur.left;
if(mostRight != null){
while(mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else{
mostRight.right = null;
}
}
System.out.print(cur.value + " ");
cur = cur.right;
}
System.out.println();
}
后序:只看是否存在第二次返回当前值(抛弃所有的叶节点),然后对于每个第二次返回的数,打印左子节点和右子节点,从而实现后序遍历
public static void morrisPos(Node head){
if(head == null){
return;
}
Node cur1 = head;
Node cur2 = null;
while(cur1 != null){
cur2 = cur1.left;
if(cur2 != null){
while(cur2.right != null && cur2.right != cur1){
cur2 = cur2.right;
}
if(cur2.right == null){
cur2.right = cur1;
cur1 = cur1.left;
continue;
}else{
cur2.right = null;
//整个节点整棵树右节点逆序打印
printEdge(cur1.left);
}
}
cur1 = cur1.right;
}
//单独逆序打印有边界,用逆转链表的形式更改,然后最后调整回来
printEdge(head);
System.out.println();
}
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);
}