斐波那契數列是一組非常有規律的數列,如下所示
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 .....
第0個數是0,第1個數是1,第2個數是第1個數和第0個數相加的和(1+0),第3個數是第2個數和第1個數相加的和(1+1),依次類推,第n個數永遠都是第n-1個數 和第n-2個數的和。后面的數,永遠是它前面兩個數的和。數學表示,就是
F
0 = 0, F
1=1, F
2 = F
1 + F
0, F
n = F
n-1 + F
n-2
那現在求第n個斐波那契數,比如第9個斐波那契數是多少,那怎么辦
1,遞歸
最先想到的是遞歸,根據公式 F
n = F
n-1 + F
n-2,求F
n,就要先求F
n-1 和 F
n-2, F
n-1 就要求F
n-2 和F
n-3,如果定義一個函數fib, 它接受n作為參數,返回第n個斐波那契數, 那么 fib(n) 就要返回fib(n-1) and fib(n-2), fib(n-1),fib(n-2) 正好調用自己,fib(n-1) 正好返回 fib(n-2) + fib(n-3) 函數不停地調用自己,但調用的參數值卻是不斷在減小,n, n-1, n-2, n-3,最終參數會到0,1,這時直接返回值,就可以了,因為F
0 = 0, F
1=1
function fib1(n) { if(n === 0) { return 0; } if (n === 1) { return 1; } return fib1(n - 1) + fib1(n -2); }
但是遞歸實現有一個問題,就是存在大量的重復計算
fib(5) / \ fib(4) fib(3) / \ / \ fib(3) fib(2) fib(2) fib(1) / \ / \ / \ fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) / \ fib(1) fib(0)
其實可以從前向后看,知道第0個數和第1個數,就可以知道第2個數,知道第2個數,又知道第1個數,就可以知道出第3個數,知道某個數, 又知道它的前一個數,就可以知道它后一個數,知道某個數之后,可以把它存起來,和它前面一個數,進行計算,就可以知道后一個數,然后后一個數再存起來,和前面的數進行計算,就可以知道后后一個數,依次類推,就可以知道第n個數。知道某個數,然后把它存起來,並計算下一個數,有兩種實現方式,一種是數組,把知道的某一個數作數組的每一項存起來。一種是聲明變量,把以前計算的數用變量存起來。
2,數組
斐波那契數列的第0個數和第1個數是知道的,分別是0和1,那就聲明一個數組arr, 數組第0項是0,arr[0] = 0,第1項是1,arr[1] = 1; 因為知道第1個數和第0個數,可以知道第2個數,所以知道數組的第0項和第1項, 就可以知道第2項(第0項+第1項) arr[2] = arr[1] + arr[0], 知道第2個數,就知道第3個數(第2個數+第1個數),也就意味着知道arr[2], 就可以計算出 arr[3] = arr[2] + arr[1], 同理,知道arr[3], 又可以計算arr[4], 直到arr[n]。可以發現,斐波那契數列的每一個數對應着數組的每一項。第n個斐波那契數就是數組的第n項。現在就變成了知道arr[0], arr[1], 求arr[n], 公式應該是arr[n] = arr[n-1] + arr[n-2] 。可以使用循環,從第2項開始,直接循環到n,循環體就是arr[n] = arr[n-1] + arr[n-2]。那arr[n] 就是第n個斐波那契數,聲明一個 n+1 長度的數組。
function fib2(n) { if (n == 0) { return 0; } if (n == 1) { return 1; } let arr = new Array(n + 1); arr[0] = 0; arr[1] = 1; for (let index = 2; index <= n; index++) { arr[index] = arr[index -1] + arr[index-2]; } return arr[n]; }
3,使用變量
使用數組有點浪費空間了,實際上,求斐波那契數中的某個數,只要知道它的前面第一個數和前面的第二個數就可以了,也就是說,在計算的過程中,只要保留前面第一個數和前面第二個數就可以了,沒有必要把前面所有的數都用數組保存起來。
現在知道第0個數是0, 第1個數是1, 可以把第1個數看做是前面第一個數,把第0個數看做是前面第二個數, 前面兩個數都知道了,那就可以計算出一個斐波那契數,這個斐波那契數就是第2個數1+0。此時,如果把計算得出的第2個數看成是前面第一個數,前面第二個數是知道的(就是第1個數(1))那就可以計算出第3個斐波那契數1+1。 此時,如果把計算得出的第3個數看成是前一個數,前面第二個數也是知道的(就是第2個數 1),那就可以計算出第4個數(2+1)。可以看到求斐波那契數,就是不停的調換前面的第一個數和前面的第二個數。現在可以用代碼實現一下
聲明兩個變量,preFirst, preSecond 表示前面一個數和前面第二個數,求出的斐波那契數設為curFib, 第一次的時候,
let preFirst = 1; let preSecond = 0; let curFib = preFirst + preSecond; // 1 + 0 = 1 此時curFib 是第2個數
第二次的時候,第一次preFirst 變成了preSecond, 把第一次中求出來的curFib 作為preFirst,
preSecond = preFirst; // 1
preFirst = curFib; // 1 curFib = preFirst + preSecond; // 1 + 1 = 2 curFib 是第3個數
第三次的時候,就是重復第二次的動作,第二次中的preFirst 變成preSecond, 把第二次計算出來的結果curFib作為preFirst,
preSecond = preFirst; // 1
preFirst = curFib; // 2 curFib = preFirst + preSecond; // 2 + 1 = 3 curFib 是第4個數
重復意味着循環,第二次開始循環,它求出來的是第3個數,第三次循環,它求出來的是第4個數,那么第n-1次循環,可以求出第n個數
function fib3(n) { let preFirst = 1; let preSecond = 0; let curFib = preFirst + preSecond; for (let i = 2; i <= n-1; i++) { preSecond = preFirst; preFirst = curFib; curFib = preFirst + preSecond; } return curFib; }
當然,n=0 和n=1 的時候,直接return 0 和1 就好了。還有一種循環,就是在循環中計算斐波那契數,那循環就要從第1次開始計算,
function fib3(n) { if (n == 0){ return 0; } if (n == 1){ return 1; } let preFirst = 1; let preSecond = 0; let curFib; for (let i = 1; i <= n-1; i++) { curFib = preFirst + preSecond; preSecond = preFirst; preFirst = curFib; } return curFib; }