日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构与算法--再谈递归与循环(斐波那契数列)

發布時間:2023/12/4 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构与算法--再谈递归与循环(斐波那契数列) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

再談遞歸與循環

  • 在某些算法中,可能需要重復計算相同的問題,通常我們可以選擇用遞歸或者循環兩種方法。遞歸是一個函數內部的調用這個函數自身。循環則是通過設置計算的初始值以及終止條件,在一個范圍內重復運算。比如,我們求累加1+2+3+…n,這個既可以用循環也可以用遞歸
/*** 遞歸實現* */public Long addFrom1N(long n){return n <= 0 ? 0 : n + addFrom1N(n-1);}/*** 循環實現* */public long addForm1N_interative(long n){if(n <=0){return 0;}long result= 0;for (long i = 1; i < n; i++) {result +=i;}return result;}
  • 如上案例實現,遞歸代碼簡潔,循環代碼比較多,同樣的在之前的文章二叉樹實現原理在樹的前序,中序,后序遍歷的代碼中,遞歸實現也明顯比循環實現要簡潔的多,所以我們盡量用遞歸來表達我們的算法思想。

  • 遞歸的缺點:

    • 遞歸優點顯著,由于是函數自身的調用,而函數調用有時間與空間的消耗:每一次調用都需要內存棧分配空間保存參數返回地址以及臨時變量,而往棧里壓入與彈出數據也需要時間,那就自然遞歸實現的效率比同等條件下循環要低下了
    • 另外遞歸中可能有很多計算是重復的,這個比較致命,對性能帶來很大影響。
    • 除了效率,遞歸還有可能出現調用棧溢出問題。因為每個進程棧空間有限,當遞歸次數太多,超出棧容量,導致棧溢出。
案例分析:斐波那契數列
  • 題目:寫一個函數,輸入n,求斐波那契(Fibonacci)數列的第n項。斐波那契數列的定義如下:
f(n) = f(n-1) + f(n-2) , n>1 f(n) = 0 , n=0 f(n) = 1 , n=1
斐波那契數列遞歸實現
  • 記得譚浩強版本的C語言中講解遞歸的時候就是用的斐波那契數列的案例,所以對這個問題非常的熟悉。看到之后自然就能提供如下代碼:
/*** f(n) = f(n-1) + f(n-2)* n > 2* n=1 : f(1) = 1* n=2 : f(2) = 1* f(3) = f(1) + f(2) = 1 + 1 = 2;*/public static Long getFibonacci(int n) {if (n <= 0L) {return 0L;}if (n == 1L || n == 2L) {return 1L;}return getFibonacci(n - 1) + getFibonacci(n - 2);}
  • 教科書上只是為了講解遞歸,這個案例正好比較合適,并不表示是最優解,其實以上方法是一種存在嚴重效率問題的解法,如下分析:
    • 我們求解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)
  • 如下代碼實現:
/*** 動態規劃求值* */public static Long getFibonacciGreat(int n) {if (n <= 0L) {return 0L;}if (n == 1L || n == 2L) {return 1L;}Long answer = 1L;Long last = 1L;Long nextLast = 1L;for (Long i = 0L; i < n - 2; i++) {answer = last + nextLast;nextLast = last;last = answer;}return answer;}
時間復雜度更優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次方平凡就可以。這可以用遞歸的思路來實現。

  • 我們用如下方式實現,因為存在矩陣的計算,用代碼實現比較繁瑣,如下:

/*** 矩陣對象定義* @author liaojiamin* @Date:Created in 15:21 2021/3/16*/ public class Matrix2By2 {private long m_00;private long m_01;private long m_10;private long m_11;public Matrix2By2(){this.m_00 = 0;this.m_01 = 0;this.m_10 = 0;this.m_11 = 0;}public Matrix2By2(long m00, long m01, long m10, long m11){this.m_00 = m00;this.m_01 = m01;this.m_10 = m10;this.m_11 = m11;}public long getM_00() {return m_00;}public void setM_00(long m_00) {this.m_00 = m_00;}public long getM_01() {return m_01;}public void setM_01(long m_01) {this.m_01 = m_01;}public long getM_10() {return m_10;}public void setM_10(long m_10) {this.m_10 = m_10;}public long getM_11() {return m_11;}public void setM_11(long m_11) {this.m_11 = m_11;} }*** 獲取斐波那契數列* @author liaojiamin* @Date:Created in 12:06 2021/2/2*/ public class Fibonacci {/*** 矩陣乘法求值* */public static Matrix2By2 matrixMultiply(Matrix2By2 matrix1, Matrix2By2 matrix2){return new Matrix2By2(matrix1.getM_00()*matrix2.getM_00() + matrix1.getM_01()*matrix2.getM_10(),matrix1.getM_00()*matrix2.getM_01() + matrix1.getM_01()*matrix2.getM_11(),matrix1.getM_10()*matrix2.getM_00() + matrix1.getM_11()*matrix2.getM_10(),matrix1.getM_10()*matrix2.getM_01() + matrix1.getM_11()*matrix2.getM_11());}/*** 矩陣乘方實現* */public static Matrix2By2 matrixPower(int n){if( n<= 0){return new Matrix2By2();}Matrix2By2 matrix = new Matrix2By2();if(n==1){matrix = new Matrix2By2(1,1,1,0);}else if(n%2 == 0){matrix = matrixPower(n/2);matrix = matrixMultiply(matrix, matrix);}else if(n%2 == 1){matrix = matrixPower((n-1)/2);matrix = matrixMultiply(matrix, matrix);matrix = matrixMultiply(matrix, new Matrix2By2(1,1,1,0));}return matrix;}public static long getFibonacciBest(int n){if(n == 0){return 0;}if (n <= 0) {return 0;}if (n == 1) {return 1;}Matrix2By2 powerNminus2 = matrixPower(n-1);return powerNminus2.getM_00();}public static void main(String[] args) {System.out.println("-------------------------1");System.out.println(System.currentTimeMillis()/1000);System.out.println(getFibonacci(40));System.out.println(System.currentTimeMillis()/1000);System.out.println("-------------------------2");System.out.println(System.currentTimeMillis()/1000);System.out.println(getFibonacciGreat(40));System.out.println(System.currentTimeMillis()/1000);System.out.println("-------------------------3");System.out.println(System.currentTimeMillis()/1000);System.out.println(getFibonacciBest(40));System.out.println(System.currentTimeMillis()/1000);} }
  • 時間復雜度:設為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),實際上就是斐波那契數列

上一篇:數據結構與算法–查找與排序另類用法
下一篇:數據結構與算法–位運算

總結

以上是生活随笔為你收集整理的数据结构与算法--再谈递归与循环(斐波那契数列)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。