先複習一下前、中、後遍歷的順序:node
用遞歸來寫二叉樹遍歷是很是簡單的,例如前序遍歷的代碼以下:算法
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
stack == [1,2,4]
看到這是否是已經發現了這個迭代遍歷的過程和遞歸遍歷的過程如出一轍?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() // 逆前序遍歷順序的倒序就是後序遍歷的順序 }