Great-Fortune › 討論專區 › 程式語言 › Python 園地 › 遞迴函數
該主題包含 0 則回覆,有 1 個參與人,並且由 BettyBDChiang 於 7 年, 11 月 前 最後更新。
-
作者文章
-
2017-06-02 13:53:51 #1178
轉載自廖雪峰的官方網站
在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞迴函數。
舉個例子,我們來計算階乘n! = 1 x 2 x 3 x … x n,用函數fact(n)表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示為n x fact(n-1),只有n=1時需要特殊處理。
於是,fact(n)用遞迴的方式寫出來就是:def fact(n): if n==1: return 1 return n * fact(n - 1)
上面就是一個遞迴函數。可以試試:
>>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
如果我們計算fact(5),可以根據函式定義看到計算過程如下:
===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120
遞迴函數的優點是定義簡單,邏輯清晰。理論上,所有的遞迴函數都可以寫成迴圈的方式,但迴圈的邏輯不如遞迴清晰。
使用遞迴函數需要注意防止堆疊溢出。在電腦中,函式呼叫是通過堆疊(stack)這種資料結構實現的,每當進入一個函式呼叫,堆疊就會加一層堆疊幀,每當函數返回,堆疊就會減一層堆疊幀。由於堆疊的大小不是無限的,所以,遞迴呼叫的次數過多,會導致堆疊溢出。可以試試fact(1000):
>>> fact(1000) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in fact ... File "<stdin>", line 4, in fact RuntimeError: maximum recursion depth exceeded in comparison
解決遞迴呼叫堆疊溢出的方法是通過尾遞迴優化,事實上尾遞迴和迴圈的效果是一樣的,所以,把迴圈看成是一種特殊的尾遞迴函數也是可以的。
尾遞迴是指,在函數返回的時候,調用自身本身,並且,return語句不能包含運算式。這樣,編譯器或者解譯器就可以把尾遞迴做優化,使遞迴本身無論調用多少次,都只佔用一個堆疊幀,不會出現堆疊溢出的情況。
上面的fact(n)函數由於return n * fact(n – 1)引入了乘法運算式,所以就不是尾遞迴了。要改成尾遞迴方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞迴函數中:
def fact(n): return fact_iter(n, 1) def fact_iter(num, product): if num == 1: return product return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num – 1, num * product)僅返回遞迴函數本身,num – 1和num * product在函式呼叫前就會被計算,不影響函式呼叫。
fact(5)對應的fact_iter(5, 1)的調用如下:===> fact_iter(5, 1) ===> fact_iter(4, 5) ===> fact_iter(3, 20) ===> fact_iter(2, 60) ===> fact_iter(1, 120) ===> 120
尾遞迴呼叫時,如果做了優化,堆疊不會增長,因此,無論多少次調用也不會導致堆疊溢出。
遺憾的是,大多數程式設計語言沒有針對尾遞迴做優化,Python解譯器也沒有做優化,所以,即使把上面的fact(n)函數改成尾遞迴方式,也會導致堆疊溢出。小結
使用遞迴函數的優點是邏輯簡單清晰,缺點是過深的調用會導致堆疊溢出。
針對尾遞迴優化的語言可以通過尾遞迴防止堆疊溢出。尾遞迴事實上和迴圈是等價的,沒有迴圈語句的程式設計語言只能通過尾遞迴實現迴圈。
Python標準的解譯器沒有針對尾遞迴做優化,任何遞迴函數都存在堆疊溢出的問題。 -
作者文章
抱歉,回覆主題必需先登入。