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