当前位置:首页 » 《关于电脑》 » 正文

汉诺塔问题详解及扩展(c++)

1 人参与  2024年11月12日 11:22  分类 : 《关于电脑》  评论

点击全文阅读


汉诺塔(Hanoi Tower)问题是一个著名的数学问题,它涉及到递归算法。问题的背景来源于一个传说:在印度的一个寺庙里,有三根金刚石柱和64个直径大小不一的金盘。僧侣们被命令将这些金盘从一根柱子按照从小到大的顺序移动到另一根柱子上,但必须遵守以下规则:

每次只能移动一个金盘。每个金盘只能放在柱子上,不能放在其他地方。任何时候,大盘不能放在小盘上面

假设现在我们有3根柱子,第一根上从下往上放着3,2,1三个不同大小的盘子 ,它大体的移动步骤是这样的:

首先我们先看要移动的1,2圆盘,目光聚集在1,2圆盘,3圆盘暂时不需要移动;

1:先将a桩上1,2圆盘中1圆盘移动到空柱c;

2:将剩下2圆盘移动到空柱b;

3:再将c柱上1圆盘移动到b柱上;

这是1,2圆盘具体的移动过程;

 

之后 我们审视全局,目光聚集在全部的圆盘上,将1,2圆盘看作一个整体,暂时不考虑它的具体移动过程,(具体移动过程就在上面)

1:先将a柱上1,2圆盘放在空柱c上,

2:再将3圆盘放在空柱b上;

3:再将c上1,2圆盘放在b上的3上

 

对比以下上面的图,有没有发现两个移动过程是不是几乎一样,可以看作第二图中嵌套着第一个图的过程

如果有n个盘子,那么我们要做的是先将n-1个盘子移动到c,然后将最后一个盘子也就是第n个盘子移动到b,再把c上的n-1个盘子移动到b,其中n-1个盘子的移动过程就是将n-2个盘子移动到c,然后将第n-1个盘子移动到b 再将c柱上n-2个盘子移动到b,然后其中第n-2个盘子的移动过程是先将n-3个盘子移动到。。。。。

可以发现这整个其实就是一层套一层,其中的移动过程是重复的,因此我们可以用递归来解决;

代码:

#include<iostream>

#include<stack>
using namespace std;
stack<int>s[3];//每个盘子是先进后出,类似于栈,因此我们建立三个栈,相当于三根柱子;

void move(int a, int b) {//移动函数,将a柱子上的一个盘子移动到b柱子上
    int tmp = s[a].top();//取出a柱子上的栈顶元素,存在tmp临时变量中
    s[a].pop();//a柱子取出该元素,类似于把a柱子顶部的一个盘子拿走
    s[b].push(tmp);//将tmp存入b柱中,类似于把盘子放在b的顶部
    cout << a << "-->" << b<<endl;//输出这一步的移动过程
}
void hnt(int a, int b, int c, int n) {//汉诺塔函数的四个参数依次是//起始柱子,经过柱子,目标柱子,每次移动的盘子数目
    if (n == 1) {//如果移动的盘子数为1了,即最后移动的盘子了,就把a中最后剩下的一个盘子移动到c柱子上
        move(a, c);
        return;
    }
    hnt(a, c, b, n - 1);//先将a柱子上的n-1个盘子经过c柱子移动到b柱子上
    move(a, c);//将a中剩下的一个盘子移动到c柱子上
    hnt(b, a, c, n - 1);//再将b柱子上n-1个盘子经过a移动c柱子上
}
int main() {
    int n;
    cin >> n;//输入盘子数n
    for (int i = n; i >= 1; i--)s[0].push(i);//先初始化0柱子,从大盘子到小盘子开始入栈
    hnt(0, 1, 2, n);//将n个圆盘从0柱子上经过1柱子移动到2柱子
    while (!s[2].empty()) {//输出最后2柱子上的元素,即2柱子上的盘子
        cout << s[2].top();
        s[2].pop();
    }
    return 0;
}

 尝试结果:

可以看到3个盘子时具体移动过程。


 

扩展:我们怎么求得整个过程移动的步数呢?3个盘子,我们可以通过数出来,如果是10,100个盘子呢?当然你也可以在函数move中增加一个变量记录移动的步数,但是, 这是递归,随着递归层次的不断增大,会导致栈溢出的问题,我在这里试了一下100个盘子,结果是运行了数十秒也得不到结果,一直在跑

因此我们要找另外的方法去计算这个步数;

通过模拟整个过程,我们可以发现

每一次移动的盘子数目其实是上一次移动盘子的数目乘2再加1

f(1)=1;

f(n)=f(n-1)+1+f(n-1)//类似于a柱子上n个盘子的步数等于将n-1个盘子移动到c柱的步数,加上a上最后一个盘子移动到b这一步,再加上c柱子上n-1个盘子移动到b柱子上的步数

因此我们可以通过循环写出代码计算步数

int main() {
    long long f[60];
    int n;
    cin >> n;
    f[1] = 1;
    for (int i = 2; i <= n; i++) {
        f[i] = 2 * f[i-1] + 1;
    }
    printf("%lld", f[n]);

现在我们可以试一下100盘子要移动多少步数

 

出错的原因呢是因为数据太大了,longlong都装不下了,longlong最大值是2^63-1大概20位数左右,我们试一下60

 可以看到有多少步吧。。。。60个盘子都已经差不多20位的步数了,怪不得之前跑100一直都计算不出结果;


点击全文阅读


本文链接:http://zhangshiyu.com/post/184972.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1