Morris神級遍歷二叉樹,時間複雜度為O(1)
Morris演算法介紹
Morris演算法在遍歷的時候避免使用了棧結構,而是讓下層到上層有指標,具體是通過底層節點指向NULL的空閒指標返回上層的某個節點,從而完成下層到上層的移動。我們知道二叉樹有很多空閒的指標,比如某個人節點沒有右孩子,我們稱這種情況為空閒狀態,Morris演算法的遍歷就是利用了這些 空閒的指標!
Morris演算法演算法的規則:
當我拿到一個節點(node)的時候看它有沒有左子樹,沒有的話向右指標方向移動
有的話找到左子樹的最右節點,如果這個最右節點的右指標為空則讓它指向node,然後node向左指標移動
,如果這個最右節點已經指向node,則讓它指向空則讓node向右指標移動
我們就拿一個普通的二叉樹來說吧:
描述:我們從根節點開始,node節點就為1,1有左子樹,找到1左子樹的最右節點為5,5的右指標為空,讓5的右指標指向node即1,然後node向左指標移動,變為2,2也有左子樹,左子樹的最右節點為4,4的右指標為空則指向2,node向左移動變為4,node為4沒有左子樹,所以node向右指標方向移動回到2(這是第二次來到2),2有左子樹,並且它左子樹的最右節點已經指向了node(即2),則讓這個最右節點指向NULL,node向右指標移動來到5,5沒有左子樹,向右指標移動來到1,1有左子樹,切左子樹的最右節點指向了node(即自己),讓最右節點指向NULL,node向右指標移動來到3,3有子樹,左子樹的最右節點右指標為空所以讓他指向node(即3),node向左指標移動來到7,7沒有左子樹,向右指標移動回到3(第二次來到3),3有左子樹切左子樹的最右節點指向自己,則讓左子樹的最右節點指向NULL,node往右指標走,來到8,此樹遍歷完畢。
我們可以看到節點2,1,3在node走的過程中會兩次來到它,而其他節點node只會來到一次!
歸納為:如果一個節點有左子樹node就會訪問它兩次,如果沒有左子樹,node只會訪問它一次。
這樣我們就可以開始做前、中序遍歷了:
前序遍歷:
一個節點沒有左子樹直接列印當前節點
如果有的話(第一次來到此節點的時候就去列印),即再往左子樹節點走之前列印
code
struct Node
{
int value;
Node* left;
Node* right;
Node(int data)
{
value = data;
}
};
//Morris前序遍歷
void MorrisPre(Node* root)
{
if(root == NULL)
return;
Node* cur1 = root;
Node* cur2 = NULL;
while(cur1 != NULL)
{
cur2 = cur1->left;
if(cur2!=NULL)
{
while(cur2->right!=NULL && cur2->right!=cur1)
{
cur = cur->right;
}
if(cur2->right == NULL)
{
cur2->right = cur1;
cout<<cur1->value<<" ";//在node走向左子樹之前列印
cur1 = cur1->left;
continue;
}else{
cur2->right = NULL;//如果cur2->right指向node,則置空
}
}else{//cur2為空,直接列印當前節點 cur1往右指標走
cout<<cur1->value<<" ";
}
cur1 = cur->right;
}
cout<<endl;
}
中序遍歷
在沒有左子樹的時候直接往右子樹走之前列印
有左子樹先去遍歷左子樹然後回來當前節點列印,之後再去右子樹(都是往右指標走之前列印他)
code
//morris中序遍歷
void MorrisIn(Node* root)
{
if(root == NULL)
return;
Node* cur1 = root;
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;
}
}
cout<<cur1->value<<" ";
cur1 = cur1->right;
}
cout<<endl;
}
後序遍歷
後序遍歷就有點麻煩了,我們仔細觀察前面的前中序,node最多可以訪問一個節點兩次,前序是第一次訪問就列印當前節點,中序是第二次訪問列印當前節點,那麼後序需要第三次訪問的時候列印當前節點,但是一個節點最多被訪問兩次,那怎麼辦呢?
下來我們引入一個方法:
圖中紅線畫出來的稱為這棵二叉樹的所有右邊界,所有的右邊界節點加起來就是二叉樹的節點個數N,每個節點最多可能被遍歷2次,遍歷整體二叉樹的代價就是2N
後序的方法就是:
第二次來到到此節點,逆序列印它的右邊界就是後序,整個走完之後(遍歷到二叉樹最右節點)後單獨逆序列印整棵樹的右邊界。
那怎麼逆序列印呢?類似於單鏈表的逆置,更改右節點的指標指向即可,在列印完在更改回去,(如1->right = 3,3->right = 8,可以設定為 8->prev = 3,3->pre = 1)
code
/morris後序遍歷
void PrintEdge(Node* from);
void MorrisPos(Node* root)
{
if(root == NULL)
{
return;
}
Node* cur1 = root;
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(cur->left);
}
}
cur1 = cur1->right;
}
PrintEdge(root);
cout<<endl;
}
void ReverseEdge(Node* from);
void PrintEdge(Node* root)
{
Node* tail = ReverseEdge(root);
Node* cur = tail;
while(cur != NULL)
{
cout<<cur->value<<" ";
cur = cur->right;
}
ReverseEdge(tail);
}
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;
}