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

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

  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。此時的調用棧如圖所示:函數

爲何要說這個呢?由於遞歸遍歷的執行過程就是這樣的,只不過是函數不停的調用自身,直到遇到遞歸出口(終止條件)。post

舉個例子,如今要用遞歸前序遍歷如下二叉樹:spa

1
    \
     2
    /
   3

它的遍歷順序爲 1-2-3,調用棧如圖所示:code

理解了遞歸調用棧的狀況,再來看看怎麼利用遞歸思想實現前序迭代遍歷:blog

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 的右子節點開始循環

看到這是否是已經發現了這個迭代遍歷的過程和遞歸遍歷的過程如出一轍?leetcode

並且用遞歸的思想來實現迭代遍歷,優勢在於好理解,之後再遇到這種問題立刻就能想起來怎麼作了。

中序遍歷

中序遍歷和前序遍歷差很少,區別在於節點出棧時,纔將節點的值推入到 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() // 逆前序遍歷順序的倒序就是後序遍歷的順序
}

參考資料

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

相關文章
相關標籤/搜索