走進不同的斐波那契數列

本文介紹了遞歸的原理及其斐波那契數列的四種實現方式。

全部源碼均已上傳至github:連接git

遞歸

遞歸算是算法中比較難的點了。遞歸的應用很是普遍。呢什麼樣的問題能夠用遞歸來解決呢?須要如下三個條件:

  • 一個問題的解能夠分解爲幾個子問題的解
  • 這個問題與分解以後的子問題,除了數據規模不一樣,求解思路徹底同樣
  • 存在遞歸終止條件

經典案例:

比較經典的例子就是最知名的斐波那契數列了。本文也以斐波那契數列爲例,先簡單介紹一下,斐波那契數列(Fibonacci sequence),又稱黃金分割數列(這個名字高大上)。程序員

指的是這樣一個數列:一、一、二、三、五、八、1三、2一、3四、……
github

在數學上,斐波納契數列以以下被以遞推的方法定義:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在現代物理、準晶體結構、化學等領域,斐波納契數列都有直接的應用。大體就是這樣。
算法

在寫遞歸以前要注意這麼幾個問題:

  • 遞歸的定義:簡單理解就是函數本身調用本身,將問題不斷分割成更小的子問題
  • 遞歸的缺陷:遞歸到必定程度,會發生"棧溢出",由於函數調用會使用棧來保存臨時變量。每調用一個函數,都會將臨時變量封裝爲棧幀壓入內存棧,等函數執行完成返回時,纔出棧。系統棧或者虛擬機棧空間通常都不大。若是遞歸求解的數據規模很大,調用層次很深,一直壓入棧,就會有堆棧溢出的風險。
  • 遞歸的"時間複雜度":遞歸總次數*每次遞歸的次數
  • 遞歸的"空間複雜度":遞歸的深度*每次遞歸空間的大小(注意:"每次遞歸空間的大小"是個常數,能夠基本忽略不計)
  • 遞歸的"深度":樹的高度(遞歸的過程是一個"二叉樹")

斐波那契數列的四種實現方式(兩種遞歸,兩種常規)

遞歸

時間複雜度:O(2^N)
空間複雜度:O(N)

這是最簡單的遞歸,只有兩行代碼,是否是很簡單呢?數組

public int recursion(int num) {
		if (num < 3) return 1;
		return recursion(num - 1) + recursion(num - 2);
	}複製代碼

可是這種實現的缺陷是重複計算的次數太多了,效率極其低下:緩存

F(3) = F(1) + F(2);bash

F(4) = F(2) + F(3);函數

F(5) = F(3) + F(4);測試

F(6) = F(5) + F(6);
優化

當計算到F(6)的時候,F(3)就已經重複了3次,所以改造一下:

public int recursion(int num) {
		// 必定要先給出遞歸跳出條件
		if (num < 3)
			return 1;
		// resCache是一個HashMap,key是num,value是res
		if (resCache.containsKey(num)) {
			return resCache.get(num);
		}
		int res = recursion(num - 1) + recursion(num - 2);
		resCache.put(num, res);
		return res;
	}複製代碼

ps:加一個hashMap來作緩存,避免重複計算

尾遞歸

時間複雜度:O(N-2)約等於0(N)
空間複雜度:O(N-2)約等於0(N)(編譯器若是優化的話是O(1))

先簡單瞭解一下什麼是尾遞歸。

  1. 定義:在一個程序中,執行的最後一條語句是對本身的調用,並且沒有別的運算
  2. 尾遞歸的實現:是在編譯器優化的條件下實現的

小知識(編譯器優化):

遞歸的第一次調用時會開闢一份空間,此後的遞歸調用不會再開闢空間,而是在剛纔開闢的空間上作一些修改,實現這次遞歸,例如求F(10),編譯器會給F(10)的調用開闢棧幀,調用F(9)的時候不會再從新開闢棧幀,而是在剛開闢的棧幀上作一些修改,由於遞歸的每一次調用都是同樣的流程,只是會有一些數據不一樣,因此不會再開闢空間。

public int tailRecursion(int num, int first, int second) {
		if (num < 3)
			return 1;
		if (num == 3)
			return first + second;
		return tailRecursion(num - 1, second, first + second);
	}複製代碼

下面兩種是非遞歸的實現,就比較簡單了,在此不作闡述。

常規實現

public int commonRcursion(int num) {
		if(num < 3) return 1;
		int first = 1;
		int second = 1;
		int res = 0;
		for (int i = 1; i < num - 1; i++) {
			res = first + second;
			first = second;
			second = res;
		}
		return res;
	}複製代碼

基於數組的實現

public int arrayRecursion(int num) {
		if(num < 3) return 1;
		int[] arrays = new int[num + 1];
		arrays[1] = 1;
		arrays[2] = 1;
		for (int i = 3; i <= num; i++) {
			arrays[i] = arrays[i - 1] + arrays[i - 2];
		}
		return arrays[num];
	}複製代碼

測試結果


擴展

下一篇則分享程序員必會的五個排序。

end


您的點贊和關注是對我最大的支持,謝謝!
相關文章
相關標籤/搜索