1. 程式人生 > 實用技巧 >用遞迴的思想實現二叉樹前、中、後序迭代遍歷

用遞迴的思想實現二叉樹前、中、後序迭代遍歷

先複習一下前、中、後遍歷的順序:

  1. 前序遍歷順序:中-左-右
  2. 中序遍歷順序:左-中-右
  3. 後序遍歷順序:左-右-中

用遞迴來寫二叉樹遍歷是非常簡單的,例如前序遍歷的程式碼如下:

const result = []
function preorderTraversal(node) {
    if (!node) return null
    result.push(node.val)
    preorderTraversal(node.left)
    preorderTraversal(node.right)
}

preorderTraversal(root)

我們都知道,在呼叫函式時,系統會在棧中為每個函式維護相應的變數(引數、區域性變數、返回地址等等)。

例如有 a,b,c 三個函式,先呼叫 a,a 又呼叫 b,b 最後呼叫 c。此時的呼叫棧如圖所示:

為什麼要說這個呢?因為遞迴遍歷的執行過程就是這樣的,只不過是函式不停的呼叫自身,直到遇到遞迴出口(終止條件)。

舉個例子,現在要用遞迴前序遍歷以下二叉樹:

   1
    \
     2
    /
   3

它的遍歷順序為 1-2-3,呼叫棧如圖所示:

理解了遞迴呼叫棧的情況,再來看看怎麼利用遞迴思想實現前序迭代遍歷:

function preorderTraversal(root) {
    const result = []
    // 用一個數組 stack 模擬呼叫棧
    const stack = []
    let node = root
    while (stack.length || node) {
        // 遞迴遍歷節點的左子樹,直到空為止
        while (node) {
            result.push(node.val)
            stack.push(node)
            node = node.left
        }
        // 跳出迴圈時 node 為空,由於前序遍歷的特性
        // 當前 node 節點的上一個節點必定是它的父親節點
        // 前序遍歷是中-左-右,現在左子樹已經到頭了,該遍歷父節點的右子樹了
        // 所以要彈出父節點,從它的右子樹開始新一輪迴圈
        node = stack.pop()
        node = node.right
    }

    return result
}

再看一個具體的示例,用迭代遍歷跑一遍:

            1
          /    \
         2      3
        /  \   /  \
       4    5 6    7
  1. 初始節點 node 為 1
  2. while 遍歷完它的左子節點
  3. 當前 stack == [1,2,4]
  4. 初始節點已經遍歷完它下面的最後一個左子節點了,即節點 4 的左子節點,所以現在要開始遍歷 4 的右子節點
  5. 彈出節點 4 並從它的右子節點開始新的迴圈
  6. 由於節點 4 的右子節點為空,所以不會進入 while 迴圈,然後彈出節點 4 的父節點 2
  7. 再從節點 2 的右子節點開始迴圈

看到這是不是已經發現了這個迭代遍歷的過程和遞迴遍歷的過程一模一樣?

而且用遞迴的思想來實現迭代遍歷,優點在於好理解,以後再遇到這種問題馬上就能想起來怎麼做了。

中序遍歷

中序遍歷和前序遍歷差不多,區別在於節點出棧時,才將節點的值推入到 result 中。

function inorderTraversal(root) {
    const result = []
    const stack = []
    let node = root
    while (stack.length || node) {
        while (node) {
            stack.push(node)
            node = node.left
        }
        
        node = stack.pop()
        result.push(node.val)
        node = node.right
    }
    
    return result
}

後序遍歷

前序遍歷過程為中-左-右,逆前序遍歷過程就是將遍歷左右子樹的順序調換一下,即中-右-左。

然後再看一下後序遍歷的過程左-右-中,可以看出逆前序遍歷順序的倒序就是後序遍歷的順序。

利用這一特點寫出的後序遍歷程式碼如下:

function postorderTraversal(root) {
    const result = []
    const stack = []
    let node = root
    while (stack.length || node) {
        while (node) {
            result.push(node.val)
            stack.push(node)
            node = node.right // 原來是 node.left,這裡換成 node.right
        }

        node = stack.pop()
        node = node.left // 原來是 node.right,這裡換成 node.left
    }
    
    return result.reverse() // 逆前序遍歷順序的倒序就是後序遍歷的順序
}

參考資料

他來了,他帶著他的三兄弟來了,前中後序遍歷統一的演算法