morris遍历解决问题:把二叉树遍历空间复杂度降为O(1)
之前学习的二叉树遍历,不管何种方式,时间复杂度如何,空间复杂度都为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向右移动
左边是需要遍历的树,右边是这个方法遍历二叉树,curr节点所经过的位置。
如果一个节点有左子树,Morris遍历会到达它两次;如果没有左子树,只遍历一次。当第二次到达某一个节点时,它的左子树上的节点都已经遍历完了。所以总的时间复杂度去除常数,依旧为O(N)。
Morris遍历理解:
Morris遍历极大地类似递归版的二叉树遍历,只是递归版是到一个节点,先去左,回来,再去右,回来,到达一个节点三次。
而Morris遍历只到两次,它是用了左子树最右节点的右孩子是否为空或者指向自己来判断,对某个节点是第一次到达还是第二次到达。
Morris遍历也可以针对非完全二叉树
【代码】
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遍历改为先序、中序、后序遍历
先序:第一次到达节点就打印
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();
}
后序:只关注每个有第二次返回的节点
- 每当有一个第二次到达的节点,就逆序打印其左子树的右边界;
- 整个树的二次返回节点的操作1打印完之后,打印整棵树的右边界。
这里证明了一下时间复杂度O(N)。。。但是没太理解
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);
}