817|遞歸函數(shù)

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431756044276a15558a759ec43de8e30eb0ed169fb11000

在函數(shù)內(nèi)部,可以調(diào)用其他函數(shù)寻歧。如果一個函數(shù)在內(nèi)部調(diào)用自身本身阁谆,這個函數(shù)就是遞歸函數(shù)煤搜。

在函數(shù)內(nèi)部酣溃,可以調(diào)用其他函數(shù)。如果一個函數(shù)在內(nèi)部調(diào)用自身本身烛愧,這個函數(shù)就是遞歸函數(shù)音念。

舉個例子氯哮,我們來計算階乘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時需要特殊處理。

于是出牧,fact(n)用遞歸的方式寫出來就是:

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

遞歸函數(shù)的優(yōu)點是定義簡單穴肘,邏輯清晰。理論上舔痕,所有的遞歸函數(shù)都可以寫成循環(huán)的方式,但循環(huán)的邏輯不如遞歸清晰豹缀。

使用遞歸函數(shù)需要注意防止棧溢出伯复。在計算機中,函數(shù)調(diào)用是通過棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的邢笙,每當(dāng)進(jìn)入一個函數(shù)調(diào)用啸如,棧就會加一層棧幀,每當(dāng)函數(shù)返回氮惯,棧就會減一層棧幀叮雳。由于棧的大小不是無限的,所以妇汗,遞歸調(diào)用的次數(shù)過多帘不,會導(dǎo)致棧溢出⊙罴可以試試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

解決遞歸調(diào)用棧溢出的方法是通過尾遞歸優(yōu)化寞焙,事實上尾遞歸和循環(huán)的效果是一樣的,所以互婿,把循環(huán)看成是一種特殊的尾遞歸函數(shù)也是可以的捣郊。

尾遞歸是指,在函數(shù)返回的時候慈参,調(diào)用自身本身呛牲,并且,return語句不能包含表達(dá)式驮配。這樣娘扩,編譯器或者解釋器就可以把尾遞歸做優(yōu)化尊勿,使遞歸本身無論調(diào)用多少次,都只占用一個棧幀畜侦,不會出現(xiàn)棧溢出的情況元扔。

果一個函數(shù)中所有遞歸形式的調(diào)用都出現(xiàn)在函數(shù)的末尾,我們稱這個遞歸函數(shù)是尾遞歸的旋膳。當(dāng)遞歸調(diào)用是整個函數(shù)體中最后執(zhí)行的語句且它的返回值不屬于表達(dá)式的一部分時澎语,這個遞歸調(diào)用就是尾遞歸。尾遞歸函數(shù)的特點是在回歸過程中不用做任何操作验懊,這個特性很重要擅羞,因為大多數(shù)現(xiàn)代的編譯器會利用這種特點自動生成優(yōu)化的代碼。

尾遞歸通過計數(shù)來控制步驟义图,在每個步驟傳達(dá)具體的數(shù)减俏,而不是計算式。從而降低對棧的占用碱工。

上面的fact(n)函數(shù)由于return n * fact(n - 1)引入了乘法表達(dá)式娃承,所以就不是尾遞歸了。要改成尾遞歸方式怕篷,需要多一點代碼历筝,主要是要把每一步的乘積傳入到遞歸函數(shù)中:

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)僅返回遞歸函數(shù)本身廊谓,num - 1和num * product在函數(shù)調(diào)用前就會被計算梳猪,不影響函數(shù)調(diào)用。

fact(5)對應(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)用時蒸痹,如果做了優(yōu)化春弥,棧不會增長,因此叠荠,無論多少次調(diào)用也不會導(dǎo)致棧溢出匿沛。

遺憾的是,大多數(shù)編程語言沒有針對尾遞歸做優(yōu)化蝙叛,Python解釋器也沒有做優(yōu)化俺祠,所以,即使把上面的fact(n)函數(shù)改成尾遞歸方式借帘,也會導(dǎo)致棧溢出蜘渣。

小結(jié)

使用遞歸函數(shù)的優(yōu)點是邏輯簡單清晰,缺點是過深的調(diào)用會導(dǎo)致棧溢出肺然。

針對尾遞歸優(yōu)化的語言可以通過尾遞歸防止棧溢出蔫缸。尾遞歸事實上和循環(huán)是等價的,沒有循環(huán)語句的編程語言只能通過尾遞歸實現(xiàn)循環(huán)际起。

Python標(biāo)準(zhǔn)的解釋器沒有針對尾遞歸做優(yōu)化拾碌,任何遞歸函數(shù)都存在棧溢出的問題吐葱。

練習(xí)

漢諾塔的移動可以用遞歸函數(shù)非常簡單地實現(xiàn)。

請編寫move(n, a, b, c)函數(shù)校翔,它接收參數(shù)n弟跑,表示3個柱子A、B防症、C中第1個柱子A的盤子數(shù)量孟辑,然后打印出把所有盤子從A借助B移動到C的方法。

漢諾塔:漢諾塔(又稱河內(nèi)塔)問題是源于印度一個古老傳說的益智玩具蔫敲。大梵天創(chuàng)造世界的時候做了三根金剛石柱子饲嗽,在一根柱子上從下往上按照大小順序摞著64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上奈嘿。并且規(guī)定貌虾,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤裙犹。

解題:

1)把盤子在原始柱子上從上往下尽狠,從1開始整數(shù)編號,那么小序號的盤子一定要在大序號的盤子之上伯诬。

2)當(dāng)?shù)趎個盤子被從A柱移到C 柱時晚唇,它一定是A柱的最后一個盤子,移開之后A柱子空了盗似;它一定是C柱第一個盤子,移來之前C柱子是空的平项。

3)第n個盤子移到C柱子后赫舒,整個局面變成了(n-1)局面: B柱上是從1-(n-1)排列的盤子,A柱空闽瓢,我們的問題變成了將這(n-1)個盤子,從b柱上經(jīng)過a柱接癌,按照原來的規(guī)矩全部移動到C柱上去。

所以扣讼,這就是一個典型的遞歸問題缺猛。

def hanoi(n,a,b,c):
    if n == 1:
        print (a,' ==> ', c)
    else:
        hanoi(n-1,a,c,b) #將前n-1個盤子移動到b上
        hanoi(1,a,b,c) #將最底下一個盤子從a移動到c上
        hanoi(n-1,b,a,c) #開始下一個步驟,將b上的n-1個盤子移到 c上

hanoi(3,'A','B','C')

('A', ' ==> ', 'C')
('A', ' ==> ', 'B')
('C', ' ==> ', 'B')
('A', ' ==> ', 'C')
('B', ' ==> ', 'A')
('B', ' ==> ', 'C')
('A', ' ==> ', 'C')
[Finished in 0.0s]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椭符,一起剝皮案震驚了整個濱河市荔燎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌销钝,老刑警劉巖有咨,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒸健,居然都是意外死亡座享,警方通過查閱死者的電腦和手機婉商,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渣叛,“玉大人丈秩,你說我怎么就攤上這事〈狙茫” “怎么了蘑秽?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滤祖。 經(jīng)常有香客問我筷狼,道長,這世上最難降的妖魔是什么匠童? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任埂材,我火速辦了婚禮,結(jié)果婚禮上汤求,老公的妹妹穿的比我還像新娘俏险。我一直安慰自己,他們只是感情好扬绪,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布竖独。 她就那樣靜靜地躺著,像睡著了一般挤牛。 火紅的嫁衣襯著肌膚如雪莹痢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天墓赴,我揣著相機與錄音竞膳,去河邊找鬼。 笑死诫硕,一個胖子當(dāng)著我的面吹牛坦辟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播章办,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼锉走,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了藕届?” 一聲冷哼從身側(cè)響起挪蹭,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翰舌,沒想到半個月后嚣潜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡椅贱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年懂算,在試婚紗的時候發(fā)現(xiàn)自己被綠了只冻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡计技,死狀恐怖喜德,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垮媒,我是刑警寧澤舍悯,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站睡雇,受9級特大地震影響萌衬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜它抱,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一秕豫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧观蓄,春花似錦混移、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亲茅,卻和暖如春回铛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背克锣。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工勺届, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娶耍。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像饼酿,于是被迫代替她去往敵國和親榕酒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在函數(shù)內(nèi)部故俐,可以調(diào)用其他函數(shù)想鹰。如果一個函數(shù)在內(nèi)部調(diào)用自身本身,這個函數(shù)就是遞歸函數(shù)药版。 舉個例子辑舷,我們來計算階乘n!...
    小呀小芒果閱讀 3,824評論 0 0
  • 1.函數(shù)參數(shù)的默認(rèn)值 (1).基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值槽片,只能采用變通的方法何缓。
    趙然228閱讀 690評論 0 0
  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前肢础,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法碌廓。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,392評論 0 1
  • 長路漫浩奔游久传轰,無邊遠(yuǎn)志不可求 但得逍遙天上去,豈在人間幾回留
    馬丁路子閱讀 160評論 0 0
  • 吾有一顆敏感的心 去感受世界…… 聽到了微小的聲音 看到了透明的情感 聞到了氤氳的花香 嘗到了人間的美味 更能覺察...
    活著不易閱讀 309評論 3 6