一、汉诺塔问题背景
相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如下图所示)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
二、程序代码分析
void Hanoi(int n, char a, char b,char c)//n代表盘片数目,a,b,c代表三根杆
{
if(n==1)//递归出口,当起始杆上只有一个盘片时,直接移动到最终目标杆c上
printf(" 盘片%d从%c--->%c\n",n,a,c);
else
{
Hanoi(n-1,a,c,b);
printf(" 盘片%d从%c--->%c\n",n,a,c);
Hanoi(n-1,b,a,c);
}
}
(#下方一大波文字来袭)
我们假设Hanoi(int n, char a, char b,char c)中a,b,c所处的位置分别代表起始杆,中间杆和目标杆(注意:不是a,b,c杆代表起始杆,中间杆和目标杆,而是它们所处的位置即在n后面的第一个位置,第二个位置和第三个位置)。
(1)从最简单的情况来看,当只有一个盘片的时候,盘片直接从起始杆a上直接移动到目标杆c上;
(2)当起始杆a上有2个盘片的时候(即n=2,这一段提到的n都等于2),Hanoi1(2,a,b,c),它俩的共同目标都是c杆,这时,对于盘片1来说想去c杆按理说相当容易,直接从起始杆a跳到c杆就好了,但是盘片2比盘片1要大,它底下还有个老大盘片2呢,为了满足游戏规则,让老大盘片2先到c杆上,盘片1就只能先以杆b作为临时目标杆,先从起始杆a杆上跳到b杆上,就相当于代码中的Hanoi1(n-1,a,c,b)这一部分(这时b杆作为盘片1的目标杆,b杆处在目标杆的位置上);当盘片1移动到杆b后,紧接着盘片2就从起始杆a直接移动到最终的目标杆c上,就相当于对应代码中的printf(" 盘片%d从%c—>%c\n",n,a,c) 这一部分,待盘片2移动完后,盘片1终于可以心安理得地将b杆作为它的起始杆,从b杆移动到最终目标杆c上了,对应代码中的Hanoi1(n-1,b,a,c)这一部分(此时c杆就是盘片1的目标杆了,c杆又重新处在了目标杆的位置上);
(3)进一步分析,当n=3时,显然不满足n==1的条件,因而进入Hanoi1(n-1,a,c,b)即Hanoi1(2,a,c,b)再一次递归,这时b杆处在了目标杆的位置上,分析这个函数Hanoi1(2,a,c,b),两个盘片,转到(2)中,从(2)的分析中,我们不难得出,不管这两个盘片的移动过程,最终它们都能够移动到心仪的**目标杆(注意(2)中的目标杆是c,而此时Hanoi1(2,a,c,b)的目标杆是b)**上,按照这个思路,当这三个盘片中的盘片1和盘片2都移动到b杆上之后(也就是Hanoi1(2,a,c,b)结束之后),剩下的盘片3就轻松的移动到最终目标杆c上了,盘片3的移动同样对应代码中的printf(" 盘片%d从%c—>%c\n",n,a,c)这一部分,待盘片3移动完后,就该盘片1和盘片2再次动身移动了,即就该解决Hanoi(2,b,a,c)了,根据上面的分析,这时我们发现剩下的这两个盘片,它们的目标杆又变回到最终的目标杆c了,当然此时,对于盘片1和盘片2来说,它们的起始杆变为了b杆(b杆处在起始杆的位置),结合(2)分析,不管这两个盘片的移动过程,最终它们都能够按照游戏规则从起始杆移动到目标杆(这时目标杆c)上;
Hanoi(3, a, b, c)
Hanoi1(2,a,c,b)之后
printf(" 盘片%d从%c—>%c\n",3,a,c)之后
Hanoi(2,b,a,c)之后
(4)当n=4,同样的,可以借助(3)和(2)的分析得出结论,当Hanoi(3,a,c,b)(即盘片1和盘片2和盘片3都在b盘上)解决后,处于初始杆a最底层的盘片4将直接从a移动到目标盘c上(对应printf(" 盘片%d从%c—>%c\n",n,a,c)),然后在Hanoi(3,b,a,c)结束(即盘片1和盘片2和盘片3最终都移到c盘上)后,剩下的三个盘片都可最终都可以移动到c杆上;n=5,6,7…一样的思路(如果不闲麻烦的小伙伴可以自己仔细的分析一遍哈)
(5)通过分析,不难发现,上面的代码有一个很好的理解思路,n==1的情况比较简单,主要分析n>1的情况。当n>1的时候,可以将这n个盘片看作两部分,即第n个盘片和前面的(n-1)个盘片(就是将前面n-1个盘片看作一个整体,为了方便,就叫盘片整体n-1)两部分,这就相当于(2)中的两个盘片,盘片n对应盘片2,盘片整体n-1对应盘片1,先将整体n-1个盘片通过Hanoi(n-1,a,c,b)移动到b杆上,然后将盘片n直接从a杆移动到c杆上,最后再通过Hanoi(n-1,b,a,c)将盘片整体n-1移动到最终的目标杆c上,至于怎么移动,这就是计算机根据这个函数要自己去解决的问题了。(这么费脑细胞的事情,就让计算机自己去解决吧)
(6)总之,对于汉诺塔递归函数来说,当n>1时,它最先要解决的是最顶层的盘片移动问题,即Hanoi(int n, char a, char b,char c)一直不断的重复调用自己,从n到n-1到n-2到n-3…一直到1的时候,即到达起始杆当前最顶层的盘片时,先将最顶层的盘片移开,然后再移动其下面一层的盘片,然后再移动下下一层的盘片,然后再(这些盘片要先找一个临时的目标杆存放)…一直移动到起始杆上只有最底层的盘片时,将其直接移动到最终目标杆c上,这样的过程一直往复进行,直到所有的盘片都移动到最终目标杆c上,至于这些盘片移动到哪根杆上、是如何移动的,这个是计算机可以解决的,当然在这些盘片移动的过程中,为了满足游戏规则,它们的临时目标杆会不断变化,这也是计算机考虑的,我们在设计算法的时候可以不用去考虑。这个分析可以结合下面的图解说明一起理解。
三、图解说明
为了方便,假设n=64;下面的图片中c是最终目标杆;(若啰嗦,可以直接跳到结尾看小结)
刚开始的时候,a是起始杆,b是中间杆,c是目标杆,如下图所示数字越大代表盘片越大,n=64明显属于n>1的情况,这个时候起始杆a上的所有盘片都想要去目标杆c杆上,按照游戏规则,首先应该是盘64先去c杆上,但是盘64前面还压着63个小盘,没办法直接从a跳到c上,盘64只好对离自己最近的盘63说“我要去c杆”,那盘63也很无奈呀,(盘63想要是我能出去的话,肯定不能直接去c杆,要先在b杆上待会儿)但是现在上面压着62个比自己小的盘片,也不能直接从杆a直接出去呀,然后盘63又对离自己比较近的盘62说“盘64要去c杆”,然后盘62也无奈,(盘62想要是我能出去的话,肯定不能直接去c杆,要先在b杆上待会儿),接着就一直往a杆顶一直传消息,直到传到盘1那里,好了,现在盘1上面已经没有盘片了,可以直接移动了,但是问题来了,盘1想从我开始到盘63都要在b杆上待会儿,空出c杆让盘64先过去,那要怎么移动才能最终把c杆给空出来,让最大的盘64到c杆底层呢?于是它就和盘2到盘63一起商量,想出了一套属于盘1到盘63的移动规则,即解决(此时b杆就是盘(64-1)的目标杆了)为了让盘64在c杆的底层,盘1到盘63的目标杆变为了b杆,为了让盘63在b杆的底层,盘1到盘62的目标杆变为了c杆,为了让盘62在目标杆c上,盘1到盘61目标杆为b杆,为了让盘61在目标杆b上,盘1到盘61目标杆为c杆(到此为止,盘1已经有了额?个目标盘了…)…盘1先怎么移,盘2又怎么移,盘3…再怎么移的问题;(具体怎么移动,计算机可以解决,就像二、中的分析一样)
好了,这个时候按照盘1到盘63商量好的移动规则,盘1到盘63就一直移啊移移啊移,盘64就看着它们一直移啊移移啊移…最终不知道什么时候,它们都按照游戏规则移到了b杆上,
这时候,等待了不知多久的盘64终于可以激动地从起始杆a杆上直接跳到最终目标杆c杆上了;然后这时候,看到盘64已经去到了c杆上,盘63也按捺不住,对离它最近的盘62说“我也要去c盘”…然后就这样一直又传到盘1那里,盘1到盘62经过了第一次的移动,它们有了经验,一起商量好移动规则,这次它们的临时目标杆变为了a杆,b杆对于它们来说此时就是起始杆了,
说移就移,这回就是盘63看着盘1到盘62一直移啊移移啊移,也是不知看了多久,最终,盘1到盘62从b杆都移动到了a杆上,
此时,盘62也激动地从杆b(此时b杆对于盘63来说是起始杆)直接跳到了目标杆c上了;那接下来就轮到了盘62…盘61…
一直移…不知过了多少亿年,最终只剩下盘1了,此时的b杆对于盘1来说就是它的起始杆(为什么b杆是它的起始杆,可以自己去找找规律推一下霍),终于起始杆上只有它一个盘片了,
那就很简单了,盘1鼓足干劲,直接从b杆底部跳到了目标杆c杆顶层,和其他的盘相遇了。(最累的就是盘1了,一直在移动,盘1的移动次数最多)
(1)简单来理解,上面一堆啰嗦的话概括起来就是,将盘1到盘63看作一个整体,**这样64个盘片就分成了两部分,这就相当于两个盘片在移动,**盘64要到目标杆c上,得先让它前面的63个盘片移动到b杆上(即要解决Hanoi1(63,a,c,b)),这个时候b杆对于这63个盘片来说就是它们暂时得目标杆,所以Hanoi1(63,a,c,b)中b杆处在了目标杆的位置上,当这个整体都移动到了b杆上之后(至于怎么移动,这个计算机会解决的),盘64就可以直接从起始杆a移动到目标杆c上了,盘64移完之后,剩下的这个整体(63个盘片)就可以将目标杆换为c杆,然后移动到c杆上就好了,即解决Hanoi1(63,b,a,c),这时我们发现因为这个整体现在都是b杆上,它们都是要从b杆移动到目标杆c上的,此时的b杆对于这63个整体盘片来说是它们的起始杆,所以b杆处在了起始杆的位置上。(注意我们在此处假设的是只有两个盘片,盘片64和前63个整体盘片)
(2)既然64个盘片可以这样来理解,那假设现在不是64个盘片,而是63个盘片,我们也可以把它看作盘片63和前62个盘片整体两部分,也是一样的分析思路,再接下来假设杆上只要移62个盘片,那么可以把它们看作盘62和前61个盘片整体两部分,…一直到假设到题目只给两个盘片的时候,我们都发现这样的分析思路是合乎逻辑的,即只有两个盘片,要先把其中小的盘片以a为起始杆移到b杆上,然后再从起始杆a上移动大的盘片到c杆上,最后再将b杆上的小盘片以b杆为起始杆移到c杆上;
(3)既然(2)中的分析是可行的,那么我们就可以往更多的盘片角度去分析,假设现在题目给65个盘片、66个盘片、67个盘片…一直到给n个盘片的时候,那我们就可以将这n个盘片看作盘片n和前(n-1)个盘片整体,先将这个整体移动到b杆上(对应Hanoi(n-1,a,c,b)),在将盘片n移到c杆上(对应printf(" 盘片%d从%c—>%c\n",n,a,c)),最后又将盘片整体(n-1)移到c杆上就可以了(对应Hanoi(n-1,b,a,c));