此题感觉比较重要,在此题的思路上延伸有比较多的难题!!
传送门:牛客
题目简介:
糖和抖m在玩个游戏,规定谁输了就要请谁吃顿大餐:抖m给糖a b c三个驻, 并在a柱上
放置了数量为n的圆盘,圆盘的大小从上到下依次增大,现在要做的事就是把a柱的圆盘全
部移到c柱,移动的过程中保持小盘在上,大盘在下,且限定圆盘只能够移动到相邻的柱
子,即a柱子上的圆盘只能够移动到b,b柱子上的圆盘只能够移动到a或者c,c同理。
现在请你设计一个程序,计算所需移动的最小步数, 帮助糖赢得大餐!
样例:
输入:
1
输出:
2
首先一看题面,一眼不就是汉诺塔吗(脑子里直接闪过一些七七八八的结论,好像可以递推也可以递归诶)
因为递推的实现代码比较好理解,所以接下来先上递推的思路,首先定义一个F(n)
表示将n个圆盘从A位置借助B位置移动到C位置需要F(n)步,然后我们很显然的就会知道我们我们在移动到C位置之前需要先将n-1个圆盘先借助C位置移动到B位置,再将最大的圆盘移动到C位置,此时我们需要F(n-1)+1的步数,最后在借助A位置将n-1个圆盘移动C位置,又需要F(n-1)个步数
注意:此时可能会有同学发问,为什么从B->A->C需要的步数和A->C->B一样呢,思考一下,我们三个的柱子是一模一样的,也就是说我们可以将此时的B当做A,A当做C,C当做B(这个转化的思想在递归方法中很重要,不仔细考虑一下会弄混淆)
此时我们就得到一个数列的递推关系啦
F(n)=2*F(n-1)+1
得到这个关系后,汉诺塔的答案就呼之欲出啦
接下来是递归关系的代码实现
abc为刚开始的三根柱子
void move(char a, char b, char c, int n){
if(n == 0){
return ;
}
move(a, c, b, n - 1);
printf("%c -> %c\n", a, c);
ans++;
move(b, a, c, n - 1);
}
千万不要被递归时三个字母的交换给搞混(刚开始我根本无法接受,看着abc换来换去,直接自闭),可能到这里,无数人会跟我一样打起了退堂鼓,MD,递推式子这么好推又好实现,为什么我还要学习这么个玩意.
这里注意,递推的方法是有很大局限性的,首先他并没有用代码来模拟出圆盘的交换,也就是说这种代码是无法实现输出交换过程的,而输出交换过程往往会是一些较难题目要求我们实现的,这是我们只能诉求于递归方法了
接下来我来模拟一下递归的实现过程:
首先根据之前递推方法的思路,我们需要将n-1个圆盘先移动到B的位置,为了方便起见我们将move函数的a参数看做起始的位置,b参数看做借助的位置,c参数看做目标的位置,那么第一步的代码就很好理解了,就是n-1从a借助c位置移动到b位置,此时我们的代码会进行递归,进行函数move(a,c,b,n-1),这是我们先想一下,我们将n-1的位置从a->c->b是不是一下子无法做到的,此时我们就又有了刚开始的需求,将此时的b看做我们刚开始的c位置,c位置看做我们刚开始的b位置,我们需要先将n-2个盘子从a->b->c,再将第n-1个盘子移动到c位置,再将n-2个盘子从c->b才能完成我们的需求,所以我们又需要进行一步move(a,c,b,n-2),注意此时我们move函数中的c是之前的b,也就是借助刚开始的b位置移动到c位置.这样子是不是就很好理解了.
很好,在上方我们已经介绍了一部分的函数过程,但是试想一下,虽然我们是有能搞懂代码的能力,但是如果每次都打代码时都这么麻烦的思考,是不是还是有一点绕的呢,这时我们试着抽象的去理解这部分代码,我们先总体的大局观的去观看这部分代码,a参数是我们刚开始的出发位置,b是借助的位置,c是目的地,我们先从出发地开始经过借助地到达目的地(也就是a->c->b),接下来的输出a参数和参数,也就是我们的出发地->目的地,不是就最大的盘子的移动吗,接着的b->a->c不就是将一开始我们的移动到b位置的盘子移动到c位置吗,这种是不是就比较好记了(其实就是n=2时的模拟实现过程罢了,我们不用仔细考虑递归的深层部分)
然后我们回到一开始的题目,其实并不是纯粹的汉诺塔问题,你会发现这道题的a位置只能移动到b位置,所以我们适当的变化一下
递推方法:我们先将n-1个盘子借助b位置移动到c位置(此时我们不能直接移动到b位置了),需要F(n-1)步,再将第n个移动到b位置需要一步,再将n-1个盘子通过b位置移动到a位置,又需要F(n-1)步,接着第n个移动到c位置,1步,最后n-1个盘子从a借助b到c,需要F(n-1)步.此刻移动完成,得到递推关系:
F(n)=3*F(n-1)+2
递归方法:按照上面递推方法的思路,先是a->b->c,所以有move(a,b,c,n-1),接着我们需要a->b,也就是printf(“%c->%c\n”,a,b);接着是c->b->a,所以有move(c,b,a,n-1),然后是b->c,也就是printf(“%c->%c\n”,b,c);最后是a->b->c
是不是只用n=2这种特殊情况来实现我们的代码就好理解多了呢
具体的代码
void move(char a, char b, char c, int n){
if(n == 0){
return ;
}
move(a, b, c, n - 1);
ans2++;
printf("%c -> %c\n", a, b);
move(c, b , a, n - 1);
ans2++;
printf("%c -> %c\n", b, c);
move(a, b, c, n - 1);
}