zoukankan      html  css  js  c++  java
  • Hanoi Tower 汉诺塔问题

    Hanoi Tower 汉诺塔问题
    汉诺(Hanoi)塔问题:古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上(如图)。有一个和尚想把这64个盘子从A座移到B座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求打印移动的步骤。

    graphic

    其实算法非常简单,当盘子的个数为n时,移动的次数应等于2^n – 1(有兴趣的可以自己证明试试看)。后来一位美国学者发现一种出人意料的简单方法,只要轮流进行两步操作就可以了。首先把三根柱子按顺序排成品字型,把所有的圆盘按从大到小的顺序放在柱子A上,根据圆盘的数量确定柱子的排放顺序:若n为偶数,按顺时针方向依次摆放 A B C;
    若n为奇数,按顺时针方向依次摆放 A C B。
    (1)按顺时针方向把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,则把它移动到C;若圆盘1在柱子C,则把它移动到A。
    (2)接着,把另外两根柱子上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上,当两根柱子都非空时,移动较小的圆盘。这一步没有明确规定移动哪个圆盘,你可能以为会有多种可能性,其实不然,可实施的行动是唯一的。
    (3)反复进行(1)(2)操作,最后就能按规定完成汉诺塔的移动。
    所以结果非常简单,就是按照移动规则向一个方向移动金片:
    如3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C
    汉诺塔问题也是程序设计中的经典递归问题,下面我们将给出递归和非递归的不同实现源代码。

    (一)算法次数:
    H(1)=1,只有一个盘子时只需移一次。
    H(n)=2*H(n-1)+1=2*2*(H(n-2))+2+1=2^k*H(n-k)+2^(k-1)+...+2+1
    当k=n-1时
    H(n)=2^(n-1)+2^(n-2)+...+2+1

    然后求简化的式子,根据如下:
    设S=2^0+2^1+2^2+……+2^n
    则2S=2^1+2^2+……+2^(n+1)
    所以S=2S-S=(2^1+2^2+……+2^(n+1))-(2^0+2^1+2^2+……+2^n)=2^(n+1)-2^0=2^(n+1)-1
    也就是说:
    H(n)=2^n -1

    (二)递归算法:
    以前老师是这样解释:大和尚只移最后一个小盘子,让二和尚移好其他的盘子;然后二和尚也只移一个盘子,让三和尚移好其他盘子,如此类推,每往下一个和尚,问题的规模都变小,到最后的小和尚只需移一个盘子即可。
    参考别人的思路:
    这个问题在盘子比较多的情况下,很难直接写出移动步骤。我们可以先分析盘子比较少的情况。假定盘子从大向小依次为:盘子1,盘子2,...,盘子64。

    如果只有一个盘子,则不需要利用B座,直接将盘子从A移动到C。
    如果有2个盘子,可以先将盘子1上的盘子2移动到B;将盘子1移动到c;将盘子2移动到c。这说明了:可以借助B将2个盘子从A移动到C,当然,也可以借助C将2个盘子从A移动到B。
    如果有3个盘子,那么根据2个盘子的结论,可以借助c将盘子1上的两个盘子从A移动到B;将盘子1从A移动到C,A变成空座;借助A座,将B上的两个盘子移动到C。这说明:可以借助一个空座,将3个盘子从一个座移动到另一个。
    如果有4个盘子,那么首先借助空座C,将盘子1上的三个盘子从A移动到B;将盘子1移动到C,A变成空座;借助空座A,将B座上的三个盘子移动到C。
    上述的思路可以一直扩展到64个盘子的情况:可以借助空座C将盘子1上的63个盘子从A移动到B;将盘子1移动到C,A变成空座;借助空座A,将B座上的63个盘子移动到C。
    归纳成递归公式,可以写成:

    其中,Hanoi函数的第一个参数表示盘子的数量,第二个参数表示源座,第三个参数表示借用的座,第四个参数代表目的座。比如Hanoi(n-1,A,C,B)表示借助C座把n- 1个盘子从A座移动到B座。
    Move函数的第一个参数表示源座,第二个参数代表目的座。Move函数的功能是将源座最上面的一个盘子移动到目的座上。
    根据以上的分析,不难写出程序:

    void Move(char chSour, char chDest)
    {
    /*打印移动步骤*/
    printf("\nMove the top plate of %c to %c",chSour, chDest);
    }
    Hanoi(int n, char chA, char chB, char chC)
    {
    /*检查当前的盘子数量是否为1*/
    if(n==1) /*盘子数量为1,打印结果后,不再继续进行递归*/
    Move(chA,chC);
    else/*盘子数量大于1,继续进行递归过程*/
    {
    Hanoi(n-1,chA,chC,chB);
    Move(chA,chC);
    Hanoi(n-1,chB,chA,chC);
    }
    }
    main()
    {
    int n;
    /*输入盘子的数量*/
    printf("\nPlease input number of the plates: ");
    scanf("%d",&n);
    printf("\nMoving %d plates from A to C:",n); 
     
    /*调用函数计算,并打印输出结果*/
    Hanoi(n,'A','B','C');
    }

    一般会简化成如下:

    #include<stdio.h>
    int count = 0;  //The total movement.
    void hanoi(int n,char A,char B,char C)
    {
        count++;
        if(n==1)
        {
            printf("Move disk %d from %c to %c\n",n,A,C);
        }
        else
        {
            hanoi(n-1,A,C,B);
            printf("Move disk %d from %c to %c\n",n,A,C);
            hanoi(n-1,B,A,C);
        }
    }
    void main()
    {
        int n; 
        printf("请输入数字n以解决n阶汉诺塔问题:\n");
        scanf("%d",&n);
        hanoi(n,'A','B','C');
    } 

    汉诺塔算法的非递归实现C++源代码:

    #include <iostream>
    using namespace std; 
     
    //圆盘的个数最多为64 
    const int MAX = 64; 
     
    //用来表示每根柱子的信息
    struct st{
          int s[MAX]; //柱子上的圆盘存储情况
          int top; //栈顶,用来最上面的圆盘
          char name; //柱子的名字,可以是A,B,C中的一个
          int Top()//取栈顶元素
          {
                return s[top];
          }
          int Pop()//出栈
          {
                return s[top--];
          }
          void Push(int x)//入栈
          {
                s[++top] = x;
          }
    } ; 
     
    long Pow(int x, int y); //计算x^y
    void Creat(st ta[], int n); //给结构数组设置初值
    void Hannuota(st ta[], long max); //移动汉诺塔的主要函数 
     
    int main(void)
    {
          int n;
          cin >> n; //输入圆盘的个数
          st ta[3]; //三根柱子的信息用结构数组存储
          Creat(ta, n); //给结构数组设置初值 
     
          long max = Pow(2, n) - 1;//动的次数应等于2^n - 1
          Hannuota(ta, max);//移动汉诺塔的主要函数 
     
          system("pause");
          return 0;
    } 
     
    void Creat(st ta[], int n)
    {
          ta[0].name = 'A';
          ta[0].top = n-1;
         //把所有的圆盘按从大到小的顺序放在柱子A上
          for (int i=0; i<n; i++)
                ta[0].s[i] = n - i;
          //柱子B,C上开始没有没有圆盘
          ta[1].top = ta[2].top = 0;
          for (int i=0; i<n; i++)
                ta[1].s[i] = ta[2].s[i] = 0;
         //若n为偶数,按顺时针方向依次摆放 A B C
          if (n%2 == 0)
          {
                ta[1].name = 'B';
                ta[2].name = 'C';
          }
          else  //若n为奇数,按顺时针方向依次摆放 A C B
          {
                ta[1].name = 'C';
                ta[2].name = 'B';
          }
    } 
     
    long Pow(int x, int y)
    {
          long sum = 1;
          for (int i=0; i<y; i++)
                sum *= x; 
     
          return sum;
    } 
     
    void Hannuota(st ta[], long max)
    {
      int k = 0; //累计移动的次数
      int i = 0;
      int ch;
      while (k < max)
      {
        //按顺时针方向把圆盘1从现在的柱子移动到下一根柱子
        ch = ta[i%3].Pop();
       ta[(i+1)%3].Push(ch);
       cout << ++k << ": " <<
             "Move disk " << ch << " from " << ta[i%3].name <<
             " to " << ta[(i+1)%3].name << endl;
       i++;
       //把另外两根柱子上可以移动的圆盘移动到新的柱子上
       if (k < max)
       {     
        //把非空柱子上的圆盘移动到空柱子上,当两根柱子都为空时,移动较小的圆盘
        if (ta[(i+1)%3].Top() == 0 ||
            ta[(i-1)%3].Top() > 0 &&
            ta[(i+1)%3].Top() > ta[(i-1)%3].Top())
       {
            ch =  ta[(i-1)%3].Pop();
            ta[(i+1)%3].Push(ch);
            cout << ++k << ": " << "Move disk "
                 << ch << " from " << ta[(i-1)%3].name
                 << " to " << ta[(i+1)%3].name << endl;
        }
        else
        {
           ch =  ta[(i+1)%3].Pop();
           ta[(i-1)%3].Push(ch);
           cout << ++k << ": " << "Move disk "
                << ch << " from " << ta[(i+1)%3].name
                << " to " << ta[(i-1)%3].name << endl;
        }
     }
    }
    } 
  • 相关阅读:
    ztree学习---将默认勾选的展开
    CentOS之RPM
    CentOS之文档的压缩与打包
    CentOS之Vim
    CentOS用户和用户组管理
    Linux CentOS更改文件的权限
    CentOS的文件属性:命令 ls -l
    CentOS命令
    Java解析excel
    easyUI的combotree的树的懒加载。
  • 原文地址:https://www.cnblogs.com/lionfight/p/2512057.html
Copyright © 2011-2022 走看看