1. 程式人生 > >Morris神級遍歷二叉樹,時間複雜度為O(1)

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 = NULLwhile(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;
}