更多數學趣題:Fibonacci斐波拉契數列

===》點我返回目錄《===

現在世人皆知的Fibonacci斐波拉契數列最早來源於兔子繁殖問題,大約在800年前由Fibonacci引入(他的另一大貢獻是引入了阿拉伯數字)。說的是假定兔子在出生兩個月後,就有繁殖能力,一對兔子每個月能生出一對小兔子來。如果所有兔子都不死,那麼一年以後可以繁殖多少對兔子?

對一對新生的兔子,我們看一下怎麼繁殖的:

第一個月兔子沒有繁殖能力,所以還是一對;

第二個月兔子還是沒有繁殖能力,所以還是一對;

第三個月兔子生下一對小兔,有了兩對;

第四個月兔子又生下一對小兔,有了三對;

第五個月兔子又生下一對小兔,第三個月生下來的小兔長大了也生了新一代小兔,所以有了5對;

...

寫成數列爲 1,1,2,3,5,...

從這個數列,我們可以得出結論,從第三項開始,每一項都是前兩項之和。Fn=Fn-1+Fn-2。我們可以用遞推的方法寫一個程序計算一下:

n=10

a = 1

b = 1

for i in range(3,n+1):

    c = a + b

    a=b

    b=c

程序很簡單,如果計算某位的這個數字,就是從頭開始循環,一直累加下去。我們也能看出來,這個程序,循環次數是O(n)。

有沒有辦法能不經過循環,一下子求出值來?有的,可以使用如下通項公式:

。用無理數表示自然數,這是一個範例。

Fibonacci數列有一個很美的性質,就是它的通項比越來越接近於黃金分割比0.618。這個是觀測的結果,也是通過極限證明的結果。

Xn=Fn+1/Fn=(Fn+Fn-1)/Fn=1+ Fn-1/Fn=1+1/Xn-1

求極限,得到x=1+1/x。解出值爲,這就是黃金分割值。因此美學上也會經常提及Fibonacci數列。

真理好神奇啊。在我們的現實世界裏,會經常碰到斐波那契數。

比如花瓣的數量,蘭花、茉利花、百合花有3個花瓣,毛茛屬的植物有5個花瓣,翠雀屬植物有8個花瓣,萬壽菊屬植物有13個花瓣,向日葵的花瓣有的是21枚有的是34枚,雛菊的花瓣有的是34、55或89枚。

比如樹木的生長,新的一枝從葉腋長出,而另外的新枝又從舊枝長出來,枝條的數目就是Fibonacci數。這是生物學上的魯德維格定律。

自然總是在不經意之間向我們吐露它內在的真理之美。

斐波那契數列前幾項的平方和可以看做不同大小的正方形,由於斐波那契的遞推公式,它們可以拼成一個大的矩形。於是有了下面迷人的鸚鵡螺螺旋圖案:

 

斐波拉契數列,還有好多場景也會碰到。

小時候,大人經常會問到一個走樓梯的問題,說是一次只能走一級樓梯或者兩級樓梯,走上20級的樓梯,一共有多少種走法。

這個問題跟兔子生兔子初看起來沒什麼相同,但是我們仔細分析一下。因爲一次只能走一級樓梯或者兩級樓梯,所以你站在20級樓梯的時候,必定是從第18級或者第19級走過來的,而你站在19級樓梯的時候,必定是從第18級或者第17級走過來的,這樣一步一步推,得到的就是這個公式:Fn=Fn-1+Fn-2。跟兔子問題是一樣的。

那麼有沒有可能按照這個結構寫程序呢?有的,我們看一下:

def fib(n):

    if n == 1:

        return 1

    if n == 2:

        return 1

    return fib(n-1)+fib(n-2)

我們看這個函數的定義,fib(n)的返回值是fib(n-1)+fib(n-2)。這個概念上很清晰,但是如果只有這一句就陷入死循環中無法出來了,所以當n爲1和2的時候,我們要給他規定一個值。測試一下,肯定是對的。

現在程序結構就和數學表達式很接近了,直覺上更容易理解。這就是遞歸的實現。

用遞歸的思想,我們還可以重寫以前的階乘。

def factorial(n):

    if n == 1:

        return 1

    else:

        return n*factorial(n-1)

這個程序更加簡單一點,我們一步步看這個程序如何執行factoria(4)。

第一回合,執行factorial(4),計算機會開闢一片棧空間,保存這次調用的上下文,記住了n=4。執行else指令,去計算 n*factorial(n-1),也就是4* factorial(3)。執行中斷,調用factorial(3),進入第二回合。

第二回合,執行factorial(3),計算機會開闢一片新的棧空間,保存這次調用的上下文,記住了n=3。執行else指令,去計算 n*factorial(n-1),也就是3* factorial(2)。執行中斷,調用factorial(2),進入第三回合。

第三回合,執行factorial(2),計算機還是會開闢一片新的棧空間,保存這次調用的上下文,記住了n=2。執行else指令,去計算 n*factorial(n-1),也就是2* factorial(1)。執行中斷,調用factorial(1),進入第四回合。

第四回合,執行factorial(1),計算機仍然還是會開闢一片新的棧空間,保存這次調用的上下文,記住了n=1。執行if指令,return 1。釋放本回合的棧空間,帶着返回值1回到第三回合。

繼續執行第三回合的中斷點:2* factorial(1),即執行2*1,然後返回。釋放本回合的棧空間,帶着返回值2回到第二回合。

繼續執行第二回合的中斷點:3* factorial(2),即執行3*2,然後返回。釋放本回合的棧空間,帶着返回值6回到第一回合。

繼續執行第一回合的中斷點:4* factorial(3),即執行4*6,然後返回。釋放本回合的棧空間,帶着返回值24返回給客戶。

遞歸程序的好處是結構簡單,接近數學公式。

我們再來看一個用遞歸的例子,求最大共約數:

def gcd(a, b):

    if a < b:

        a, b = b, a

    if b == 0:

        return a

    while b != 0:

        a,b = b,a%b

    return a

用的輾轉相除法。注意一個新的寫法a, b = b, a,這是把以前b的值賦給a,把以前a的值賦給b,相當於交換。同樣a,b = b,a%b,這是把以前b的值賦給a,把以前a%b的值賦給b。

用到遞歸後,程序變成:

def gcd(a, b):

    if a < b:

        a, b = b, a

    if b == 0:

        return a

    a,b = b,a%b

    return gcd(a,b)

遞歸的缺點就是性能低,佔用棧空間多,甚至會溢出出錯(自己測試一下,給一個大的數,如10000,會出錯RecursionError: maximum recursion depth exceeded in comparison)。對斐波拉契數列,遞歸實現的性能爲O(1.618*n)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章