汉诺塔基础

不得不说,杭电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上找一下然后做一些复杂一点的题型。

另外这些大多都是一些思想和方法,具体代码还要依据具体的题而定,比如一些数据类型在不同的题里的使用等,


 

 

 

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值