Python之遞迴函式

來源:互聯網
上載者:User

標籤:code   int   --   ack   style   導致   gpo   post   .com   

Python之遞迴函式

在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞迴函式。

舉個例子,我們來計算階乘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 factRuntimeError: 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 - 1num * 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標準的解譯器沒有針對尾遞迴做最佳化,任何遞迴函式都存在棧溢出的問題。

 

練習

漢諾塔的移動可以用遞迴函式非常簡單地實現。

請編寫move(n, a, b, c)函數,它接收參數n,表示3個柱子A、B、C中第1個柱子A的盤子數量,然後列印出把所有盤子從A藉助B移動到C的方法,例如:

# -*- coding: utf-8 -*-def move(n, a, b, c):    if n == 1:        print(a, ‘-->‘, c)

# 期待輸出:# A --> C# A --> B# C --> B# A --> C# B --> A# B --> C# A --> Cmove(3, ‘A‘, ‘B‘, ‘C‘)

#!/usr/bin/env python3# -*- coding: utf-8 -*-# 利用遞迴函式計算階乘# N! = 1 * 2 * 3 * ... * Ndef fact(n):    if n == 1:        return 1    return n * fact(n-1)print(‘fact(1) =‘, fact(1))print(‘fact(5) =‘, fact(5))print(‘fact(10) =‘, fact(10))# 利用遞迴函式移動漢諾塔:def move(n, a, b, c):    if n == 1:        print(‘move‘, a, ‘-->‘, c)    else:        move(n-1, a, c, b)        move(1, a, b, c)        move(n-1, b, a, c)move(4, ‘A‘, ‘B‘, ‘C‘)

 

Python之遞迴函式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.