不得不说,杭电oj上关于汉诺塔的各种题型,我实在是佩服。
在解决汉诺塔问题之前,先来讲一个小故事:
早在印度就有一个传说,大梵天在创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64个黄金圆盘。于是大梵天命令婆罗门把圆盘按大小顺序重新摆放到另一根柱子上。并且规定,大圆盘不能放在小圆盘上面,在三根柱子之间一次只能移动一个圆盘。
这就是最基础的汉诺塔问题,,就是让我们求移动步数的最优解(即用最少的步数来达到我们的目的),做这个题我们一般想到的是用递归来做(如果说不明白的话,可以自己去试一下,我建议可以去玩一下汉诺塔小游戏,挺好玩的哈哈,玩多了就能发现其中的规律了)。
用递归做的话代码如下:(假设有A,B,C三个柱子,其中圆盘都在A柱子上,现在的目的是将A柱子上的圆盘都移到C柱子上,具体题意和要求就如上面的故事所说)
#include<stdio.h>
int sum=0;
void f(int n,char A,char B,char C)
{
if(n==1)
{
sum++; //A柱子上最底层(即最大)的圆盘直接移到C柱子上
}
else
{
f(n-1,A,C,B); //首先将n-1个圆盘从A柱上绕过C柱移到B柱上
sum++; //然后将最大圆盘从A柱上直接移到C柱上
f(n-1,B,A,C); //最后再将n-1个圆盘从B柱上绕过A柱移到C柱上
}
}
int main()
{
int n;
scanf("%d",&n);
f(n,'A','B','C');
printf("%d",sum);
return 0;
}
但其实这道题也可以不用递归来做,只要找到规律,规律如下:
圆盘个数n:1 2 3 4 5 6
移动步数最优解:1 3 7 15 31 63
这样的话我们很容易发现其遵循2^n-1,那么我们就可以写代码了
#include<stdio.h>
#include<math.h>
int main()
{
long long n,sum;
scanf("%lld",&n);
int i;
for(i=1;i<=n;i++)
{
sum=pow(2,i)-1;
}
printf("%lld",sum);
return 0;
}
这里之所以用long long是因为我发现当n较大时,竟然是负数,所以使用长整型来代替整型。虽然这种方法挺笨的,但是它简单呀。
除了这个题以外,我之前实训的时候就遇到过这样一道题,题就是在上面的汉诺塔问题的基础上加了一个条件:不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间柱或从中间移出),意思就是说不能直接从A柱上移动到C柱上或从C柱上直接移到A柱上,必须要通过B柱来移,我将情况都列举了一下
圆盘个数:1 2 3 4 5
移动步数最优解:2 8 26 80 242
可以发现,从第二项起,它的步数都遵循3乘以它的前一项再加上2,这样代码就好写了
#include<stdio.h>
#define N 100
int main()
{
int n,a[N]={0};
scanf("%d",&n);
a[1]=2;
int i;
for(i=2;i<=n;i++)
{
a[i]=3*a[i-1]+2;
}
printf("%d",a[n]);
return 0;
}
在看汉诺塔的题型时,我也发现了两道特别有意思的题,都是基于最基础的汉诺塔问题,对其进行了改编,一个是让我们求到达目标时k号盘需要的最少移动数,而另一个是让我们求第m次移动的盘子的号数,这两个题目恰好相反,也是很有意思了,那么我们首先来看第一个题,我是一个一个去算了一下,毕竟不会的话就要多动手嘛
从小到大盘子的号数依次为 1号盘,2号盘,3号盘……
1.n=3时(即共三个盘子)
第k号盘:1 2 3
最少移动数:4 2 1
2.n=4时
第k号盘:1 2 3 4
最少移动数:8 4 2 1
3.n=5时
第k号盘:1 2 3 4 5
最少移动数:16 8 4 2 1
不难看出,小圆盘总是大一号圆盘移动数的2倍,因此代码如下:
#include<stdio.h>
#define N 100
int main()
{
int n,k,a[N]={0};
scanf("%d%d",&n,&k);
a[n]=1;
int i;
for(i=n;i>k;i--)
{
a[i-1]=2*a[i];
}
printf("%d",a[k]);
return 0;
}
接着就是第二题了,但这个并没有上面那个好想,但是我们完全可以换一个角度想,既然它让我们求第m次移动的盘子的号数,那么我们得先摸清每一号盘子在我们移动第几次时它会移动,我们不妨举个例子,就拿3号盘子来说,它分别是第4,12,20,28......次时会移动so,我们很容易看出来这些数都是4的倍数,而且是奇数倍,而4又可以看成是2^(3-1),所以i号盘子就是2^(i-1),因此代码如下:
#include<stdio.h>
#include<math.h>
#define N 100
int main()
{
int n,m,a[N]={0};
scanf("%d%d",&n,&m);
int i;
for(i=1;i<=n;i++)
{
a[i]=pow(2,i-1);
if((m/a[i])%2==1&&m%a[i]==0)
{
printf("%d",i);
}
}
return 0;
}
这样就真的是简单多了,然而,我这一次只能输入一组数据,要想一次可以输入多组的话,可以参考以下代码:
#include<stdio.h>
#include<math.h>
#define N 100
int main()
{
long long n,m,a[N]={0};
long long i,r;
for(i=1;i<=n;i++)
{
a[i]=pow(2,i-1);
}
while(~scanf("%lld%lld",&n,&m),n+m)
{
for(i=1;i<=n;i++)
{
r=(m/a[i]);
if(r%2==1&&m%a[i]==0)
{
printf("%lld\n",i);
}
}
}
return 0;
}
这是一位大神的代码(以上代码),讲得十分详细,我把思路以及代码敲了一下来给大家分享,在这里也是十分感谢大神。
在这里补充一个小小的知识点:
若是不懂 ~scanf,可以参见(大神在里面有详细的讲解)
https://blog.csdn.net/SiriusSun_/article/details/84575798
关于汉诺塔还有非常非常多的题型,当然大多是由最初始的改编过来的,这些也都只是一些较简单的,有兴趣的话可以去杭电的oj上找一下然后做一些复杂一点的题型。
另外这些大多都是一些思想和方法,具体代码还要依据具体的题而定,比如一些数据类型在不同的题里的使用等,