1. 程式人生 > >斐波那契數列的遞迴演算法與非遞迴演算法

斐波那契數列的遞迴演算法與非遞迴演算法

一、斐波那契數列

由於斐波納挈數列是以兔子的繁殖引入的,因此也叫“兔子數列”。它指的是這樣一個數列:0,1,1,2,3,5,8,13......從這組數可以很明顯看出這樣一個規律:從第三個數開始,後邊一個數一定是在其之前兩個數的和。在數學上,斐波納挈數列可以以這樣的公式表示:F(0) = 0F(1) = 1F(n) = F(n-1) + F(n-2),(n>=2)

二、斐波納挈數列的實現

既然該數列已經有這樣一個規律:F(n) = F(n-1) + F(n-2);那麼我們很容易就能想到用遞迴的方法,這樣寫出來的程式碼比較簡潔

long long Fib1(long long num)
{
	assert(num >= 0);

	//遞迴
	if ((num == 1) || (num == 0))
	{
		return num;
	}
	return Fib1(num-1)+Fib1(num-2);
}
當然,我們也可以這樣寫:
long long Fib1(long long num)
{
	assert(num >= 0);

	//遞迴
	return num<2 ? num:(Fib1(num-1)+Fib1(num-2));
}

這樣的遞迴演算法雖然只有簡單的幾行,但是效率卻很低。為什麼呢?我們可以分析其遞迴呼叫的時間複雜度:

時間複雜度 ----- O(2^N)

由於使用遞迴時,其執行步驟是:要得到後一個數之前必須先計算出之前的兩個數,即在每個遞迴呼叫時都會觸發另外兩個遞迴呼叫,例如:要得到F(10)之前得先得到F(9)、F(8),那麼得到F(9)之前得先得到F(8)、F(7)......如此遞迴下去


從上圖我們可以看出,這樣的計算是以 2 的次方在增長的。除此之外,我們也可以看到,F(8)和F(7)的值都被多次計算,如果遞迴的深度越深,那麼F(8)和F(7)的值會被計算更多次,但是這樣計算的結果都是一樣的,除了其中之一外,其餘的都是浪費,可想而知,這樣的開銷是非常恐怖的!

所以,如果在時間複雜度和空間複雜度都有要求的話,我們可以用以下兩種非遞迴演算法來實現:

@@:時間複雜度為O(N),空間複雜度為O(N)

建立一個數組,每次將前兩個數相加後直接賦給後一個數。這樣的話,有N個數就建立一個包含N個數的一維陣列,所以空間複雜度為O(N);由於只需從頭向尾遍歷一邊,時間複雜度為O(N)

long long* Fib2(long long num)
{
	assert(num >= 0);
	//非遞迴
	long long* array = new long long[num+1];
	array[0] = 0;
	array[1] = 1;
	for (int i=2; i<=num; i++)
	{
		array[i] = array[i-1] + array[i-2];
	}
	return array;
}

@@:時間複雜度為O(N),空間複雜度為O(1)

藉助兩個變數 first 和 second ,每次將 first 和 second 相加後賦給 third ,再將 second 賦給 first ,third 賦給 second,如此迴圈。

long long Fib3(long long num)
{
	assert(num >= 0);
	long long first = 0;
	long long second = 1;
	long long third = 0;
	for(int i=2; i<=num; i++)
	{
		third = first + second;
		first = second;
		second = third;
	}
	return third;
}