1. 程式人生 > >理解遞迴的本質:遞迴與棧

理解遞迴的本質:遞迴與棧

轉自:https://blog.csdn.net/bobbypollo/article/details/79891556 

遞迴的基本思想 所謂遞迴,就是有去有回。 遞迴的基本思想,是把規模較大的一個問題,分解成規模較小的多個子問題去解決,而每一個子問題又可以繼續拆分成多個更小的子問題。 最重要的一點就是假設子問題已經解決了,現在要基於已經解決的子問題來解決當前問題;或者說,必須先解決子問題,再基於子問題來解決當前問題。

或者可以這麼理解:遞迴解決的是有依賴順序關係的多個問題。 我們假設一個抽象問題有兩個時間點要素:開始處理,結束處理。

那麼遞迴處理的順序就是,先開始處理的問題,最後才能結束處理。 假設如下問題的依賴關係: 【A】----依賴---->【B】----依賴---->【C】 我們的終極目的是要解決問題A, 那麼三個問題的處理順序如下: 開始處理問題A; 由於A依賴B,因此開始處理問題B; 由於B依賴C,開始處理問題C; 結束處理問題C; 結束處理問題B; 結束處理問題A。

從函式呼叫看廣義遞迴 對於軟體來說,函式的呼叫關係就是一個廣義遞迴的過程,如下, func_A() { func_B(); } func_B() { func_C(); } func_C() { ///// }

呼叫函式A; 呼叫函式B; 呼叫函式C; 函式C返回; 函式B返回; 函式A返回;

狹義遞迴函式 有一種特例,就是處理問題A/B/C的方法是一樣的,這就是產生了狹義的“遞迴函式“,即函式內又呼叫函式自身。

從上述分析看,遞迴對問題的處理順序,是遵循了先入後出(也就是先開始的問題最後結束)的規律。 先入後出?棧! 沒錯,廣義遞迴問題的處理,需要用棧來解決。經典的例子就是函式呼叫,就是依靠棧來實現的。

遞迴函式的非遞迴化 現在再來深入分析一下狹義的遞迴函式(也就是函式呼叫自身)。 我們知道遞迴函式存在的最大問題是,當遞迴次數足夠大時,會導致函式棧溢位而宕機,函式棧的大小一般是一個固定值,對於linux來說一般預設是8M。 因此,程式設計老司機會教導我們,不得用遞迴函式!但遞迴函式的程式碼實現實在是簡潔啊,不讓用?臣妾做不到啊! 那麼問題來了,所有遞迴函式都能非遞迴化嗎?答案是肯定的。 本質上講,對於同一個問題,如果必然要用廣義遞迴的方案來處理,那麼狹義遞迴函式只不過是其中的一種實現方式,如果放棄狹義遞迴函式的話,我們不得不借助一個額外的資料結構:棧。 如此看來,無論如何都要用到棧,只不過要麼讓編譯器來維護一個棧(函式棧),要麼讓程式狗來維護一個棧(資料棧)。 這兩個棧的區別如下:       函式棧 資料棧 位置 程序的stack區 程序的heap區 大小限制 小(8M?) 能分配到很大 每個棧“元素”所需空間 比較大,因為要儲存函式上下文 可以設計到很小,比如只儲存一個指標 棧開銷 大 可以做的很小 程式碼簡易程度/可維護性 簡潔易讀 相對更復雜 注:函式棧開銷是一個絕對值,但也算是一個“相對“概念,一個非量化的理性分析是,內部邏輯越簡單的函式,棧開銷的影響越大,因為函式的出入棧指令佔整個函式體指令的比重較大。 很多情況下,程式碼的易維護性是一個比效能開銷更加重要的因素,因此,只要實際應用中不會造成函式棧溢位,我個人是更建議採用遞迴函式法的。

舉例說明:二叉樹的非遞迴遍歷

非遞迴演算法的遞迴化 既然遞迴演算法可以用資料棧來進行非遞迴化,那麼藉助資料棧而實現的非遞迴演算法,理論上也可以被遞迴化。也就是說,兩者是可逆的,橋樑就是棧。 ---------------------  作者:bobbypollo  來源:CSDN  原文:https://blog.csdn.net/bobbypollo/article/details/79891556  版權宣告:本文為博主原創文章,轉載請附上博文連結!