【數據結構與算法】二叉樹的 Morris 遍歷(前序、中序、后序)


前置說明

不了解二叉樹非遞歸遍歷的可以看我之前的文章【數據結構與算法】二叉樹模板及例題

Morris 遍歷

概述

Morris 遍歷是一種遍歷二叉樹的方式,並且時間復雜度O(N),額外空間復雜度O(1) 。通過利用原樹中大量空閑指針的方式,達到節省空間的目的

分析

設一棵二叉樹有 n 個節點,則所有節點的指針域總和為 2 * n ,所有節點的非空指針域總和為 n - 1(非根節點被一個指針指向,根節點不被指針指向),所有節點的空指針域總和為 2n - (n - 1) = n + 1。

可以看到有大量的空指針域沒有用到,在可以改變原二叉樹結構的前提下,我們可以通過合理利用節點的空指針域,不開辟額外空間進行二叉樹的非遞歸遍歷。

❓ 那么先序、中序、后序遍歷的節點訪問順序是如何確定的呢

如上圖,根據紫色箭頭順序訪問,第一次訪問到的節點組成的集合就是先序遍歷的結果。類似的,第二次訪問到的節點組成的集合就是中序遍歷的結果;第三次訪問到的節點組成的集合就是后序遍歷的結果。

通過設置節點訪問不同次數的操作就可以實現三種遍歷。

❓ Morris 遍歷的實質:建立一種機制,對於沒有左子樹的節點只到達一次,對於有左子樹的節點會到達兩次

🔔 Morris 遍歷的原則

假設來到當前節點 cur,開始時 cur 來到頭節點位置

  • 如果 cur 沒有左孩子,cur向右移動(cur = cur.right)

  • 如果 cur 有左孩子,找到左子樹上最右的節點 mostRight

    • a.如果 mostRight 的右指針指向空,讓其指向 cur, 然后 cur 向左移動(cur = cur.left)

    • b.如果 mostRight 的右指針指向 cur,讓其指向 null, 然后 cur 向右移動(cur = cur.right)

  • cur 為空時遍歷停止

🌰 舉個例子:


已訪問節點次序:1

1️⃣ 首先 cur 來到頭結點 1,按照 morris 原則的第二條第一點,它存在左孩子,cur 左子樹上最右的節點為 5,它的 right 指針指向空,所以讓其指向 1,cur 向左移動到2。
已訪問節點次序:1 2

2️⃣ 2 有左孩子,且它左子樹最右的節點 4 指向空,按照 morris 原則的第二條第一點,讓 4 的 right 指針指向 2,cur 向左移動到 4
已訪問節點次序:1 2 4

3️⃣ 4 不存在左孩子,按照 morris 原則的第一條,cur 向右移動,在第二步中,4 的 right 指針已經指向了 2,所以 cur 會回到 2
已訪問節點次序:1 2 4 2

4️⃣ 重新回到 2,有左孩子,它左子樹最右的節點為 4,但是在第二步中,4 的 right 指針已經指向了 2,不為空。所以按照 morris 原則的第二條第二點,cur 向右移動到 5,同時 4 的 right 指針重新指向空
已訪問節點次序:1 2 4 2 5

5️⃣ 5 不存在左孩子,按照 morris 原則的第一條,cur 向右移動,在第一步中,5 的 right 指針已經指向了 1,所以 cur 會回到 1
已訪問節點次序:1 2 4 2 5 1

6️⃣ cur 回到 1,回到頭結點,左子樹遍歷完成,1 有左孩子,左子樹上最右的節點為 5,它的 right 指針指向 1,按照 morris 原則的第二條第二點,cur 向右移動到 3,同時 5 的 right 指針重新指回空
已訪問節點次序:1 2 4 2 5 1 3

7️⃣ 3 有左孩子,且它左子樹最右的節點 6 指向空,按照 morris 原則的第二條第一點,讓 6 的 right 指針指向 3,cur 向左移動到 6
已訪問節點次序:1 2 4 2 5 1 3 6

8️⃣ 6 不存在左孩子,按照 morris 原則的第一條,cur 向右移動,在第二步中,6 的 right 指針已經指向了 3,所以 cur 會回到 3
已訪問節點次序:1 2 4 2 5 1 3 6 3

9️⃣ 重新回到 3,有左孩子,它左子樹最右的節點為 6,但是在第二步中,6 的 right 指針已經指向了 3,不為空。所以按照 morris 原則的第二條第二點,cur 向右移動到 7,同時 6 的 right 指針重新指向空
已訪問節點次序:1 2 4 2 5 1 3 6 3 7

1️⃣0️⃣ cur 沒有左孩子,向右移動到 null,遍歷停止
最終已訪問節點次序:1 2 4 2 5 1 3 6 3 7

可以發現,節點1 2 3(有左子樹)被 cur 訪問了兩次,而節點4 5 6 7(沒有左子樹)被 cur 訪問了一次。正好驗證了之前提到的 Morris 遍歷的機制:對於沒有左子樹的節點只到達一次,對於有左子樹的節點會到達兩次。

以上就是 Morris 遍歷的全過程了,通過在遍歷過程中適當的位置,即每個節點訪問特定次數后設置操作,可以實現三種遍歷

前序遍歷

  • 對於沒有左子樹的節點只到達一次,直接打印

  • 對於有左子樹的節點會到達兩次,則在第一次到達時打印

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    if (root != null) {
        TreeNode cur = root;
        TreeNode mostRight = null;
        while (cur != null) {
            // cur表示當前節點,mostRight表示cur的左孩子的最右節點
            mostRight = cur.left;
            if (mostRight != null) {
                // cur有左孩子,找到cur左子樹最右節點
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                // mostRight的右孩子指向空,讓其指向cur,cur向左移動
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    ans.add(cur.val);  // 此時第一次訪問節點,記錄答案
                    cur = cur.left;
                    continue;          // 直接進入下一次循環(容易忘)
                } else {
                    // mostRight的右孩子指向cur,讓其指向空,cur向右移動
                    mostRight.right = null;
                    cur = cur.right;
                }
            } else {
                /// 沒有左子樹的節點只到達一次直接記錄答案, cur 向右移動
                ans.add(cur.val);
                cur = cur.right;
            }
        }
    }
    return ans;
}

中序遍歷

  • 對於沒有左子樹的節點只到達一次,直接打印

  • 對於有左子樹的節點會到達兩次,第二次到達時打印

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    if (root != null) {
        TreeNode cur = root;
        TreeNode mostRight = null;
        while (cur != null) {
            // cur表示當前節點,mostRight表示cur的左孩子的最右節點
            mostRight = cur.left;
            if (mostRight != null) {
                // cur有左孩子,找到cur左子樹最右節點
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                // mostRight的右孩子指向空,讓其指向cur,cur向左移動
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;            // 直接進入下一次循環(容易忘)
                } else {
                    ans.add(cur.val);        // 第二次到達,記錄答案 
                    mostRight.right = null;  // mostRight的右孩子指向cur,讓其指向空,cur向右移動     
                    cur = cur.right;
                }
            } else {
                ans.add(cur.val);   // 沒有左子樹的節點只到達一次直接記錄答案, cur 向右移動
                cur = cur.right;
            }
        }
    }
    return ans;
}

后序遍歷

  • 第二次訪問節點時逆序打印該節點左樹的右邊界

  • 最后單獨逆序打印整棵樹的右邊界

public List<Integer> postorderTraversal(TreeNode root) {
     if (root != null) {
        TreeNode cur = root;
        TreeNode 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;
                    printEdge(cur.left);     // 第二次訪問時逆序打印該節點左樹的右邊界
                    cur = cur.right;
                }
            } else {
                cur = cur.right;
            }
        }
        printEdge(root);     // 最后單獨打印整棵樹的右邊界
    }
    return ans;
}

public void printEdge(TreeNode node) {   // 逆序打印:反轉鏈表打印后再反轉回原樣
    TreeNode tail = reverseEdge(node);
    TreeNode cur = tail;
    while (cur != null) {
        ans.add(cur.val);
        cur = cur.right;
    }
    reverseEdge(tail);
}

public TreeNode reverseEdge(TreeNode node) {  // 鏈表反轉
    TreeNode pre = null;
    TreeNode next = null;
    while (node != null) {
        next = node.right;
        node.right = pre;
        pre = node;
        node = next;
    }
    return pre;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM