汉诺(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

算法2:Hanoi塔的更多相关文章

  1. 算法训练 Hanoi问题

      算法训练 Hanoi问题   时间限制:1.0s   内存限制:512.0MB      问题描述 如果将课本上的Hanoi塔问题稍做修改:仍然是给定N只盘子,3根柱子,但是允许每次最多移动相邻的 ...

  2. 栈与递归的实现(Hanoi塔问题等等)

    函数中有直接或间接地调用自身函数的语句,这样的函数称为递归函数.递归函数用 得好,可简化编程工作.但函数自己调用自己,有可能造成死循环.为了避免死循环,要 做到两点: (1) 降阶.递归函数虽然调用自 ...

  3. 经典递归算法研究:hanoi塔的理解与实现

    关于hanoi塔的原理以及概念,请Google,访问不了去百度. 主要设计到C中程序设计中递归的实现: 主代码实现如下: void hanoi(int src, int dest, int tmp, ...

  4. (转)Hanoi塔问题分析

    转自:http://shmilyaw-hotmail-com.iteye.com/blog/2077098 简介 关于Hanoi塔问题的分析,在网上的文章都写烂了.之所以打算写这篇文章,更多的是针对这 ...

  5. Hanoi塔问题——递归

    /////////////Hanoi塔问题///////#include<iostream>using namespace std;void hanoi(int i,char A,char ...

  6. Java实现 蓝桥杯VIP 算法训练 Hanoi问题

    问题描述 如果将课本上的Hanoi塔问题稍做修改:仍然是给定N只盘子,3根柱子,但是允许每次最多移动相邻的M只盘子(当然移动盘子的数目也可以小于M),最少需要多少次? 例如N=5,M=2时,可以分别将 ...

  7. (算法)Hanoi Problem汉诺塔问题

    Problem: There are three poles and N disks where each disk is heaver than the next disk. In the init ...

  8. Hanoi塔问题

    说明:河内之塔(Towers of Hanoi)是法国人M.Claus(Lucas)于1883年从泰国带至法国的,河内为越战时北越的首都,即现在的胡志明市:1883年法国数学家 Edouard Luc ...

  9. 【题解】Hanoi塔问题

    题目描述 有三根柱A,B,C.在柱A上有N块盘片,所有盘片都是大的在下面,小片能放在大片上面.并依次编好序号,现要将A上的N块片移到C柱上,每次只能移动一片,而且在同一根柱子上必须保持上面的盘片比下面 ...

  10. python算法-汉诺塔问题

    汉诺塔问题   初始状态: 思考:当盘子的个数是3的时候,大家写出移动顺序 移动的步骤: 3个盘子,从a到c 1.前面两个盘子,从a到b 1)把前面一个盘子,从a到c a->c 2)把第二个盘子 ...

随机推荐

  1. 本地项目上传到Git仓库

    1. 进入项目主目录,打开Git Bash,执行以下命令,将项目变为一个git管理的项目: $ git init 执行成功后,会在项目根目录生成一个.git的文件夹. 可以执行以下命令查看项目状态: ...

  2. 杰哥教你面试之一百问系列:java中高级多线程concurrent的使用

    目录 问题1:什么是ConcurrentHashMap?它与HashMap的区别是什么? 问题2:什么是CopyOnWriteArrayList?它适用于什么样的场景? 问题3:什么是Blocking ...

  3. Golang日志新选择:slog

    go1.21中,slog这一被Go语言团队精心设计的结构化日志包正式落地,本文将带领读者上手slog,体会其与传统log的差异. WHY 在日志处理上,我们从前使用的log包缺乏结构化的输出,导致信息 ...

  4. 删除软件 geek

    下载链接 Geek Uninstaller_v1.5.1.162 -技术松鼠 (jishusongshu.com)

  5. MySQL运维2-主从复制

    一.主从复制概念 主从复制是指将主数据库的DDL和DML操作通过二进制日志传到从服务器中,然后在从服务器上对这些日志重新执行也叫重做,从而使得从数据库和主库的数据保持同步. MySQL支持一台主库同时 ...

  6. destoon关于archiver归档的性能优化

    今天在处理一个项目时候发现archiver单个模块归档超过百万数据,打开速度就特慢,所以打开archiver下index.php文件进行分析,发现有句sql作怪 1 $result = $db-> ...

  7. android 反编译 apk 分享 smali2java 1.0.0.558

    smali2java 1.0.0.558 smali2java是一个将smali代码反编译成java代码的工具. 什么是smali?smali是将Android字节码用可阅读的字符串形式表现出来的一种 ...

  8. 软件开发人员 Kubernetes 入门指南|Part 1

    Kubernetes 是一个用于部署和管理容器的编排系统.使用 Kubernetes,用户可以通过自动执行管理任务(例如在跨节点间扩展容器并在容器停止时重新启动任务),在不同环境中可靠地运行容器. K ...

  9. CF1336A

    题目简化和分析: 明确一点这是一棵树. 为了保证每个工业城市的设置效益最大,应该设在最深的节点. 从深到浅,可以使用优先队列去实现. 设置一个的价值为 \(dep_u-siz_u-1\). 关于作者一 ...

  10. ThreadPoolExecutor使用浅谈

    1. 基础介绍 ThreadPoolExecutor是Python标准库concurrent.futures模块中的一个类,用于实现线程池的功能. ThreadPoolExecutor模块相比于thr ...