数据结构与算法--再谈递归与循环(斐波那契数列)
再談遞歸與循環(huán)
- 在某些算法中,可能需要重復(fù)計算相同的問題,通常我們可以選擇用遞歸或者循環(huán)兩種方法。遞歸是一個函數(shù)內(nèi)部的調(diào)用這個函數(shù)自身。循環(huán)則是通過設(shè)置計算的初始值以及終止條件,在一個范圍內(nèi)重復(fù)運(yùn)算。比如,我們求累加1+2+3+…n,這個既可以用循環(huán)也可以用遞歸
-
如上案例實(shí)現(xiàn),遞歸代碼簡潔,循環(huán)代碼比較多,同樣的在之前的文章二叉樹實(shí)現(xiàn)原理在樹的前序,中序,后序遍歷的代碼中,遞歸實(shí)現(xiàn)也明顯比循環(huán)實(shí)現(xiàn)要簡潔的多,所以我們盡量用遞歸來表達(dá)我們的算法思想。
-
遞歸的缺點(diǎn):
- 遞歸優(yōu)點(diǎn)顯著,由于是函數(shù)自身的調(diào)用,而函數(shù)調(diào)用有時間與空間的消耗:每一次調(diào)用都需要內(nèi)存棧分配空間保存參數(shù)返回地址以及臨時變量,而往棧里壓入與彈出數(shù)據(jù)也需要時間,那就自然遞歸實(shí)現(xiàn)的效率比同等條件下循環(huán)要低下了
- 另外遞歸中可能有很多計算是重復(fù)的,這個比較致命,對性能帶來很大影響。
- 除了效率,遞歸還有可能出現(xiàn)調(diào)用棧溢出問題。因?yàn)槊總€進(jìn)程棧空間有限,當(dāng)遞歸次數(shù)太多,超出棧容量,導(dǎo)致棧溢出。
案例分析:斐波那契數(shù)列
- 題目:寫一個函數(shù),輸入n,求斐波那契(Fibonacci)數(shù)列的第n項(xiàng)。斐波那契數(shù)列的定義如下:
斐波那契數(shù)列遞歸實(shí)現(xiàn)
- 記得譚浩強(qiáng)版本的C語言中講解遞歸的時候就是用的斐波那契數(shù)列的案例,所以對這個問題非常的熟悉。看到之后自然就能提供如下代碼:
- 教科書上只是為了講解遞歸,這個案例正好比較合適,并不表示是最優(yōu)解,其實(shí)以上方法是一種存在嚴(yán)重效率問題的解法,如下分析:
- 我們求解f(10) 需要求解f(9),f(8),繼而需要先求解f(8),f(7),…我們可以用樹形結(jié)構(gòu)來說明這種依賴求解關(guān)系:
- 如上圖中分解,樹中很多節(jié)點(diǎn)是重復(fù)的,而且重復(fù)的節(jié)點(diǎn)數(shù)會隨著n的增大指數(shù)級別的增大,我們可以用以上算法測試第100項(xiàng)的值,慢的你懷疑人生。
我認(rèn)為的最優(yōu)解:動態(tài)規(guī)劃(循環(huán)實(shí)現(xiàn))
- 改進(jìn)方法并不復(fù)雜,上述代碼中是因?yàn)榇罅恐貜?fù)計算,我們只要避免重復(fù)計算就行了。比如我們將已經(jīng)計算好的數(shù)列保存到一個臨時變量,下次計算直接查找前一次計算的結(jié)果,就無須重復(fù)計算之前的值。
- 例如我們從下往上算,根據(jù)f(0) 和f(1) 求f(2), 繼續(xù)f(1),f(2) 求f(3),依次類推得出第n項(xiàng)。很容易得出解。而且時間復(fù)雜度控制在O(n)
- 如下代碼實(shí)現(xiàn):
時間復(fù)雜度更優(yōu)O(logn)但是復(fù)雜度過于高的解法
- 一般以上解法是最優(yōu)解,但是如果在追求時間復(fù)雜度最優(yōu)的算法場景下,我們有更快的O(logn)的算法。由于這種算法需要一個比較生僻的數(shù)學(xué)公式(離散數(shù)學(xué)沒學(xué)好的代價),因此很少有人會去寫這種算法,此處我們只介紹該算法,不遞推數(shù)學(xué)公式(不會),如下:
- 先介紹數(shù)學(xué)公式如下:
[f(n)f(n?1)f(n?1)f(n?2)]=[1110]n?1\left[ \begin{matrix} f(n) &f(n-1) \\ f(n-1) & f(n-2) \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] ^{n-1} [f(n)f(n?1)?f(n?1)f(n?2)?]=[11?10?]n?1
-
如上數(shù)學(xué)公式可以用數(shù)學(xué)歸納法證明,有了這個公式我們只需要求如下矩陣的值,既可以的到f(n)的值,
[1110]n?1\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] ^{n-1} [11?10?]n?1 -
那么我們只需要求,基礎(chǔ)矩陣的乘方問題。如果只是簡單的從0~n循環(huán),n次方需要n次運(yùn)算,那么時間復(fù)雜度還是O(n),并不比之前的方法快,但是我們可以考慮乘方的如下性質(zhì)
[1110]\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] [11?10?] -
情況一
an=an/2?an/2,n為偶數(shù)a^n = a^{n/2}* a^{n/2} , n為偶數(shù) an=an/2?an/2,n為偶數(shù) -
情況二
an=a(n?1)/2?a(n?1)/2?a,n為奇數(shù)a^n = a^{(n-1)/2}* a^{(n-1)/2}*a , n為奇數(shù) an=a(n?1)/2?a(n?1)/2?a,n為奇數(shù) -
從上面公式我們看出,要求n次方,我們可以先求n/2次方,再把n/2次方平凡就可以。這可以用遞歸的思路來實(shí)現(xiàn)。
-
我們用如下方式實(shí)現(xiàn),因?yàn)榇嬖诰仃嚨挠嬎?#xff0c;用代碼實(shí)現(xiàn)比較繁瑣,如下:
-
時間復(fù)雜度:設(shè)為f(n),其中n 是矩陣的冪次。從上述代碼中不難得出f(n) = f(n/2) + O(1) 。利用主定理,可以解得f(n) = O(log?n\log^{n}logn)
-
空間復(fù)雜度:每一次遞歸調(diào)用時新建了一個變量matrixPower(n/2)。由于代碼需要執(zhí)行log?2n\log_2^{n}log2n?次,即遞歸深度是log?2n\log_2^{n}log2n? ,所以空間復(fù)雜度是O(log?n\log^{n}logn)
解法比較
- 用不同方法求解斐波那契數(shù)列的時間效率有很大區(qū)別。第一種基于遞歸的解法,時間復(fù)雜度效率低,時間開發(fā)中不可能會用
- 第二種將遞歸算法用循環(huán)實(shí)現(xiàn),極大提高效率
- 第三種方法將斐波那契數(shù)列轉(zhuǎn)換炒年糕矩陣n次方求解,少有這種算法出現(xiàn),此處只是提出這種解法而已
變種題型
-
處理斐波那契數(shù)列這種問題,還有不少算法原理與斐波那契數(shù)列是一致的,例如:
-
題目:一只青蛙一次可以跳一個臺階,也可以跳兩個臺階。求解青蛙跳上n個臺階有多少中跳法
-
分析
- 最簡單情況,如果總共只有一節(jié)臺階,只有一種解法,如果有兩個臺階,有兩種跳法,
- 一般情況,n級臺階看出是n的函數(shù),記f(n), 當(dāng) n> 2 時候,第一次條就有兩種不同選擇,
- 第一種跳1級,此時后面的臺階的跳法等于f(n-1) ,那么總的跳法是 1 * f(n-1) = f(n -1)
- 第二種跳2級,此時后面的臺階跳法等于f(n-2) ,那么總的跳法是 1*f(n-2) = f(n-2)
- 所以n級臺階的不同跳法是 (n) = f(n-1) + f(n-2),實(shí)際上就是斐波那契數(shù)列
上一篇:數(shù)據(jù)結(jié)構(gòu)與算法–查找與排序另類用法
下一篇:數(shù)據(jù)結(jié)構(gòu)與算法–位運(yùn)算
總結(jié)
以上是生活随笔為你收集整理的数据结构与算法--再谈递归与循环(斐波那契数列)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构与算法--查找与排序另类用法-旋
- 下一篇: 数据结构与算法--位运算