算法2:Hanoi塔

汉诺(Hanoi)塔

一、背景介绍

在印度,有这么一个古老的传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片,一次只移动一片,不管在哪根针上,小片必在大片上面。当所有的金片都从梵天穿好的那根针上移到另外一概针上时,世界就将在一声霹雳中消灭,梵塔、庙宇和众生都将同归于尽。


二、数学定义

从左到右有三根柱子A、B、C,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数。

我们记移动总次数为sum。

不难发现,

当n==1时,sum=1,

第一次:A→C;

当n==2时,sum=3,

第一次:A→B,

第二次:A→C,

第三次:B→C

当n==3时,sum=7;

​ 第一次: A→C,
​ 第二次: A→B,
​ 第三次: C→B,
​ 第四次 :A→C,
​ 第五次 :B→A,
​ 第六次 :B→C,
​ 第七次: A→C;

因此,

不难发现规律:

​ 1个圆盘的次数 2的1次方减1
​ 2个圆盘的次数 2的2次方减1
​ 3个圆盘的次数 2的3次方减1
……
​ n个圆盘的次数 2的n次方减1

故:移动次数为:2^n - 1。


三、递归算法

void hanoi(int n, int a, int b, int  c) {
    if (n > 0) {
        hanoi(n - 1, a, c, b);
        move(a, b);
        hanoi(n - 1, c, b, a);
    }
}

其中,hanoi(n, a, b, c)表示将塔座A上自下而上,由大到小叠放在一起的n个圆盘依移动规则移至塔座B上并按照同样的顺序叠放。在移动过程中,以塔座C作为辅助塔座。move(a, b)表示将塔座A上编号为n的圆盘移到塔座B上。

实现这个算法可以简单分为三个步骤:

(1) 把n-1个盘子由A 移到 B;
(2) 把第n个盘子由 A移到 C;
(3) 把n-1个盘子由B 移到 C;

从这里入手,在加上上面数学问题解法的分析,我们不难发现,移到的步数必定为奇数步:

(1)中间的一步是把最大的一个盘子由A移到C上去;
(2)中间一步之上可以看成把A上n-1个盘子通过借助辅助塔(C塔)移到了B上,
(3)中间一步之下可以看成把B上n-1个盘子通过借助辅助塔(A塔)移到了C上;


四、完整代码

#include<iostream>
#include<math.h>
using namespace std;

void hanoi(int n, char A, char B, char C) {
    if (n == 1)
        cout << "将圆盘" << n << "" << A << "移动到" << C << endl;
    else {
        hanoi(n - 1, A, C, B);
        cout<< "将圆盘" << n << "" << A << "移动到" << C << endl;
        hanoi(n - 1, B, A, C);
    }
}

int main() {
    int n;
    cout << "请输入A上圆盘的个数n:";
    cin >> n;
    cout << endl;
    hanoi(n, 'A', 'B', 'C');
    cout << endl;
    int sum = pow(2, n) - 1;
    printf("移动的总次数sum为:%d",sum);
    return 0;
}

五、非递归(拓展)

该方法设计了一个数据结构 struct act {int flag,num; char x, y, z;} S[2000]; 存储当前操作信息,其中flag==0表示直接移动num号圆盘,否则需要进一步分解;num表示当前操作移动盘子的编号,x,y,z表示当前操作对应的3个塔柱,分别是出发柱,中点柱和目标柱。

void Hanoi_1(int n, char a, char b, char c)//非递归算法1
{
    struct act { int flag, num;  char x, y, z; } S[2000]; //存储当前操作信息,flag==0表示直接移动num号圆盘,否则需要进一步分解 
    int top, m;
    char ta, tb, tc;

    S[0].flag = 1;//初值入栈 
    S[0].num = n;
    S[0].x = a;
    S[0].y = b;
    S[0].z = c;
    top = 0;
    int count = 0;
    while (top >= 0)
    {
        if (S[top].flag == 0 || S[top].num == 1)//直接将num号圆盘从x移动到z
        {
            printf("%d: %d %c -> %c   ", ++count, S[top].num, S[top].x, S[top].z);
            --top;
        }
        else
        {  //提取栈顶信息 
            m = S[top].num;
            ta = S[top].x;
            tb = S[top].y;
            tc = S[top].z;
            //将 Hanoi(n-1, b, a, c); 操作入栈 ,覆盖原栈顶信息 
            S[top].num = m - 1;
            S[top].x = tb;
            S[top].y = ta;
            S[top++].z = tc;
            //将 将第m个盘从a柱移动到c柱 操作入栈
            S[top].flag = 0;
            S[top].num = m;
            S[top].x = ta;
            S[top++].z = tc;
            //将 Hanoi(n-1, a, c, b); 操作入栈
            S[top].flag = 1;
            S[top].num = m - 1;
            S[top].x = ta;
            S[top].y = tc;
            S[top].z = tb;
        }
    }
}

以上非递归算法利用栈模拟递归过程的基本方法。对于有n个盘子的汉诺塔问题,需要操作的步骤为2^n – 1,如果每一个步骤看成一个节点,则刚好构成一棵满二叉树,树高h与盘子数量的关系为h==n。结点所在的层数与对应盘子的编号关系为level==n+1-level,即盘子1在第n层,盘子n在第1层;若某个结点的操作为“盘子n从A->C”,则其左孩子操作为“盘子n-1从A->B”,右孩子操作为“盘子n-1从B->C”;中序遍历满二叉树,结点的编号恰好对应移动的次序。

因此我们可以构造一棵满二叉树,然后中序遍历该二叉树。

void Hanoi_3(int n, char a, char b, char c)
{
    struct act { int num;  char x, y, z; } BT[132000]; 
    //存储每一步操作的满二叉树
    int S[MAX] = { 0 };
    int i, top, count = 0;

    BT[1].num = n;//为根结点赋值 
    BT[1].x = a;
    BT[1].y = b;
    BT[1].z = c;
    n = pow(2, n - 1);
    for (i = 1; i < n; i++)//为每个节点的左右孩子赋值 
    {
        BT[i + i].num = BT[i + i + 1].num = BT[i].num - 1;
        BT[i + i].x = BT[i + i + 1].y = BT[i].x;
        BT[i + i].z = BT[i + i + 1].x = BT[i].y;
        BT[i + i].y = BT[i + i + 1].z = BT[i].z;
    }

    //中序遍历满二叉树 
    n += n;
    i = 1;
    top = -1;
    while (i < n || top >= 0)
    {
        if (i < n)//入栈,并搜索左孩子
        {
            S[++top] = i;
            i += i;
        }
        else//输出并退栈,搜索右孩子
        {
            i = S[top--];
            printf("%d: %d %c -> %c   ", ++count, BT[i].num, BT[i].x, BT[i].z);
            i += i + 1;
        }
    }
}

鸣谢:http://blog.csdn.net/qiaoruozhuo

 

posted @ 2022-11-22 10:28  诩言Wan  阅读(361)  评论(0编辑  收藏  举报