递归求二叉树的深度_优雅地用堆栈替代递归实现二叉树的深度优先搜索
本文語言類型:JavaScript
有一個理論是“所有的遞歸都可以用堆棧實現”,道理大家都懂,實現起來怎么樣呢?
用js的前端開發者或許都不關心算法,本文嘗試用前端們熟悉的編碼形式,讓前端能更容易理解。我就從最簡單的二叉樹的深度優先搜索(DFS)入手。
數據結構定義
function Node(name) {this.name = name;this.left = null;this.right = null;this.setLeft = function(left) {this.left = left;return this;}this.setRight = function(right) {this.right = right;return this;}this.visit = function() {console.log(`visit ${this.name}`);} }上面是一個樹節點對象的構造方法。每個對象的left和right分別指向自己的左右子節點,還有一個visit()方法,調用它就表示遍歷到這個節點了。
構造一棵二叉樹
根據上面的方法,我們就可以構造出一棵簡單的二叉樹了:
let a = new Node('a') let b = new Node('b') let c = new Node('c') let d = new Node('d') let e = new Node('e') let f = new Node('f') let g = new Node('g') let h = new Node('h')a.setLeft(b).setRight(f) b.setLeft(c).setRight(d) d.setLeft(e) f.setLeft(g).setRight(h)遞歸實現法
遞歸的實現就很簡單了,這里不用解釋:
//recursive function dfs1(node) {if(!node) return;node.visit()dfs1(node.left)dfs1(node.right) }dfs1(a)運行結果:
visit a visit b visit c visit d visit e visit f visit g visit h轉成堆棧模式
遞歸函數實際上在編譯器內部維護了一個隱藏的工作棧。遞歸發生一次,就進行一次進棧、遞歸結束一次,就進行一次彈棧。當這個棧變成空的時候,也是整個遞歸函數結束的時候。
以上遞歸函數dfs1的特征是:
- 1.所有的操作都在棧頂的這個節點上進行,首先訪問這個節點,此時是這個節點第一次出現在棧頂;
- 2.如果當前節點有左子節點,就壓入它的左子節點入棧,當左子節點處理完畢彈出時,當前節點又回到棧頂;
- 3.當前節點第二次回到棧頂時,就要考慮右子節點了,壓入右子節點入棧,當左子節點處理完畢彈出時,當前節點第三次回到棧頂;
當前節點第三次回到棧頂時,表示本節點處理完畢,可以彈出了,本節點使命結束,轉而處理它的父節點。
遞歸轉非遞歸的難點是判斷何時彈棧。經過以上分析,無非是以上三個步驟都處理完畢時——也就是節點第三次出現在棧頂時、也就是dfs1中的三行語句都執行完畢時——彈棧。
那么,可以設計一個數據結構:{ node, rest: 3 },模擬dfs1的作用域保存在堆棧中,每當這個作用域出現在棧頂一次,rest自減1,當rest為0時,這個作用域就可以彈出了。
//use stacks function dfs2(node) {if(!node) return;let stack = []stack.push(__makeScope(node))while(true) {let Scope = stack[stack.length - 1]let current = Scope.nodeif(Scope.rest === 3) {Scope.rest--current.visit()} else if(Scope.rest === 2) {Scope.rest--if(current.left) {stack.push(__makeScope(current.left))}} else if(Scope.rest === 1) {Scope.rest--if(current.right) {stack.push(__makeScope(current.right))}} else/* if(Scope.rest === 0)*/ {stack.pop()if(stack.length === 0) break;}}function __makeScope(node) {return { node, rest: 3 }} }dfs2(a)性能分析
當樹節點比較少的時候,兩個函數性能差別不大,但是當節點數達到10萬數量級時(老舊瀏覽器會更低),dfs1直接"stack overflow",dfs2依然堅挺。從這里或許也能看出遞歸的弱點。
引申
這里先考慮了二叉樹,每個節點的處理次數為3,那么n叉樹的情況下,每個節點處理次數就是n+1,以上程序還是很方便改寫的,無非就是子樹用數組來存儲。這個以后有時間再寫。
本文為原創,轉載請注明出處
總結
以上是生活随笔為你收集整理的递归求二叉树的深度_优雅地用堆栈替代递归实现二叉树的深度优先搜索的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ROG 新款幻 16 翻转版 / 星空版
- 下一篇: 请在贵网站的根目录下部署一个文件_使用