算法学习(4)----汉诺塔递归算法和非递归算法

  学习《算法设计与分析基础》,习题2.4 第5题要求为汉诺塔游戏设计一个非递归的算法。

  思,不得其解。看书后答案提示:

你如果做不到,也不要沮丧:这个问题的非递归算法虽然不复杂,但却不容易发现。作为一种安慰,可以在因特网上寻找答案。

  好吧,话都说得这么直接了,遂百度之,得到一个感觉很好的答案,略做修改,摘录于下:

原文地址:http://blog.sina.com.cn/s/blog_48e3f9cd01000474.html

#################################################################################

#################################################################################

在版上看有人讨论汉诺塔的非递归算法,有人介绍怎么样非递归,自己想了半天,总算想明白了。整理了下方便大家:

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

递归算法:

定义 void Hanoi(char src, char des, char via, int n)
表示把n个盘子从src上借助via移动到des上。
显然有

     void Hanoi(char src, char des, char via, int n)
      {
          Hanoi(src, via, des, n - 1);
          Move(src, des, n); //把第n个盘子直接从src移动到des
          Hanoi(via,des, src, n - 1);
      }

根据递归算法,设f(n)为n个盘子要移动的次数。
那么显然 :

f(n + 1) = 2*f(n) + 1  ->  [f(n + 1) + 1] = 2*[f(n) + 1]
f(1) = 1,-> f(n) + 1 = (1 + 1)^n -> f(n) = 2^n - 1。
f(64)= 2^64-1=18446744073709551615   

假如每秒钟一次,共需多长时间呢?一年大约有 31536926 秒,计算表明移完这些金片需要5800多亿年,比地球寿命还要长,事实上,世界、梵塔、

庙宇和众生都已经灰飞烟灭。

非递归算法:
定义从小到大的盘子序号分别为1,2,……n。
可以用一个1到2^n - 1的2进制序列可以模拟出n个盘子的汉诺塔过程中被移动的盘子的序号序列。

即给定一个n,我们通过0到2^n - 1序列可以判断出任意一步应该移动那个盘子。
判断方法:第m步移动的盘子序号是m用二进制表示的最低位bit为1的位置。

证明: n = 1,显然成立。
假设n = k 成立。
n = k + 1时,对应序列1到2^(k+1) - 1,显然这个序列关于2^k左右对称。
假设我们要把k + 1个盘子从A移动C。
那么2^k可以对应着Move(k + 1, A, C)。 1 到 2^k - 1 根据假设可以
对应Hanoi(A, B, C, k)。至于2^k + 1 到 2^(k + 1) - 1把最高位的1去掉对应序列变成1到2^k - 1,显然2^k + 1 到 2^(k + 1) - 1和1到2^k - 1这两个序列中的对应元素的最低位bit为1的位置相同。因此2^k + 1 到 2^(k + 1) - 1可以对应Hanoi(B, C,A,k)。
所以对n = k + 1也成立。

下面讨论第m步应该移动对应的盘子从哪到哪?
定义顺序为 A->B->C->A, 逆序为C->B->A->C。

性质对n个盘子的汉诺塔,任意一个盘子k(k <= n)k在整个汉诺塔的移动过程中要么一直顺序的,要么一直逆序的。而且如果k在n个盘子移动过程的顺序和k - 1(如果k > 1)以及k + 1(如果k < n)的顺序是反序。

比如:n = 3

1 A->C
2 A->B
1 C->B
3 A->C
1 B->A
2 B->C
1 A->C

其中1的轨迹A->C->B->A>C逆序,2的轨迹A->B->C顺序,3的轨迹A->C逆序
     
证明:假设n <= k成立
对于n = k + 1 根据递归算法
Hanoi(A,C,B,k + 1) = Hanoi(A, B, C, k) + Move(A, C, k + 1) + Hanoi(B, C,A,k);

整个过程中盘子k + 1只移动一次A->C为逆序对应着2^k。

对于任意盘子m < k + 1,
m盘子的移动由两部分组成一部分是前半部分Hanoi(A, B, C, k)以及后半部分的Hanoi(B, C,A,k)组成。显然有如果m在Hanoi(A, C, B, k)轨迹顺序的话,则m在Hanoi(A, B, C, k)以及Hanoi(B, C,A,k)都是逆序。反之亦然。这两部分衔接起来就会证明m在Hanoi(A,C,B,k)和Hanoi(A,C,B,k + 1)中是反序的。
同时有Hanoi塔中最大的盘子永远是逆序且只移动1步,A->C。
这样的话:

m = k + 1,在Hanoi(A,C,B,k + 1)中是逆序。
m = k,由于在Hanoi(A,C,B,k)中是逆序的,所以Hanoi(A,C,B,k + 1)中是顺序的。
m = k - 1,由于在Hanoi(A,C,B,k - 1)是逆序的,所以Hanoi(A,C,B,k)是顺序的,所以Hanoi(A,C,B,k + 1)是逆序的。
依次下去……
结论得证。

总结:在n个汉诺中n, n - 2, n - 4……是逆序移动,n - 1, n - 3,n - 5……是顺序移动。

有了以上结论,非递归的程序就很好写了。写了个递归和非递归比较程序:

#include <iostream>
using namespace std;
void Hanoi(char src, char des, char via, int n)
{
 if(n == 1)
 {
  cout << n <<" : "<< src <<" --> " <<des << endl;
  return;
 }
 Hanoi(src, via, des, n - 1);
 cout << n <<" : "<< src <<" --> " <<des << endl;
 Hanoi(via, des, src, n - 1);
}

int main()
{
 int n;
 cin >> n;
    cout<<"recusive:"<< endl;
 Hanoi(‘A‘,‘C‘,‘B‘, n);
 cout << endl;
 cout<<"normal:"<<endl;
    char order[2][256];
 char pos[64];
for(int i=0;i<64;i++)
{
       pos[i]=‘A‘;    //初始的时候,所有的圆盘位置都是 ‘A‘;
}
 order[0][‘A‘] = ‘B‘;
 order[0][‘B‘] = ‘C‘;
 order[0][‘C‘] = ‘A‘;
 order[1][‘A‘] = ‘C‘;
 order[1][‘B‘] = ‘A‘;
 order[1][‘C‘] = ‘B‘;
 //0是顺序 1是逆序
 int index[64];
 //确定轨迹的顺序还是逆序
 int i, j, m;
    for(i = n; i > 0; i -= 2)
   index[i] = 1;
 for(i = n - 1; i > 0; i -= 2)
   index[i] = 0;
    memset(pos, ‘A‘, sizeof(pos));
 for(i = 1; i < (1 << n); i ++)
    {
    for(m = 1, j = i; j%2 == 0; j/=2, m ++);  //计算出当前步骤序号的最低的 bit 为 1 的位置。
    cout << m <<" : "<< pos[m]  <<" --> " << order[index[m]][pos[m]] << endl;
    pos[m] = order[index[m]][pos[m]]; //更改当前位置
    }
 return 0;
}

感叹原作者提出的算法真是精妙。

另外补充:关于计算一个整数 bit 为 1 的最低位的问题,可以如下计算:

int lowestbit(int n)
{
    int tmp=n-1;
    tmp=tmp^n; //假设n 的最低bit 为 1 的位为m,则此时 tmp 为低 m 位都为 1 、剩余高位都为 0 的数字。
    tmp+=1;    //此时 tmp= pow(2,m);
    return log2(tmp);
}
时间: 2024-12-11 17:20:46

算法学习(4)----汉诺塔递归算法和非递归算法的相关文章

算法学习(二) 全排列问题的非递归算法——模拟堆栈

前一段时间总结了全排列问题的几种递归解法,今天再总结一下如何通过对系统栈行为的模拟来非递归的实现全排列问题. 我们用一个数组stack[]来表示一个栈,用一个top指针来表示栈顶,用一个flags[]数组来标示每一个数字的可用性:用i来表示当前的状态. 初始状态top=0:i=-1:flags数组全为1: i递增,如果i没有越界并且flags[i]==1,那么就将i写入栈中,栈顶往前移动一位:最后把flags[i]赋值为0,i回溯到初始状态-1: 当栈顶越界,就将整个栈的信息打印出来,然后top

C语言之算法初步(汉诺塔--递归算法)

个人觉得汉诺塔这个递归算法比电子老鼠的难了一些,不过一旦理解了也还是可以的,其实网上也有很多代码,可以直接参考.记得大一开始时就做过汉诺塔的习题,但是那时代码写得很长很长,也是不理解递归的结果.现在想起来汉诺塔的算法就3个步骤:第一,把a上的n-1个盘通过c移动到b.第二,把a上的最下面的盘移到c.第三,因为n-1个盘全在b上了,所以把b当做a重复以上步骤就好了.所以算法看起来就简单多了.不过,思考过程还是很痛苦的,难以理解.递归中会保存数据的好处在这里又得到体现,太神奇了. 汉诺塔代码如下:

JavaScript算法实现之汉诺塔(Hanoi)

目前前端新手,看到的不喜勿喷,还望大神指教. 随着Node.js,Angular.js,JQuery的流行,点燃了我学习JavaScript的热情!以后打算每天早上跟晚上抽2小时左右时间将经典的算法都用JS来实现,加快学习JS的步伐(用这个办法方便跟自己以前学过的C++语言作对比,找出不同),希望自己能够坚持下去!!! 首先来个汉诺塔的. <script>      function hanoi(n,a,b,c){          if(n==1){              documen

算法笔记_013:汉诺塔问题(Java递归法和非递归法)

目录 1 问题描述 2 解决方案  2.1 递归法 2.2 非递归法 1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus is possible for using animation. e.g. if n = 2 ; A→B ; A→C ; B→C; if n = 3; A→C ; A→B ; C→B ; A→C ; B→A ; B→C ; A→C; 翻译:模拟汉诺塔问题的移动规则:获得奖励的移动方法还是有可能的.

zs深入浅出学算法022——DFS———汉诺塔问题II

Description 汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上.并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘. Input 输入圆盘数n( 1 <= n <= 10) Output 按照示例输出搬盘子的过程,每次搬动输出一行 Sample Input 2 Sample Output a->b

【数据结构与算法】递归汉诺塔

汉诺塔 汉诺塔是根据一个传说形成的数学问题(关于汉诺塔): 有三根杆子A,B,C.A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小.要求按下列规则将所有圆盘移至C杆: 每次只能移动一个圆盘: 大盘不能叠在小盘上面. 提示:可将圆盘临时置于B杆,也可将从A杆移出的圆盘重新移回A杆,但都必须遵循上述两条规则. 递归汉诺塔 解题思路: 可以把问题简化成2个盘子的情况,如:A上有两个盘子,B和C是空的.如果要把A的两个盘子全部移动到C,需要经过以下步骤: 1.A移动一个盘子到B 2.A移动一个盘

面试题 08.06. 汉诺塔问题(非递归实现汉诺塔问题)

题目: 在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子.一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面).移动圆盘时受到以下限制:(1) 每次只能移动一个盘子;(2) 盘子只能从柱子顶端滑出移到下一根柱子;(3) 盘子只能叠在比它大的盘子上. 请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子. 你需要原地修改栈. 示例1: 输入:A = [2, 1, 0], B = [], C = [] 输出:C = [2,

算法:汉诺塔

[递归经典题目]汉诺塔算法 Java实现 汉诺塔非递归算法

汉诺塔的递归算法

汉诺塔是怎样通过递归算法实现的? 这个问题困扰了我一段时间,今天回过头来想想似乎明白了,因此在这里记录下自己想法. 首先贴上在Python上的代码: 1 # -*- coding: utf-8 -*- 2 3 def move(n,a,b,c): 4 if n == 1: 5 print(a+"-->"+c) 6 if n > 1: 7 move(n-1,a,c,b) 8 print(a+"-->"+c) 9 move(n-1,b,a,c) 10