對於遞歸的傲慢與偏見

最近刷leetcode 79題 Word Search需要用到DFS算法,由於是刷leetcode,心想不能用遞歸,影響效率,於是利用stack完成。之後又利用遞歸(使用cache)實現了一次,結果竟然是遞歸的算法比非遞歸更快

「低效」的遞歸

對於遞歸,通常會有效率低下的映像,一般是因爲2點:

  • 重複計算
  • 函數調用開銷

對於重複計算,可以緩存計算結果來解決。

對於函數調用開銷,可以利用「尾遞歸」來解決,不過目前的v8引擎並沒有實現對尾遞歸的優化,所以最開始我以爲遞歸沒有理由比非遞歸更快。

遞歸與堆棧

非遞歸的DFS算法使用一個「堆棧」來實現。而同樣,函數調用也是利用「棧」來完成。

首先,Javascipt並沒有原生的堆棧這個數據結構,通常是利用數組來實現,效率上應該會有所損失。

其次,系統堆棧快於手動堆棧是沒有疑問的,而且系統堆棧使用的是寄存器,比內存要快很多。

最後,函數調用會有額外開銷這個是沒法避免的,但是當函數變量不多,遞歸層級不深的時候,這個開銷帶來的效率損失不能抵消系統堆棧帶來的效率提升。

綜合來看,在不爆棧的情況下,大部分Javascript代碼裏使用了緩存的遞歸在算法效率上高於非遞歸算法,並且遞歸算法的表現力是完全高於非遞歸的。很多時候,出於臆斷進行的所謂優化,完全是負優化

關於遞歸的隨想

之前在看SICP的時候,發現函數式編程沒有循環,非函數式語言的循環操作都是利用「遞歸」的形式來完成的。而且所有的遞歸,都可以改成迭代的形式,避免了遞歸重複計算的缺點,也無需使用緩存來加速遞歸的計算,省下了緩存的開銷,所以有句話叫做“所有循環都是尾遞歸”。

總結

  • 慣性思維不可取,實踐檢驗真理
  • 遞歸 !== 慢
  • 以後圖的遍歷、樹的遍歷、巴拉巴拉其它情況,直接寫遞歸,誰懟我說遞歸效率低,就讓他來solo。(莫名的開心咋回事兒啊?)
  • 以上關於爲什麼遞歸快的推理全是推斷,但是DFS非遞歸慢於遞歸是事實(Javascript中), 跪求大神給出準確解讀。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章