Morris Traversal方法遍歷二叉樹(非遞歸占调,不用棧,O(1)空間)

本文主要解決一個問題移剪,如何實現(xiàn)二叉樹的前中后序遍歷究珊,有兩個要求:

  1. O(1)空間復(fù)雜度,即只能使用常數(shù)空間纵苛;
  2. 二叉樹的形狀不能被破壞(中間過程允許改變其形狀)剿涮。
    通常,實現(xiàn)二叉樹的前序(preorder)攻人、中序(inorder)取试、后序(postorder)遍歷有兩個常用的方法:一是遞歸(recursive),二是使用棧實現(xiàn)的迭代版本(stack+iterative)怀吻。這兩種方法都是O(n)的空間復(fù)雜度(遞歸本身占用stack空間或者用戶自定義的stack)瞬浓,所以不滿足要求。

Morris Traversal方法可以做到這兩點烙博,與前兩種方法的不同在于該方法只需要O(1)空間瑟蜈,而且同樣可以在O(n)時間內(nèi)完成烟逊。

要使用O(1)空間進(jìn)行遍歷,最大的難點在于铺根,遍歷到子節(jié)點的時候怎樣重新返回到父節(jié)點(假設(shè)節(jié)點中沒有指向父節(jié)點的p指針)宪躯,由于不能用棧作為輔助空間。為了解決這個問題位迂,Morris方法用到了線索二叉樹(threaded binary tree)的概念访雪。在Morris方法中不需要為每個節(jié)點額外分配指針指向其前驅(qū)(predecessor)和后繼節(jié)點(successor),只需要利用葉子節(jié)點中的左右空指針指向某種順序遍歷下的前驅(qū)節(jié)點或后繼節(jié)點就可以了掂林。

Morris只提供了中序遍歷的方法臣缀,在中序遍歷的基礎(chǔ)上稍加修改可以實現(xiàn)前序,而后續(xù)就要再費點心思了泻帮。所以先從中序開始介紹精置。

首先定義在這篇文章中使用的二叉樹節(jié)點結(jié)構(gòu),即由val锣杂,left和right組成:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

一脂倦、中序遍歷

步驟:

  1. 如果當(dāng)前節(jié)點的左孩子為空,則輸出當(dāng)前節(jié)點并將其右孩子作為當(dāng)前節(jié)點元莫。

  2. 如果當(dāng)前節(jié)點的左孩子不為空赖阻,在當(dāng)前節(jié)點的左子樹中找到當(dāng)前節(jié)點在中序遍歷下的前驅(qū)節(jié)點。

    a) 如果前驅(qū)節(jié)點的右孩子為空踱蠢,將它的右孩子設(shè)置為當(dāng)前節(jié)點火欧。當(dāng)前節(jié)點更新為當(dāng)前節(jié)點的左孩子。

    b) 如果前驅(qū)節(jié)點的右孩子為當(dāng)前節(jié)點茎截,將它的右孩子重新設(shè)為空(恢復(fù)樹的形狀)苇侵。輸出當(dāng)前節(jié)點。當(dāng)前節(jié)點更新為當(dāng)前節(jié)點的右孩子稼虎。

  3. 重復(fù)以上1衅檀、2直到當(dāng)前節(jié)點為空。

圖示:

下圖為每一步迭代的結(jié)果(從左至右霎俩,從上到下),cur代表當(dāng)前節(jié)點沉眶,深色節(jié)點表示該節(jié)點已輸出打却。

Morris Inorder Traversal

代碼:

void inorderMorrisTraversal(TreeNode *root) {
    TreeNode *cur = root, *prev = NULL;
    while (cur != NULL)
    {
        if (cur->left == NULL)          // 1.
        {
            printf("%d ", cur->val);
            cur = cur->right;
        }
        else
        {
            // find predecessor
            prev = cur->left;
            while (prev->right != NULL && prev->right != cur)
                prev = prev->right;

            if (prev->right == NULL)   // 2.a)
            {
                prev->right = cur;
                cur = cur->left;
            }
            else                       // 2.b)
            {
                prev->right = NULL;
                printf("%d ", cur->val);
                cur = cur->right;
            }
        }
    }
}

復(fù)雜度分析:

空間復(fù)雜度:O(1),因為只用了兩個輔助指針谎倔。

時間復(fù)雜度:O(n)柳击。證明時間復(fù)雜度為O(n),最大的疑惑在于尋找中序遍歷下二叉樹中所有節(jié)點的前驅(qū)節(jié)點的時間復(fù)雜度是多少片习,即以下兩行代碼:

while (prev->right != NULL && prev->right != cur)
     prev = prev->right;

直覺上捌肴,認(rèn)為它的復(fù)雜度是O(nlgn)蹬叭,因為找單個節(jié)點的前驅(qū)節(jié)點與樹的高度有關(guān)。但事實上状知,尋找所有節(jié)點的前驅(qū)節(jié)點只需要O(n)時間秽五。n個節(jié)點的二叉樹中一共有n-1條邊,整個過程中每條邊最多只走2次饥悴,一次是為了定位到某個節(jié)點坦喘,另一次是為了尋找上面某個節(jié)點的前驅(qū)節(jié)點,如下圖所示西设,其中紅色是為了定位到某個節(jié)點瓣铣,黑色線是為了找到前驅(qū)節(jié)點。所以復(fù)雜度為O(n)贷揽。

Morris Inorder Traversal

二棠笑、前序遍歷

前序遍歷與中序遍歷相似,代碼上只有一行不同禽绪,不同就在于輸出的順序腐晾。

步驟:

  1. 如果當(dāng)前節(jié)點的左孩子為空,則輸出當(dāng)前節(jié)點并將其右孩子作為當(dāng)前節(jié)點丐一。

  2. 如果當(dāng)前節(jié)點的左孩子不為空藻糖,在當(dāng)前節(jié)點的左子樹中找到當(dāng)前節(jié)點在中序遍歷下的前驅(qū)節(jié)點。

    a) 如果前驅(qū)節(jié)點的右孩子為空库车,將它的右孩子設(shè)置為當(dāng)前節(jié)點巨柒。輸出當(dāng)前節(jié)點(在這里輸出,這是與中序遍歷唯一一點不同)柠衍。當(dāng)前節(jié)點更新為當(dāng)前節(jié)點的左孩子洋满。

    b) 如果前驅(qū)節(jié)點的右孩子為當(dāng)前節(jié)點,將它的右孩子重新設(shè)為空珍坊。當(dāng)前節(jié)點更新為當(dāng)前節(jié)點的右孩子牺勾。

  3. 重復(fù)以上1泌辫、2直到當(dāng)前節(jié)點為空纫普。

圖示:

Morris Preorder Traversal

代碼

void preorderMorrisTraversal(TreeNode *root) {
    TreeNode *cur = root, *prev = NULL;
    while (cur != NULL)
    {
        if (cur->left == NULL)
        {
            printf("%d ", cur->val);
            cur = cur->right;
        }
        else
        {
            prev = cur->left;
            while (prev->right != NULL && prev->right != cur)
                prev = prev->right;

            if (prev->right == NULL)
            {
                printf("%d ", cur->val);  // the only difference with inorder-traversal
                prev->right = cur;
                cur = cur->left;
            }
            else
            {
                prev->right = NULL;
                cur = cur->right;
            }
        }
    }
}

復(fù)雜度分析:

時間復(fù)雜度與空間復(fù)雜度都與中序遍歷時的情況相同。

三悼嫉、后序遍歷

后續(xù)遍歷稍顯復(fù)雜履怯,需要建立一個臨時節(jié)點dump回还,令其左孩子是root。并且還需要一個子過程叹洲,就是倒序輸出某兩個節(jié)點之間路徑上的各個節(jié)點柠硕。

步驟:

當(dāng)前節(jié)點設(shè)置為臨時節(jié)點dump。

  1. 如果當(dāng)前節(jié)點的左孩子為空运提,則將其右孩子作為當(dāng)前節(jié)點蝗柔。

  2. 如果當(dāng)前節(jié)點的左孩子不為空闻葵,在當(dāng)前節(jié)點的左子樹中找到當(dāng)前節(jié)點在中序遍歷下的前驅(qū)節(jié)點。

    a) 如果前驅(qū)節(jié)點的右孩子為空癣丧,將它的右孩子設(shè)置為當(dāng)前節(jié)點槽畔。當(dāng)前節(jié)點更新為當(dāng)前節(jié)點的左孩子。

    b) 如果前驅(qū)節(jié)點的右孩子為當(dāng)前節(jié)點坎缭,將它的右孩子重新設(shè)為空竟痰。倒序輸出從當(dāng)前節(jié)點的左孩子到該前驅(qū)節(jié)點這條路徑上的所有節(jié)點。當(dāng)前節(jié)點更新為當(dāng)前節(jié)點的右孩子掏呼。

  3. 重復(fù)以上1坏快、2直到當(dāng)前節(jié)點為空。

圖示:

Morris Postorder Traversal

代碼:

void reverse(TreeNode *from, TreeNode *to) // reverse the tree nodes 'from' -> 'to'.
{
    if (from == to)
        return;
    TreeNode *x = from, *y = from->right, *z;
    while (true)
    {
        z = y->right;
        y->right = x;
        x = y;
        y = z;
        if (x == to)
            break;
    }
}

void printReverse(TreeNode* from, TreeNode *to) // print the reversed tree nodes 'from' -> 'to'.
{
    reverse(from, to);
    
    TreeNode *p = to;
    while (true)
    {
        printf("%d ", p->val);
        if (p == from)
            break;
        p = p->right;
    }
    
    reverse(to, from);
}

void postorderMorrisTraversal(TreeNode *root) {
    TreeNode dump(0);
    dump.left = root;
    TreeNode *cur = &dump, *prev = NULL;
    while (cur)
    {
        if (cur->left == NULL)
        {
            cur = cur->right;
        }
        else
        {
            prev = cur->left;
            while (prev->right != NULL && prev->right != cur)
                prev = prev->right;

            if (prev->right == NULL)
            {
                prev->right = cur;
                cur = cur->left;
            }
            else
            {
                printReverse(cur->left, prev);  // call print
                prev->right = NULL;
                cur = cur->right;
            }
        }
    }
}

復(fù)雜度分析:

空間復(fù)雜度同樣是O(1)憎夷;時間復(fù)雜度也是O(n)莽鸿,倒序輸出過程只不過是加大了常數(shù)系數(shù)。

參考:

http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/
http://www.geeksforgeeks.org/morris-traversal-for-preorder/
http://stackoverflow.com/questions/6478063/how-is-the-complexity-of-morris-traversal-on
http://blog.csdn.net/wdq347/article/details/8853371
Data Structures and Algorithms in C++ by Adam Drozdek

本文轉(zhuǎn)自Morris Traversal方法遍歷二叉樹(非遞歸拾给,不用棧祥得,O(1)空間)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蒋得,隨后出現(xiàn)的幾起案子级及,更是在濱河造成了極大的恐慌,老刑警劉巖额衙,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饮焦,死亡現(xiàn)場離奇詭異,居然都是意外死亡窍侧,警方通過查閱死者的電腦和手機(jī)县踢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伟件,“玉大人硼啤,你說我怎么就攤上這事「耍” “怎么了谴返?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長其骄。 經(jīng)常有香客問我亏镰,道長,這世上最難降的妖魔是什么拯爽? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮钧忽,結(jié)果婚禮上毯炮,老公的妹妹穿的比我還像新娘逼肯。我一直安慰自己,他們只是感情好桃煎,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布篮幢。 她就那樣靜靜地躺著,像睡著了一般为迈。 火紅的嫁衣襯著肌膚如雪三椿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天葫辐,我揣著相機(jī)與錄音搜锰,去河邊找鬼。 笑死耿战,一個胖子當(dāng)著我的面吹牛蛋叼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剂陡,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼狈涮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸭栖?” 一聲冷哼從身側(cè)響起歌馍,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晕鹊,沒想到半個月后松却,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡捏题,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年玻褪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片公荧。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡带射,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出循狰,到底是詐尸還是另有隱情窟社,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布绪钥,位于F島的核電站灿里,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏程腹。R本人自食惡果不足惜匣吊,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧色鸳,春花似錦社痛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吏砂,卻和暖如春撵儿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狐血。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工淀歇, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人氛雪。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓房匆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親报亩。 傳聞我的和親對象是個殘疾皇子浴鸿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容