遞歸函數(shù)
在函數(shù)內(nèi)部匾浪,可以調(diào)用其他函數(shù)。如果一個(gè)函數(shù)在內(nèi)部調(diào)用自身本身粹淋,這個(gè)函數(shù)就是遞歸函數(shù)。
舉個(gè)例子虏肾,我們來計(jì)算階乘n! = 1 x 2 x 3 x ... x n廓啊,用函數(shù)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時(shí)需要特殊處理。
于是吹埠,fact(n)用遞歸的方式寫出來就是:
deffact(n):ifn==1:return1returnn * fact(n -1)
上面就是一個(gè)遞歸函數(shù)第步。可以試試:
>>> fact(1)1>>> fact(5)120>>> fact(100)93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
如果我們計(jì)算fact(5)缘琅,可以根據(jù)函數(shù)定義看到計(jì)算過程如下:
===> 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
遞歸函數(shù)的優(yōu)點(diǎn)是定義簡(jiǎn)單团秽,邏輯清晰安皱。理論上励堡,所有的遞歸函數(shù)都可以寫成循環(huán)的方式妓布,但循環(huán)的邏輯不如遞歸清晰。
使用遞歸函數(shù)需要注意防止棧溢出呻纹。在計(jì)算機(jī)中堆生,函數(shù)調(diào)用是通過棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的专缠,每當(dāng)進(jìn)入一個(gè)函數(shù)調(diào)用,棧就會(huì)加一層棧幀淑仆,每當(dāng)函數(shù)返回涝婉,棧就會(huì)減一層棧幀。由于棧的大小不是無限的蔗怠,所以墩弯,遞歸調(diào)用的次數(shù)過多,會(huì)導(dǎo)致棧溢出寞射∮婀ぃ可以試試fact(1000):
>>> fact(1000)Traceback (most recent call last):? File "", line 1, inFile "", line 4, in fact? ...? File "", line 4, in factRuntimeError: maximum recursion depth exceeded
解決遞歸調(diào)用棧溢出的方法是通過尾遞歸優(yōu)化,事實(shí)上尾遞歸和循環(huán)的效果是一樣的怠惶,所以涨缚,把循環(huán)看成是一種特殊的尾遞歸函數(shù)也是可以的轧粟。
尾遞歸是指策治,在函數(shù)返回的時(shí)候,調(diào)用自身本身兰吟,并且通惫,return語句不能包含表達(dá)式。這樣混蔼,編譯器或者解釋器就可以把尾遞歸做優(yōu)化履腋,使遞歸本身無論調(diào)用多少次,都只占用一個(gè)棧幀惭嚣,不會(huì)出現(xiàn)棧溢出的情況遵湖。
上面的fact(n)函數(shù)由于return n * fact(n - 1)引入了乘法表達(dá)式,所以就不是尾遞歸了晚吞。要改成尾遞歸方式延旧,需要多一點(diǎn)代碼,主要是要把每一步的乘積傳入到遞歸函數(shù)中:
deffact(n):returnfact_iter(n,1)deffact_iter(num, product):ifnum ==1:returnproductreturnfact_iter(num -1, num * product)
可以看到槽地,return fact_iter(num - 1, num * product)僅返回遞歸函數(shù)本身迁沫,num - 1和num * product在函數(shù)調(diào)用前就會(huì)被計(jì)算,不影響函數(shù)調(diào)用捌蚊。
fact(5)對(duì)應(yīng)的fact_iter(5, 1)的調(diào)用如下:
===> fact_iter(5,1)===> fact_iter(4,5)===> fact_iter(3,20)===> fact_iter(2,60)===> fact_iter(1,120)===>120
尾遞歸調(diào)用時(shí)集畅,如果做了優(yōu)化,棧不會(huì)增長(zhǎng)缅糟,因此挺智,無論多少次調(diào)用也不會(huì)導(dǎo)致棧溢出。
遺憾的是窗宦,大多數(shù)編程語言沒有針對(duì)尾遞歸做優(yōu)化赦颇,Python解釋器也沒有做優(yōu)化谣辞,所以,即使把上面的fact(n)函數(shù)改成尾遞歸方式沐扳,也會(huì)導(dǎo)致棧溢出泥从。
使用遞歸函數(shù)的優(yōu)點(diǎn)是邏輯簡(jiǎn)單清晰,缺點(diǎn)是過深的調(diào)用會(huì)導(dǎo)致棧溢出沪摄。
針對(duì)尾遞歸優(yōu)化的語言可以通過尾遞歸防止棧溢出躯嫉。尾遞歸事實(shí)上和循環(huán)是等價(jià)的,沒有循環(huán)語句的編程語言只能通過尾遞歸實(shí)現(xiàn)循環(huán)杨拐。
Python標(biāo)準(zhǔn)的解釋器沒有針對(duì)尾遞歸做優(yōu)化祈餐,任何遞歸函數(shù)都存在棧溢出的問題。