用Python實現斐波那契(Fibonacci)函數

來源:互聯網
上載者:User
Fibonacci斐波那契數列,很簡單,就是一個遞迴嘛,學任何程式設計語言可能都會做一下這個。

最近在玩Python,在粗略的看了一下Learning Python和Core Python之後,偶然發現網上有個文章Python程式員的進化寫的很有意思。於是打算仿照一篇,那篇文章用了十餘種方法完成一個階乘函數,我在這裡會用九種不同的風格寫出一個Fibonacci函數。

要求很簡單,輸入n,輸出第n個Fibonacci數,n為正整數

下面是這九種不同的風格:

1)第一次寫程式的Python程式員:

def fib(n):  return nth fibonacci number

說明:
第一次寫程式的人往往遵循人類語言的文法而不是程式設計語言的文法,就拿我一個編程很猛的哥們來說,他寫的第一個判斷閏年的程式,裡面直接是這麼寫的:如果year是閏年,輸出year是閏年,否則year不是閏年。

2)剛學Python不久的的C程式員:

def fib(n):#{ if n<=2 :  return 1; else:  return fib(n-1)+fib(n-2);#}

說明:
在剛接觸Python時,用縮排而非大括弧的方式來劃分程式塊這種方式我是很不適應的,而且每個語句後面沒有結束符,所以每次寫完一個Python函數之後乾的第一件事一般就是一邊注釋大括弧,一邊添加漏掉的冒號。

3)懶散的Python程式員:

def fib(n):  return 1 and n<=2 or fib(n-1)+fib(n-2)

說明:
看了Learning Python之後,才知道Python沒有三元操作符?,不過鑒於Python裡bool值比較特殊(有點像C,非零即真,非空即真),再加上Python的邏輯語句也是支援短路求值(Short-Circuit Evaluation)的,這就可以寫出一個仿?語句出來。

4)更懶的Python程式員:

 fib=lambda n:1 if n<=2 else fib(n-1)+fib(n-2)

說明:
lambda關鍵字我曾在C#和Scheme裡面用過,Python裡面的lambda比C#裡簡便,並很像Scheme裡的用法,所以很快就適應了。在用Python Shell聲明一些小函數時經常用這種寫法。

5)剛學完資料結構的Python程式員:

def fib(n): x,y=0,1 while(n):  x,y,n=y,x+y,n-1 return x

說明:
前面的Fibonacci函數都是樹形遞迴的實現,哪怕是學一點演算法就應該知道這種遞迴的低效了。在這裡從樹形遞迴改為對應的迭代可以把效率提升不少。
Python的元組賦值特性是我很喜歡的一個東東,這玩意可以把代碼簡化不少。舉個例子,以前的tmp=a;a=b;b=tmp;可以直接用一句a,b=b,a實現,既簡潔又明了。

6)正在修SICP課程的Python程式員:

def fib(n):  def fib_iter(n,x,y):   if n==0 : return x   else : return fib_iter(n-1,y,x+y)  return fib_iter(n,0,1)

說明:
在這裡我使用了Scheme語言中很常見的尾遞迴(Tail-recursion)寫法。Scheme裡面沒有迭代,但可以用不變數和尾遞迴來類比迭代,從而實現相同的效果。不過我還不清楚Python有沒有對尾遞迴做相應的最佳化,回頭查一查。
PS:看過SICP的同學,一眼就能看出,這個程式其實就是SICP第一章裡的一個例子。

7)好耍小聰明的Python程式員:

fib=lambda n,x=0,y=1:x if not n else f(n-1,y,x+y)

說明:
基本的邏輯和上面的例子一樣,都是尾遞迴寫法。主要的區別就是利用了Python提供的預設參數和三元操作符,從而把代碼簡化至一行。至於預設參數,學過C++的同學都知道這玩意,至於C#4.0也引入了這東東。

8)剛修完線性代數的Python程式員:

def fib(n): def m1(a,b):  m=[[],[]]  m[0].append(a[0][0]*b[0][0]+a[0][1]*b[1][0])  m[0].append(a[0][0]*b[0][1]+a[0][1]*b[1][1])  m[1].append(a[1][0]*b[0][0]+a[1][1]*b[1][0])  m[1].append(a[1][0]*b[1][0]+a[1][1]*b[1][1])  return m def m2(a,b):  m=[]  m.append(a[0][0]*b[0][0]+a[0][1]*b[1][0])  m.append(a[1][0]*b[0][0]+a[1][1]*b[1][0])  return m return m2(reduce(m1,[[[0,1],[1,1]] for i in range(n)]),[[0],[1]])[0]

說明:
這段代碼就不像之前的代碼那樣清晰了,所以先介紹下原理(需要一點線性代數知識):
首先看一下之前的迭代版本的Fibonacci函數,很容易可以發現存在一個變換:y->x, x+y->y。換一個角度,就是[x,y]->[y,x+y]。
在這裡,我聲明一個二元向量[x,y]T,它通過一個變換得到[y,x+y]T,可以很容易得到變換矩陣是[[1,0],[1,1]],也就是說:[[1,0],[1,1]]*[x,y]T=[y,x+y]T
令二元矩陣A=[[1,0],[1,1]],二元向量x=[0,1]T,容易知道Ax的結果就是下一個Fibonacci數值,即:
Ax=[fib(1),fib(2)]T
亦有:
Ax=[fib(2),fib(3)]T
………………
以此類推,可以得到:

Aⁿx=[fib(n),fib(n-1)]T

也就是說可以通過對二元向量[0,1]T進行n次A變換,從而得到[fib(n),fib(n+1)]T,從而得到fib(n)。

在這裡我定義了一個二元矩陣的相乘函數m1,以及一個在二元向量上的變換m2,然後利用reduce操作完成一個連乘操作得到Aⁿx,最後得到fib(n)。

9)準備參加ACM比賽的Python程式員:

 def fib(n): lhm=[[0,1],[1,1]] rhm=[[0],[1]] em=[[1,0],[0,1]] #multiply two matrixes def matrix_mul(lhm,rhm):  #initialize an empty matrix filled with zero  result=[[0 for i in range(len(rhm[0]))] for j in range(len(rhm))]  #multiply loop  for i in range(len(lhm)):   for j in range(len(rhm[0])):    for k in range(len(rhm)):     result[i][j]+=lhm[i][k]*rhm[k][j]  return result  def matrix_square(mat):  return matrix_mul(mat,mat) #quick transform def fib_iter(mat,n):  if not n:   return em  elif(n%2):   return matrix_mul(mat,fib_iter(mat,n-1))  else:   return matrix_square(fib_iter(mat,n/2)) return matrix_mul(fib_iter(lhm,n),rhm)[0][0]

說明:

看過上一個fib函數就比較容易理解這一個版本了,這個版本同樣採用了二元變換的方式求fib(n)。不過區別在於這個版本的複雜度是lgn,而上一個版本則是線性。

這個版本的不同之處在於,它定義了一個矩陣的快速求冪操作fib_iter,原理很簡單,可以類比自然數的快速求冪方法,所以這裡就不多說了。

PS:雖然說是ACM版本,不過說實話我從來沒參加過那玩意,畢竟自己演算法太水了,那玩意又太高端……只能在這裡YY一下鳥~

python中,最基本的那種遞迴(如下fib1)效率太低了,只要n數字大了運算時間就會很長;而通過將計算的指儲存到一個dict中,後面計算時直接拿來使用,這種方式成為備忘(memo),如下面的fib2函數所示,則會發現效率大大提高。

在n=10以內時,fib1和fab2已耗用時間都很短看不出差異,但當n=40時,就太明顯了,fib1運行花了35秒,fab2運行只花費了0.00001秒。
n=40時,輸出如下:

jay@jay-linux:~/workspace/python.git/py2014$ python fibonacci.py 2014-10-16 16:28:35.176396fib1(40)=1023341552014-10-16 16:29:10.479953fib2(40)=1023341552014-10-16 16:29:10.480035

這兩個計算Fibonacci數列的函數,如下:https://github.com/smilejay/python/blob/master/py2014/fibonacci.py

import datetimedef fib1(n):  if n == 0:    return 0  elif n == 1:    return 1  else:    return fib1(n - 1) + fib1(n - 2) known = {0: 0, 1: 1} def fib2(n):  if n in known:    return known[n]   res = fib2(n - 1) + fib2(n - 2)  known[n] = res  return resif __name__ == '__main__':  n = 40  print(datetime.datetime.now())  print('fib1(%d)=%d' % (n, fib1(n)))  print(datetime.datetime.now())  print('fib2(%d)=%d' % (n, fib2(n)))  print(datetime.datetime.now())

後記:

由於剛學習Python沒多久,所以對其各種特性的掌握還不夠熟練。與其說是我在用Python寫程式,倒不如說我是在用C,C++,C#或是Scheme來寫程式。至於傳說中的Pythonic way,我現在還沒有什麼體會,畢竟還沒用Python寫過什麼真正的程式。
Learning Python和Core Python都是不錯的Python入門書籍,前者更適合沒有編程基礎的人閱讀。
Python是最好的初學編程入門語言,沒有之一。所以它可以取代Scheme成為MIT的電腦編程入門語言。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.