python汉诺塔递归算法_Python文摘:汉诺塔问题与递归算法
歷史傳說(shuō):
在世界中心貝拿勒斯(在印度北部)的圣廟里,一塊黃銅板上插著三根寶石針。印度教的主神梵天在創(chuàng)造世界的時(shí)候,在其中一根針上從下到上地穿好了由大到小的64片金片,這就是所謂的漢諾塔。不論白天黑夜,總有一個(gè)僧侶在按照下面的法則移動(dòng)這些金片:一次只移動(dòng)一片,不管在哪根針上,小片必須在大片上面。僧侶們預(yù)言,當(dāng)所有的金片都從梵天穿好的那根針上移到另外一根針上時(shí),世界就將在一聲霹靂中消滅,而梵塔、廟宇和眾生也都將同歸于盡。
不管這個(gè)傳說(shuō)的可信度有多大,如果考慮一下把64片金片,由一根針上移到另一根針上,并且始終保持上小下大的順序。這需要多少次移動(dòng)呢?這里需要遞歸的方法。假設(shè)有n片,移動(dòng)次數(shù)是f(n).顯然f⑴=1,f⑵=3,f⑶=7,且f(k+1)=2*f(k)+1。此后不難證明f(n)=2^n-1。n=64時(shí),f(64)= 2^64-1=18446744073709551615。假如每秒鐘一次,共需多長(zhǎng)時(shí)間呢?一個(gè)平年365天有 31536000 秒,閏年366天有31622400秒,平均每年31556952秒,計(jì)算一下,18446744073709551615/31556952=584554049253.855年
這表明移完這些金片需要5845億年以上,而地球存在至今不過(guò)45億年,太陽(yáng)系的預(yù)期壽命據(jù)說(shuō)也就是數(shù)百億年。真的過(guò)了5845億年,不說(shuō)太陽(yáng)系和銀河系,至少地球上的一切生命,連同梵塔、廟宇等,都早已經(jīng)灰飛煙滅。
***********************************************************************************************************************************************************************************
漢諾塔問(wèn)題的限制條件:
1.在小圓盤上不能放大圓盤。
2.在三根柱子之間一回只能移動(dòng)一個(gè)圓盤。
3.只能移動(dòng)在最頂端的圓盤。
首先,我們從簡(jiǎn)單的例子開(kāi)始分析,然后再總結(jié)出一般規(guī)律。
當(dāng)n = 1的時(shí)候,即此時(shí)只有一個(gè)盤子,那么直接將其移動(dòng)至C即可。移動(dòng)過(guò)程就是 A -> C
當(dāng)n = 2的時(shí)候,這時(shí)候有兩個(gè)盤子,那么在一開(kāi)始移動(dòng)的時(shí)候,我們需要借助B柱作為過(guò)渡的柱子,即將A柱最上面的那個(gè)小圓盤移至B柱,然后將A柱底下的圓盤移至C柱,最后將B柱的圓盤移至C柱即可。那么完整移動(dòng)過(guò)程就是A -> B , A -> C , B -> C
當(dāng)n = 3的時(shí)候,那么此時(shí)從上到下依次擺放著從小到大的三個(gè)圓盤,根據(jù)題目的限制條件:在小圓盤上不能放大圓盤,而且把圓盤從A柱移至C柱后,C柱圓盤的擺放情況和剛開(kāi)始A柱的是一模一樣的。所以呢,我們每次移至C柱的圓盤(移至C柱后不再移到其他柱子上去),必須是從大到小的,即一開(kāi)始的時(shí)候,我們應(yīng)該想辦法把最大的圓盤移至C柱,然后再想辦法將第二大的圓盤移至C柱......然后重復(fù)這樣的過(guò)程,直到所有的圓盤都按照原來(lái)A柱擺放的樣子移動(dòng)到了C柱。
那么根據(jù)這樣的思路,問(wèn)題就來(lái)了:
如何才能夠?qū)⒆畲蟮谋P子移至C柱呢?
那么我們從問(wèn)題入手,要將最大的盤子移至C柱,那么必然要先搬掉A柱上面的n-1個(gè)盤子,而C柱一開(kāi)始的時(shí)候是作為目標(biāo)柱的,所以我們可以用B柱作為"暫存"這n-1個(gè)盤子的過(guò)渡柱,當(dāng)把這n-1的盤子移至B柱后,我們就可以把A柱最底下的盤子移至C柱了。
而接下來(lái)的問(wèn)題是什么呢?
我們來(lái)看看現(xiàn)在各個(gè)柱子上盤子的情況,A柱上無(wú)盤子,而B柱從上到下依次擺放著從小到大的n-1個(gè)盤子,C柱上擺放著最大的那個(gè)盤子。
所以接下來(lái)的問(wèn)題就顯而易見(jiàn)了,那就是要把B柱這剩下的n-1個(gè)盤子移至C柱,而B柱作為過(guò)渡柱,那么我們需要借助A柱,將A柱作為新的"過(guò)渡"柱,將這n-1個(gè)盤子移至C柱。
*********************************************************************************************************************************************************************
作者:Adherer
來(lái)源:CSDN
原文:https://blog.csdn.net/liujian20150808/article/details/50793101
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!
*********************************************************************************************************************************************************************
該問(wèn)題可以分解成以下子問(wèn)題:
第一步:將n-1個(gè)盤子從A柱移動(dòng)至B柱(借助C柱為過(guò)渡柱)
第二步:將A柱底下最大的盤子移動(dòng)至C柱
第三步:將B柱的n-1個(gè)盤子移至C柱(借助A柱為過(guò)渡柱)
解:(1)n == 1
第1次 1號(hào)盤 A---->C sum = 1 次
(2) n == 2
第1次 1號(hào)盤 A---->B
第2次 2號(hào)盤 A---->C
第3次 1號(hào)盤 B---->C sum = 3 次
(3)n == 3
第1次 1號(hào)盤 A---->C
第2次 2號(hào)盤 A---->B
第3次 1號(hào)盤 C---->B
第4次 3號(hào)盤 A---->C
第5次 1號(hào)盤 B---->A
第6次 2號(hào)盤 B---->C
第7次 1號(hào)盤 A---->C sum = 7 次
不難發(fā)現(xiàn)規(guī)律:1個(gè)圓盤的次數(shù) 2的1次方減1
2個(gè)圓盤的次數(shù) 2的2次方減1
3個(gè)圓盤的次數(shù) 2的3次方減1
。 。 。 。 。
n個(gè)圓盤的次數(shù) 2的n次方減1
故:移動(dòng)次數(shù)為:2^n - 1
n的階乘問(wèn)題
再說(shuō)一個(gè)例子:計(jì)算n的階乘
f(n) = n!
其遞歸算法如下:
int factorial(int n){
if(n == 1)
return 1;
else
return n * factorial(n-1);
}
這段程序加載到內(nèi)存的分配圖如下:
(圖片來(lái)源于“碼農(nóng)翻身”公眾號(hào))
由于遞歸是函數(shù)自身調(diào)用自身,所以程序被編譯后代碼段中只有一份代碼。遞歸調(diào)用是如何進(jìn)行的呢?注意看堆棧中的棧幀啊, 每個(gè)棧幀就代表了被調(diào)用中的一個(gè)函數(shù), 這些函數(shù)棧幀以先進(jìn)后出的方式排列起來(lái),就形成了一個(gè)棧, 棧幀的結(jié)構(gòu)如下圖所示:
(圖片來(lái)源于“碼農(nóng)翻身”公眾號(hào))
相信大家還記得《數(shù)據(jù)結(jié)構(gòu)》(嚴(yán)蔚敏版)一書中提到的“工作記錄”就是指函數(shù)棧幀。棧頂指針被稱為“當(dāng)前環(huán)境指針”。忽略到其他內(nèi)容, 只關(guān)注輸入?yún)?shù)和返回值的話,階乘函數(shù)factorial(4)的工作棧如下圖所示:
(圖片來(lái)源于“碼農(nóng)翻身”公眾號(hào))
其計(jì)算過(guò)程如下圖所示:
(圖片來(lái)源于“碼農(nóng)翻身”公眾號(hào))
注意, 每個(gè)遞歸函數(shù)必須得有個(gè)終止條件, 要不然就會(huì)發(fā)生無(wú)限遞歸了, 永遠(yuǎn)都出不來(lái)了。
當(dāng)然針對(duì)于此遞歸算法,對(duì)于n的值是有限制的。因?yàn)槎褩H萘渴怯邢薜?#xff0c;如果n值太大程序會(huì)崩掉。
該如何解決呢?從上面的代碼中可以知道“factorial(n) = n * factorial(n-1 )” ,這個(gè)計(jì)算式是整個(gè)程序的核心。 圖中每個(gè)棧幀都需要記錄下當(dāng)前的n的值, 還要記錄下一個(gè)函數(shù)棧幀的返回值, 然后才能運(yùn)算出當(dāng)前棧幀的結(jié)果。 也就是說(shuō)使用多個(gè)棧幀是不可避免的。
可以使用下面的遞歸算法:
int factorial(int n,int result){
if(n == 1){
return result;
}
else{
return factorial(n-1,n * result);
}
}
注意函數(shù)的最后一個(gè)語(yǔ)句, 就不是 n * factorial(n-1) 了, 而是直接調(diào)用factorial(....) 這個(gè)函數(shù)本身, 這就帶來(lái)了巨大的好處。
計(jì)算過(guò)程如下:
當(dāng)執(zhí)行到factorial(1, 24)的時(shí)候直接就可以返回結(jié)果了。這就是妙處所在了,計(jì)算機(jī)發(fā)現(xiàn)這種情況,只用一個(gè)棧幀就可以搞定這些計(jì)算,無(wú)論n有多大。
(圖片來(lái)源于“碼農(nóng)翻身”公眾號(hào))
這就是所謂的“尾遞歸”了, 當(dāng)遞歸調(diào)用是函數(shù)體中最后執(zhí)行的語(yǔ)句并且它的返回值不屬于表達(dá)式一部分時(shí), 這個(gè)遞歸就是尾遞歸。
現(xiàn)代的編譯器就會(huì)發(fā)現(xiàn)這個(gè)特點(diǎn), 生成優(yōu)化的代碼, 復(fù)用棧幀。 第一個(gè)算法中因?yàn)橛袀€(gè)n * factorial(n-1) , 雖然也是遞歸,但是遞歸的結(jié)果處于一個(gè)表達(dá)式中,還要做計(jì)算, 所以就沒(méi)法復(fù)用棧幀了,只能一層一層的調(diào)用下去。
總結(jié)
以上是生活随笔為你收集整理的python汉诺塔递归算法_Python文摘:汉诺塔问题与递归算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 给将要进入职场的同学 - 开发软件不是闭
- 下一篇: 去 QCon 学习