汉诺塔问题——分治思想

汉诺塔规则如下:

1、有三根相邻的柱子,标号为x,y,z。

2、x柱子上从下到上按金字塔状叠放着n个不同大小的圆盘。

3、现在把所有盘子一个一个移动到柱子z上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方。

目录:

1.代码效果预览

2.分治思想剖析汉诺塔原理

3.汉诺塔常见疑惑剖析

4.代码及其解析,关于头部代码的理解误区

1.代码效果预览

 

2.分治思想剖析汉诺塔原理

首先汉诺塔可以分为两种情况,1层和n层。

一层就直接将盘子从x移到z,不在多做解释。

那么n层怎么解决呢?

首先明确我们的目的是什么?

把x上的盘子全部移到到z上面,且不能让大的压在小的上面。

那么第一步是什么?

 那么怎么移动这n-1层呢?

这个先不管,假设现在我们已经成功移动了这n-1层。

那么接下就是将第n层移动到z上面。

 好了这里已经成将第n层移动到z上了,那么我们回顾一下规则,不能将大盘子压在小盘子上面,现在我们引入一个概念,目标柱,工具柱,初始柱。

刚开始时盘子全部在x上面,那么x即为初始柱,此时我们希望将盘子全部移动到z上面,那么z即为目标柱,而在此过程中,我们借助了y,则y即为工具柱。

当最大的盘子移动到目标柱时,我们不管移动任何盘子到目标柱,都不会犯规了,因为,没有任何盘子大于它,不存在将大盘子压在小盘子上面的说法,因为它就是最大的。

所以此刻,我们可以等价看作,目标柱是空的,问题的规模从n降阶为了n-1,因为那个最大的盘子已经对接下来的操作产生不了任何影响了。

即如图:

 

 

好了,现在的状况是不是感觉有些相似?

那么现在我们要做什么呢?

 当完成上述操作后,状态如图:

 那么接下来就如同回到了初始状态,对比一下最开始的状态。

 

是不是除了问题规模减小了之外,没有任何区别?

这就是分治法的思想,将规模很大的问题,分割成为一个个类似的小的子问题。

好了理解了原理之后,我们回到刚开始遗留的问题上面,我们要把第n层移动到目标柱上面,就要先把,第n-1层全部先移到工具柱上面。

那么这n-1层是如何移动的呢?

 

3.汉诺塔常见疑惑剖析

我们在将前n-m层移动到工具柱的这整个过程中,还剩下m层的初始柱起到了什么作用,充当了什么角色?

当我们在移动这n-m层时,初始柱剩下的这m层都是最大的,前面的n-m层里面并没有比这m层的柱子里面任意一层盘子大的,所以可以等价于这个初始柱是空的,就像前面所讲的将第n层移动到目标柱后,这个第n层不会再对后续操作产生任何影响,所以可以把此时的目标柱看作是空的一样,是同一个道理。所以在这个过程中,可以把这个不会产生影响的初始柱当作,空的工具柱来使用。

4.代码及其解析,关于头部代码的理解误区

解析不动,代码先行。

/****************************************************
    > File Name:     Hanoi
    > Author:        唯恒
    > Mail:          2279811789@qq.com
    > Created Time:  2022年10月24日
*******************************************************/

#include <iostream>
#include<cstdlib>
#include<Windows.h>
using namespace std;

class Hanoi {
public:
    const int number;//汉诺塔层数
    char **pillar;//模拟xyz三个柱子
    void visit();//观看三个柱子
    int find_top(int row);//查找当前列的顶部不为空格的元素的层数
    void move(char X, char Z);//将顶部元素从x柱移到z柱
    void hanoi(int above, char X, char Y, char Z);//圆盘从x借助y移到z
    Hanoi(int number);//构建对象时确定汉诺塔的个数
};

Hanoi::Hanoi(int numbers):number(numbers)
{
    //为柱子分配内存
    pillar = new char*[number];

    for (int i = 0;i < number;i++)
    {
        pillar[i] = new char[3];
    }

    //将三个柱子初始化
    for (int i = 0;i < number;i++)
    {
        for (int j = 0;j < 3;j++)
        {
            if (j == 0)//如果是0号柱子则初始化为1,2,3,4,......
            {
                pillar[i][0] = '1' + i;
            }
            else//否则初始化为空格
            {
                pillar[i][j] =' ';
            } 
        } 
    }
    visit();//初始化汉诺塔后查验一下
}

void Hanoi::visit()
{
    static int count = 0;//记录下移动了几次
    cout << "当前移动了: "<<count <<"次状态为:"  << endl;
    count++;//每次观看都等于移动了一次故而+1

    //输出pillar数组里的每个元素
    for (int i = 0;i < number;i++)
    {
        for (int j = 0;j < 3;j++)
        {
            cout << " " << pillar[i][j];
        }
        cout << endl;
    }
}

int Hanoi::find_top(int row)
{
    for (int i = 0;i < number;i++)
    {
        if (pillar[i][row] != ' ')
        {
            return i ;
        }
    }

    //当查找完了还没有找到不是空格的元素,则说明这个柱子还没有盘子存在,返回柱子的长度
    return number ;
}

void Hanoi::move( char X,char Z)
{ 
    //等待1秒后清屏,使人可以观看清楚
    Sleep(1000);
    system("cls");
    //x,z是用来确定当前是第几根柱子的,减去的88是‘X’的ASCII的值
    int x = X - 88;
    int z = Z - 88;
    //记录顶部第一个部位空格的元素的层数
    int x_top = find_top(x);
    int z_top = find_top(z);
    //将初始柱的顶部元素,移动到目标柱的顶部
    pillar[z_top - 1][z]=pillar[x_top][x];
    //移动后将目标柱的顶部第一个不为空格的元素置为空格
    pillar[x_top][x] = ' '; 
    //移动后查看当前汉诺塔的情况
    visit();
    //2是目标柱Z的序号当目标柱满了说明汉诺塔满了进行返回
    if (find_top(2) == 0)
    {
        //汉诺塔的最是移动次数是2^n-1,这里进行输出,做一个提升和验证
        cout << "预计移动:" << "2^" << number << "-1次,移动次数符合预期" << endl;
        return;
    }
}

void Hanoi::hanoi(int above, char X, char Y, char Z)//把x的元素借助y移到z
{
    if (above == 0)//因为是数组从零开始
    {
        move( X, Z);//把数据从初始柱移到目标柱
    }
    else
    {
        hanoi(above - 1, X, Z, Y);//把x的元素借助z移到y
        move( X,Z);//把x的元素移到z
        hanoi(above - 1, Y, X,Z);//把y的元素借助x移到z
    }
}

int main()
{
    int n = 10;
    while (n > 9)
    {
        cout << "请输入汉诺塔的层数(数量请勿超过9,否则难以显示):";//不能超过9是因为,每个柱子输出时,间隔一个空格
        cin >> n;                                                 //二位数,会导致数字连着不利于观看,并且我定义的                                   
    }                                                             //是字符数组,只有1—9的ASCII,二位数没有ASCII
     Hanoi h(n);
    h.hanoi(n-1, 'X', 'Y', 'Z');
    return 0;
}


其它部分的代码,注释已经很清晰了,便不在多做解释,这里只讲解,最主要的部分。

关于头部代码的理解误区: 

当above==0时,每次都是把,X的数据移动到Z上面,那么岂不是会把大的盘子全部压在小的上面?而且Y去哪里了?怎么没有用到Y?

注意这里的XYZ是char类型;那么举个例子:

当汉诺塔为两层时:

第一步:进入程序hanoi(1,X,Y,Z);(传1的原因是这里的柱子是数组,下标从0开始)

第二步:above!=0,进入hanoi(0,X,Z,Y);

第三步:above==0,进入注意了,第二步时传入的是此时第三步里的Z,实际上等于柱子Y。

主代码解析:

1

当above==0,便不作解释了,当above!=0时

这一步即为,将above-1层全部移动到Z上。

即前面的这幅图:

 2.

这一步即为将x上最大的那个盘子移动到目标柱上。

即前面的这幅图:

 3.

 即前面的这幅图:

4.当前面的

 hanoi()内部的递归执行情况,便类似于前面的这幅图:

如果还是没有理解,也没有关系,后续我会在b站上传该代码的讲解视频,并在下方附上链接。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
汉诺塔是一个经典的数学问题,它由三根柱子和一些不同大小的圆盘组成。开始时,所有的圆盘都按照从大到小的顺序堆叠在一根柱子上,目标是将所有的圆盘从起始柱子移动到目标柱子上,同时遵守以下规则: 1. 每次只能移动一个圆盘; 2. 大圆盘不能放在小圆盘上面。 汉诺塔问题可以通过分治法来解决。分治法是一种将问题分解成更小的子问题,并通过解决子问题来解决原始问题的方法。 以下是汉诺塔问题的递归解法的代码示例(使用C++语言): ```cpp #include <iostream> using namespace std; void hanoi(int n, char from, char to, char aux) { if (n == 1) { cout << "Move disk 1 from " << from << " to " << to << endl; return; } hanoi(n - 1, from, aux, to); cout << "Move disk " << n << " from " << from << " to " << to << endl; hanoi(n - 1, aux, to, from); } int main() { int num_disks; cout << "Enter the number of disks: "; cin >> num_disks; hanoi(num_disks, 'A', 'C', 'B'); return 0; } ``` 这段代码中,`hanoi`函数用于解决汉诺塔问题。它接受四个参数:圆盘的数量 `n`,起始柱子 `from`,目标柱子 `to`,辅助柱子 `aux`。当 `n` 等于 1 时,直接将圆盘从起始柱子移动到目标柱子。否则,先将 `n-1` 个圆盘从起始柱子移动到辅助柱子,然后将最大的圆盘从起始柱子移动到目标柱子,最后将剩余的 `n-1` 个圆盘从辅助柱子移动到目标柱子。 请注意,这只是汉诺塔问题的一种解法,还有其他解法可以使用迭代等方法实现。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值