斐波那契數列 即 1、1、2、3、5、8、13、21、34、.....以此類推,在很多面試題中,面試官都會讓你手寫斐波那契數列的實現。對於一些有編程經驗的人來說,這很容易,他們可以很快寫出類似以下代碼:
設 n 爲 大於0的正整數,求第n個斐波那契數(1爲第一個,2爲第二個...8爲第五個)
def feb(n):
if n == 1 or n == 2:
return n
else:
return feb(n - 1) + feb(n - 2)
#這裏設數列從 1,2,3,5,8 開始
但是這裏有個很嚴重的問題就是重複計算
例如,在計算feb(5) 時,feb(1) 會調用多少次?feb(2)呢?
運行結果可見下圖
可以看到 feb(2)feb(1) 被多次調用
如何能避免這個問題呢?
避免重複 的關鍵在於 要實現檢查即將進行的計算是否已經經歷過。
有同學會想到使用列表,每計算一個feb(n),就將結果存儲到列表的下標n 處。
result=[1,,1,2,3,5,8.....]
feb(n)=result[n]
這看起來似乎是一個好方法,但因爲斐波那契數列可以是無限長的,如果計算feb(10000)是否真的需要長度10000的列表?(況且在python中實際列表所佔地址空間會大於其可見長度。)所以這種方式顯然不是可取的方式。
但是在斐波那契數列數列需要經常進行運算且n較小的時候,直接採取已經定義好的列表看起來的確是個一勞永逸的方法。
但是如果面試官要求你不使用列表,即儘可能減少內存佔用呢?
這裏問題就可以簡化爲只使用兩個變量。本身斐波那契數列 中 第n個就是 前兩個數相加的和。
只需要一直更新 feb(n-1) 和 feb(n-2) 的值即可。
現在我們可以使用for循環來實現:
def feb(n):
if n == 1 or n == 2:
return n
else:
n1,n2=1,2
for i in range(3, n):
n1,n2=n2,n1+n2
return n1 + n2
這樣做已經效果很好了,不需要藉助數組等大存儲 對象,時間複雜度也只是O(n)級別。
但是有沒有更好的解法?
學習高等數學、線性代數等課程時,可能有數學老師提到過斐波那契數列的另類解法--利用矩陣求解。
數列的遞推公式爲:f(1)=1,f(2)=2,f(n)=f(n-1)+f(n-2)(n>=3)
用矩陣表示爲:
進一步,可以得出直接推導公式:
接下來,我們先思考,如何用python實現矩陣相乘?
如果接觸過numpy 庫的話,你也許會想到numpy中的 dot 方法。
利用這個方法可以直接計算兩個矩陣(列表)的乘積。但是如果讓你自己去實現呢?
先提醒大家,矩陣可以相乘是有前提的
設有矩陣A*B
矩陣只有當左邊矩陣A的列數等於右邊矩陣B的行數時,纔可以相乘。乘積結果矩陣的行數等於左邊矩陣的行數,乘積矩陣的列數等於右邊矩陣的列數
矩陣的乘法是左行乘右列
def matrixMul(A, B):
res = [[0] * len(B[0]) for i in range(len(A))] #結果變量
for i in range(len(A)): #對於A的每行
for j in range(len(B[0])): #對於B的每列
for k in range(len(B)): #對於B的每行
res[i][j] += A[i][k] * B[k][j]
return res
上面的代碼是可用的,但是還有什麼可以優化的地方嗎?
注意到循環的層數足足三層,每一層都需要計算 len()。
所以最完美的寫法是應該是先計算好長度再直接用長度值去遍歷。
def matrixMul(A, B):
line_A,line_B,column_B=len(A),len(B),len(B[0])
res = [[0] * column_B for i in range(line_A)] #結果變量
for i in range(line_A): #對於A的每行
for j in range(column_B): #對於B的每列
for k in range(line_B): #對於B的每行
res[i][j] += A[i][k] * B[k][j]
return res
現在,我們可以用 矩陣乘法來 實現 斐波那契數列了
def matrixMul(A, B):
line_A,line_B,column_B=len(A),len(B),len(B[0])
res = [[0] * column_B for i in range(line_A)] #結果變量
for i in range(line_A): #對於A的每行
for j in range(column_B): #對於B的每列
for k in range(line_B): #對於B的每行
res[i][j] += A[i][k] * B[k][j]
return res
def feb(n):
if n <= 1: return max(n, 0)
res = [[1, 1], [0, 1]]
# 單位矩陣,等價於1 。如果 res 取值爲[[1,0],[0,1]] 則會得1,1,2,3,5,8...這個無所謂
A = [[1, 1], [1, 0]] # A矩陣
while n:
if n & 1: res = matrixMul(res, A) # 如果n是奇數,或者直到n=1停止條件
A = matrixMul(A, A) # 快速冪
n >>= 1 # 整除2,向下取整
return res[0][1]