【思維風暴】算法迭代和遞歸的理解

遞歸與迭代

遞歸與迭代都是基於控制結構:迭代用重複結構,而遞歸用選擇結構。遞歸與迭代都涉及重複:迭代顯式使用重複結構,而遞歸通過重複函數調用實現重複。遞歸與迭代都涉及終止測試:迭代在循環條件失敗時終止,遞歸在遇到基本情況時終止。使用計數器控制重複的迭代和遞歸都逐漸到達終止點:迭代一直修改計數器,直到計數器值使循環條件失敗;遞歸不斷產生最初問題的簡化副本,直到達到基本情況。迭代和遞歸過程都可以無限進行:如果循環條件測試永遠不變成false,則迭代發生無限循環;如果遞歸永遠無法回推到基本情況,則發生無窮遞歸。

遞歸消耗內存的缺點

遞歸有許多缺點,它重複調用機制,因此重複函數調用的開銷很大,將佔用很長的處理器時間和大量的內存空間。每次遞歸調用都要生成函數的另一個副本(實際上只是函數變量的另一個副本).從而消耗大量內存空間。迭代通常發生在函數內,因此沒有重複調用函數和多餘內存賦值的開銷。那麼,爲什麼選擇遞歸呢?

爲什麼要有迭代

遞歸算法表示許多問題的求解方法時算法思想非常簡潔。但是遞歸算法不僅時間效率非常差,而且由於遞歸算法是不斷的函數調用和函數返回過程,因此其實際的計算機運行時間通常遠大於循環方式算法的計算機運行時間,甚至在有限的時間內無法求解。這就存在一個把遞歸算法化爲非遞歸算法的問題。

需要用迭代消解遞歸的情況

遞歸算法特別適合於所研究的問題或所處理的數據本身是遞歸定義的情況。然而,並不意味着這種遞歸定義保證遞歸算法是解決該問題的最好方法。事實上,主要是因爲拿那種不合適的例子來解釋遞歸算法概念,從而造成了對程序設計中使用遞歸的普遍懷疑和否定態度,並把遞歸同低效等同起來。而且在遞歸算法中,往往會因爲追求代碼短或者在求解問題時一味追求規律性,多用了無用的壓棧和出棧的操作。比如用循環消解的尾遞歸,是多了無用的壓棧和出棧才使速度受損的;斐波那契數列計算的遞歸改循環迭代所帶來的速度大幅提升,是因爲改掉了重複計算的毛病。假使一個遞歸過程中本身包含了大量冗餘的操作,並且這個過程又可以用迭代來達到相同的效果。這時,我們就一般用迭代來消解遞歸。也就是說尾遞歸算法和單向遞歸算法可用迭代算法來代替。

不需要消解的遞歸

那種盲目的消解遞歸,不惜一切代價躲避遞歸,認爲“遞歸的速度慢,爲了提高速度,必須用棧或者其他的方法來消解”的說法是很片面的。如果一個遞歸過程用非遞歸的方法實現後,速度提高了,那只是因爲遞歸做了一些無用功。假使一個遞歸過程必須要用棧才能消解,那麼完全模擬後的結果根本就不會對速度有任何提升,只會減慢;如果你改完後速度提升了,那隻證明你的遞歸函數寫的有問題,如多了許多重複操作——打開關閉文件、連接斷開數據庫,而這些完全可以放到遞歸外面。可以在本質上是非遞歸的機器上實現遞歸過程這一事實本身就證明:爲着實際目的,每一個遞歸程序都可以翻譯成純粹迭代的形式,但這包含着對遞歸棧的顯式處理,而這些運算常常模糊了程序的本質,以致使它非常難以理解。
因此,是遞歸的而不是迭代的算法應當表述成遞歸過程。如漢諾塔問題等。漢諾塔問題的遞歸算法中有兩處遞歸調用,並且其中一處遞歸調用語句後還有其他語句,因此該遞歸算法不是尾遞歸或單向遞歸。要把這樣的遞歸算法轉化爲非遞歸算法,並沒有提高程序運行的速度,反而會使程序變得複雜難懂,這是不可取的。也就是說,很多遞歸算法並不容易改寫成迭代程序:它們本質上是遞歸的,沒有簡單的迭代形式。這樣的遞歸算法不宜轉化爲非遞歸算法。

結束語

說到底,在我們選擇算法時應該全面分析算法的可行性、效率、代碼優化。在綜合了算法的各個因素後,選擇合適的算法來編寫程序,這樣的程序纔會達到優化的效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章