Fibonacci數列高效解法大全及時間復雜度分析 連載【1】

在數學上,斐波那契數列是以遞歸的方法來定義


用程序算斐波那契數列最常見的就是迭代與遞歸算法。

為了評價各算法程序效率如何

安裝 profiler 來計時和能看到每行的運行次數

先在系統下輸入命令pip install line_profiler安裝

使用也很簡單,在要計時的函數定義前加上@profile這個裝飾符

然后在系統下輸入命令kernprof -l -v 被計時的源程序.py


開始看第一個程序,輸入參數n算第n項的Fibonacci數。以下皆是在Python 3.7環境下運行

1.? 非遞歸的迭代解法

def Fibonacci_sequence_01 (n: int) -> int: #參數n是表示求第n項Fibonacci數

? ? '返回單項的for迭代解法'

??? assert isinstance(n, int), 'n is an error of non-integer type.'

? ? if n>=2:

? ? ? ? prev_num, current_num = 0, 1

? ? ? ? for i in range(2, n+1):

? ? ? ? ? ? prev_num, current_num = current_num, prev_num+current_num

? ? ? ? return current_num

? ? elif n==1:

? ? ? ? return 1

? ? elif n==0:

? ? ? ? return 0

? ? else:

? ? ? ? return None

Fibonacci_sequence_01(1200)

用算到第1200項Fibonacci數來測量下用時

在我的電腦上Total time: 0.002855秒


2.? 典型的遞歸解法,算法可讀性很好

def Fibonacci_sequence_02 (n: int) -> int: #參數n是表示求第n項Fibonacci數

? ? assert isinstance(n, int), 'n is an error of non-integer type.'

? ? def Calculate_Fibonacci_sequence (n: int) -> int:

? ? ? ? '返回單項的遞歸解法'

? ? ? ? if n>=2:

? ? ? ? ? ? return Calculate_Fibonacci_sequence(n-1) + Calculate_Fibonacci_sequence(n-2)

? ? ? ? elif n==1:

? ? ? ? ? ? return 1

? ? ? ? elif n==0:

? ? ? ? ? ? return 0

? ? if n>=0:

? ? ? ? Calculate_Fibonacci_sequence(n)

? ? else:

? ? ? ? return None

然而,時間復雜度是:O(1.618 ^ n),沒有實用價值。這個時間復雜度的詳解見“算法的時間復雜度”這篇文章


3.? 尾遞歸解法

遞歸解法的一個缺點是遞歸調用會一層一層壓棧,占用棧空間達O(n)。特別是Python默認只設1000層,超過就拋出異常了

尾遞歸算法相當于迭代的變形,理論上尾遞歸不需要保留調用前信息,棧空間只需要O(1)。但是Python沒有針對尾遞歸形式做識別,尾遞歸調用時還是有一層一層壓棧動作,超出默認棧深度就不行了。對于不支持尾遞歸優化的語言,這時要用一個叫“蹦床(Trampoline)”的技巧

“蹦床”也就是對遞歸函數進行包裝,蹦床攔截了傳給遞歸函數的參數,遞歸函數代碼的加載過程都透過這個蹦床。每次的遞歸調用,實際都被蹦床轉化為蹦床去拿攔截的參數調用一次遞歸函數,這樣棧空間占用一直為O(1)

來看裝飾器樣式,樣子如:

先對尾遞歸函數加上裝飾器

@proper_tail_call

def Fibonacci_sequence(n):

然后使用時就是原樣不變的函數調用樣式

Fibonacci_sequence(n)

好了,下面就是完整的尾遞歸裝飾器及尾遞歸解法斐波那契數

import functools

import inspect

class TailCallException(Exception):

? ? def __init__(self, *args, **kwargs):

? ? ? ? self.args = args

? ? ? ? self.kwargs = kwargs

def proper_tail_call(func):

? ? '用于return式尾遞歸的蹦床裝飾器代碼。使用方法:在定義尾遞歸函數語句前一行寫裝飾器“@proper_tail_call”'

? ? @functools.wraps(func)

? ? def wrapper(*args, **kwargs):

? ? ? ? frame = inspect.currentframe()

? ? ??? if frame.f_back and frame.f_back.f_back and frame.f_code ==frame.f_back.f_back.f_code: #先判斷當前是否為遞歸調用(遞歸的話是_wrapper->被裝飾函數->_wrapper),再判斷是否存在前級和前前級調用

? ? ? ? ? ? raise TailCallException(*args, **kwargs)

? ? ? ? else:

? ? ? ? ? ? while True:

? ? ? ? ? ? ? ? try:

? ? ? ? ? ? ? ? ? ? return func(*args, **kwargs)

? ? ? ? ? ? ? ? except TailCallException as e:

? ? ? ? ? ? ? ? ? ? args = e.args

? ? ? ? ? ? ? ? ? ? kwargs = e.kwargs

? ? return wrapper

def Fibonacci_sequence_03 (n: int) -> int: #參數n是表示求第n項Fibonacci數

??? assert isinstance(n, int), 'n is an error of non-integer type.'

? ? @proper_tail_call

? ? def Calculate_Fibonacci_sequence (n: int, prev_num: int =0, current_num: int =1) -> int:

? ? ? ? '返回單項的return式尾遞歸解法'

? ? ? ? if n>=2:

? ? ? ? ? ? return Calculate_Fibonacci_sequence(n-1, current_num, prev_num+current_num)

? ? ? ? elif n==1:

? ? ? ? ? ? return current_num

? ? if n>=1:

? ? ? ? return Calculate_Fibonacci_sequence (n)

? ? elif n==0:

? ? ? ? return 0

? ? else:

? ? ? ? return None

Fibonacci_sequence_03(1200)

還是用算到第1200項Fibonacci數來測量下用時,Total time: 0.011484秒

未完待續……

Fibonacci數列高效解法大全及時間復雜度分析 連載【2】

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容