汉诺塔问题(三柱及四柱)详解

 汉诺塔(Hanoi Tower),又称河内塔,传说大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?

需要求两个问题,一是求所需要的步数,二是求移动过程中每一步的做法步骤

汉诺塔问题-步数

关于步数  是个很简单的问题  高中大家都学过  可能也做过类似的题

如果a上有n个盘子  要借助b柱子将他们移动到c上  那么  我们设总共需要移动步数为F(n)

那么 我们先将 最上面的n-1个盘子   借助c柱子移动到b上  毫无疑问需要步数  F(n-1)

那么 我们再把a上剩下的一个盘子  移动到c上 需要一步  然后再把b上的n-1个借助a移动到c上  需要F(n-1)

到这就完成了所有的移动  总共步数F(n)=2*F(n-1)+1

n=1的时候需要一步  所以 n就需要 2的n次方再减1   (  2^{n}-1  )

#include<stdio.h>
#include<math.h>
int main(){
    int n;
    scanf("%d",&n);
    printf("%d",(int)pow(2,n)-1);
    return 0;
}

这里要注意范围  如果n比较大的话可以用long long,时间上可以用快速幂优化

汉诺塔问题-步骤

关于 步骤,同样的递归思想

我们假设 函数  hanoi ( a , b , c , n )代表  把a上的n个盘子借助b移动到c的步骤

那么  如果n==1  则步骤就是  a->c  一步就好了

n不等于1 的时候  我们先把a上n-1个盘子借助c移动到b上  就是 hanoi ( a , c , b , n-1 )

再把a上最后一个挪到c     a->c

最后再把 b上n-1个盘子借助a挪到c上  hanoi ( b , a , c , n-1 )

整个过程就完成了   主要是要理解递归的思想  理解了思想  递归的程序写起来是最简单的

代码

#include<stdio.h>
void hanoi(char a,char b,char c,int n){
    if(n==1){
        printf("%c->%c ",a,c);
    }
    else{
        hanoi(a,c,b,n-1);
        printf("%c->%c ",a,c);
        hanoi(b,a,c,n-1);
    }
    return ;
}
int main(){
    int n;
    scanf("%d",&n);
    hanoi('A','B','C',n);
    return 0;
}

汉诺塔问题拓展之四柱汉诺塔

在原来的问题上再加一个柱子  其他的条件不变

将a柱上的n个圆盘 移到d柱上  同样大的不能压到小的

我们同样用三柱的方法分析问题

1、我们设将a柱最上边的x个圆盘(1<=x<n)借助b、d两个柱子移动到c上需要F[ x ] 步

2、然后我们再把a柱上剩下的n-x个盘借助b柱移动到d柱上(不能借助c  c上的都小),那么这一步骤和三柱汉诺塔是一样的,我们刚才算过了,是 2^{n-x}-1

3、最后我们再把c上的x个圆盘借助a、b移动到d上    同样需要F[ x ]步

那么 总步数 F[n]=2*F[ x ] + 2^{n-x}-1    这就是我们最终得到的式子

(这里 注意一下这个x的取值范围啊   是  (1<=x<n)    x不能为0  也不能为n  

如果x为0的话,那么相当于我们第一步和第三步没做任何事,只做了第二步,那么岂不是和三柱的情况一样了 ,有一个柱子都没用,肯定不是最优解。

如果x为n的话 那我们第二步没做任何事 第一步和第三步 先借助b、d挪到了c 再借助 a、b挪到了d  那干嘛不一步直接挪到d,所以也肯定不是最优解)

这个式子关键是F[ x ]的取值   F[ 1 ] = 1    F[ 2 ] = 3  这两个我们不用说 很清楚

那么从n=3 开始   我们求解时利用前边已知的F[ x ]  挨个枚举  留下最小值 就是答案了

 

代码

在放代码之前还有个小问题  这里由于需要计算一个2^{n-x}-1 的值  我们在枚举的时候可能会枚举到x等于1 n等于64的情况

那么   2的63次方  我们知道  longlong也存不下  可能有的同学会考虑高精度大数了  其实不用  我们要知道  我们在枚举过程中 是想找到那个最小的数  那么 过大的数字 我们虽然存不下  其实他对我们也没啥用处

而且 事实证明  64以内的所需步数是很小的  64的时候只需要18433步

那么我们只要设定一个最大值 比如是十万  然后 枚举过程中 比这个值小的 我们才更新

这样一来  int就可以解决64以内的了

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long ll;
int f[100];
int INF=100000;//设定最大值
void sv(int n){
    f[1]=1;
    f[2]=3;
    for(int i=3;i<=n;i++){
        f[i]=INF;//f[i]先置为最大值  方便后边比较
        for(int x=1;x<i;x++){
            if( (2*f[x]+pow(2,i-x)-1) <f[i])//小于f[i]的 才更新
                f[i]=2*f[x]+(int)pow(2,i-x)-1;
        }
    }
}
int n;
int main(){
    sv(65);
    while(~scanf("%d",&n)){
        printf("%d\n",f[n]);
    }

}

 

 

 

 

 

 

 

 

 

 

  • 4
    点赞
  • 25
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:精致技术 设计师:CSDN官方博客 返回首页
评论

打赏作者

1900_

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值