递归算法 —— Hanoi汉诺塔游戏

article/2025/9/9 15:07:16

前言


博客主页:干脆面la的主页

gitte链接:干脆面la的gitee仓库

      刚学习完递归函数接触汉诺塔问题的时候,汉诺塔问题困扰了我很久。博主花了很长时间理解这道题目,因此整理出了用递归解决汉诺塔问题的思路,希望对大家有所帮助。

  • 如果认为博主的文章对你有所帮助
  • 欢迎三连加关注
  • 你们的支持是我最大的动力! 

目录

前言

1 汉诺塔的由来

 2 图解1~3个圆盘的汉诺塔

2.1 1个圆盘的移动

2.2 2个圆盘的移动 

2.3 3个圆盘的移动

3 核心思路 

 4 代码实现

4.1 模拟移动的过程

4.2 Hanoi的递归代码

4.3 完整代码

4.4 运行结果

 5 总结


正文开始... 

1 汉诺塔的由来

汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘


 2 图解1~3个圆盘的汉诺塔

2.1 1个圆盘的移动

移动1次:A->C


2.2 2个圆盘的移动 

移动3次:A->B  A->C  B->C


2.3 3个圆盘的移动

移动7次:A->C  A->B  C->B  A->C  B->A  B->C  A->C


3 核心思路 

对于三个圆盘以上的汉诺塔游戏便开始愈加困难,但是通过递归的思想我们可以将一个极其复杂的步骤简化为一个简单的步骤。

对比这三幅图,我们发现从2个圆盘开始,它们都必须经历的操作就是:(1)使n-1个圆盘整体移动到B柱(中转位置)上,(2)再使第n个圆盘移动到C柱(目的位置)上,(3)再将n-1个圆盘整体移动到C柱(目的位置)上。

上一操作中的第(1)步又可以细分到:将n-2个圆盘通过 (1)使n-2个圆盘整体移动到C柱(中转位置)上,(2)再使第n-1个圆盘移动到B柱(目的位置)上,(3)再将n-2个圆盘整体移动到B柱(目的位置)上。

上一操作中的第(3)步又可以细分到:将n-2个圆盘通过 (1)使n-2个圆盘整体移动到A柱(中转位置)上,(2)再使第n-1个圆盘移动到C柱(目的位置)上,(3)再将n-2个圆盘整体移动到C柱(目的位置)上。

以此类推......

综上所述:本题应该使用递归的方法,让题目化繁从简,使一个极其复杂的步骤简化为简单的步骤。


 4 代码实现

4.1 模拟移动的过程

void move(char pos1, char pos2)
{printf(" %c->%c ", pos1, pos2);
}

4.2 Hanoi的递归代码

n:盘子个数 pos1:起始位置 pos2:中转位置 pos3:最终位置

void Hanoi(int n, char pos1, char pos2, char pos3)
{//如果只有一个盘子,只需要将盘子从起始位置移动到目的位置上if (n == 1){move(pos1, pos3);}//如果有n(n>=2)个盘子,则需要将n个盘子通过中转位置移动到目的位置上else{//将n-1个盘子通过中转位置移动到目的位置上Hanoi(n - 1, pos1, pos3, pos2);//将第n个移动到目的位置上move(pos1, pos3);//将n-1个盘子通过中转位置移动到目的位置上Hanoi(n - 1, pos2, pos1, pos3);}
}
int main()
{Hanoi(1, 'A', 'B', 'C');printf("\n");Hanoi(2, 'A', 'B', 'C');printf("\n");Hanoi(3, 'A', 'B', 'C');printf("\n");return 0;
}

4.3 完整代码

void move(char pos1, char pos2)
{printf(" %c->%c ", pos1, pos2);
}
//     n:盘子个数 pos1:起始位置 pos2:中转位置 pos3:最终位置
void Hanoi(int n, char pos1, char pos2, char pos3)
{//如果只有一个盘子,只需要将盘子从起始位置移动到目的位置上if (n == 1){move(pos1, pos3);}//如果有n(n>=2)个盘子,则需要将n个盘子通过中转位置移动到目的位置上else{//将n-1个盘子通过中转位置移动到目的位置上Hanoi(n - 1, pos1, pos3, pos2);//将第n个移动到目的位置上move(pos1, pos3);//将n-1个盘子通过中转位置移动到目的位置上Hanoi(n - 1, pos2, pos1, pos3);}
}
int main()
{Hanoi(1, 'A', 'B', 'C');printf("\n");Hanoi(2, 'A', 'B', 'C');printf("\n");Hanoi(3, 'A', 'B', 'C');printf("\n");return 0;
}

4.4 运行结果

 答案和我们预想的结果使相同的,通过这样简洁的代码,我们真的解决了汉诺塔问题。

 5 总结

问:当我们把盘子的个数改为64,会发生什么呢?

答: 结果是发现我们的程序一直在不停的运行,并打印出密密麻麻的移动过程,原因是64个圆盘的移动实在是太复杂了。

由上面分析我们得知

1个圆盘: A->C   1次:2^1-1
2个圆盘: A->B A->C B->C   3次:2^2-1
3个圆盘: A->C A->B C->B A->C B->A B->C A->C   7次:2^3-1

那么64个圆盘也就是:需要移动2^64-1次,如下图:

 假设婆罗门一秒移动一次圆盘,并且在日夜不休的情况下,则需要移动583344215029年。预言说——当这些盘子移动完毕时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽,尘归尘,土归土

 而对于一个普通的计算机来说,要解决64个圆盘的汉诺塔问题也需要耗费上百年的时间,因此才会出现这样的结果。


本次的汉诺塔游戏就讲到这啦~

(完)


http://chatgpt.dhexx.cn/article/5l7SG5rQ.shtml

相关文章

计算任意位数的黑洞数

黑洞数是指这样的整数: 由这个数字每位数字组成的最大数减去每位数字组成的最小数仍然得到这个数自身。 例如3位黑洞数是495,因为954-459495,4位数字是6174,因为7641-14676174。 def max( x ):data[]while x/1!0:kx%10xx//10data.…

蓝桥杯 黑洞数 解题报告

任意一个5位数,比如:34256,把它的各位数字打乱,重新排列,可以得到一个最大的数:65432,一个最小的数23456。求这两个数字的差,得:41976,把这个数字再次重复上述…

黑洞数—python

黑洞数:黑洞数又称陷阱数,是类具有奇特转换特性的整数。任何一个数字不全相同整数,经有限“重排求差”操作,总会得某一个或一些数,这些数即为黑洞数。“重排求差”操作即把组成该数的数字重排后得到的最大数减去重排后得到的最小数。或者是冰雹原理中的“1”黑洞数 如果有…

6174 黑洞数字

关于6174这个数字的猜想是:从0到9取任意4个不全相同的数字,从大到小排列得到一个4位大数,从小到大排列得到一个4位小数,二者大减小做差,得到一个新的差值,这个值不足4位数补0,重复排列做差的操作…

求4位数的黑洞数

黑洞数又称陷阱数,是类具有奇特转换特性的整数。任何一个数字不全相同整数,经有限“重排求差”操作,总会得某一个或一些数,这些数即为黑洞数。“重排求差”操作即把组成该数的数字重排后得到的最大数减去重排后得到的最小数。或者…

C语言验证黑洞数6174

0x00 问题描述 问题简述:任意选一个四位数(数字不能全相同),把所有数字从大到小排列,再把所有数字从小到大排列,用前者减去后者得到一个新的数。重复对新得到的数进行上述操作,7步以内必然会得到6174。 0x01 代码设计…

黑洞数 C语言

黑洞数也称为陷阱数,又称“Kaprekar问题”,是一类具有奇特转换特性的数。 任何一个各位数字不全相同的三位数,经有限次“重排求差”操作,总会得到495。 最后所得的495即为三位黑洞数。所谓“重排求差”操作即组成该数的数字重排…

黑洞数

黑洞数是指于四位数中,只要数字不完全相同,将数字由大到小的排列减去由小到大的排列。假设一开始选定的数字为,f(),f(),...,f() 用同样的规则继续算下去,最后的结果一定是6174。 比如说一开始选…

python求黑洞数_求解黑洞数

问题描写: 黑洞数又称圈套数,是类具有奇特转换特性的整数。任何1个数字不全相同的整数, 经有限“重排求差”操作,总会得到某1个或1些数,这些数即为黑洞数。 “重排求差”操作即把组成该数的数字重排后得到的最大数减去…

负载均衡之加权轮询算法

在介绍加权轮询算法(WeightedRound-Robin)之前,首先介绍一下轮询算法(Round-Robin)。 一:轮询算法(Round-Robin) 轮询算法是最简单的一种负载均衡算法。它的原理是把来自用户的请求轮流分配给内部的服务器:从服务器1开始,直到服务…

基于HTTP的长轮询实现

Web客户端与服务器之间基于Ajax(http)的常用通信方式,分为短连接与长轮询。 短连接:客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。 在长轮询机制中,客户端像传统轮询一…

Linux轮询操作

Linux设备之非阻塞I/O操作 文章目录 Linux设备之非阻塞I/O操作前言一、接口简介1、select2、poll3、epoll4、总结 二、接口介绍三、代码样例 前言 上一篇讲解了Linux设备的阻塞I/O操作,其原理是利用了把进程挂到等待队列中,等条件满足时再唤醒此进程。本…

短轮询和长轮询

轮询是由客户端每隔一段时间向服务器发出HTTP请求,服务端接收到请求后向客户端返回最新的数据。 客户端的轮询方式一般分为短轮询和长轮询。 短轮询: 一般是由客户端每隔一段时间向服务器发起一次普通HTTP请求。服务端查询当前接口是否有数据更新&#x…

轮询与长轮询

轮询:说白了就是客户端定时去请求服务端, 是客户端主动请求来促使数据更新; 长轮询:说白了 也是客户端请求服务端,但是服务端并不是即时返回,而是当有内容更新的时候才返回内容给客户端,从流程…

前端实现轮询

方法一:简单实现 componentDidMount() {this.props.countFxMissionByStatus();countSwiftMessage(); }componentWillReceiveProps(nextProps) {const {location} nextProps;// 判断页面然后在更新的周期中实现轮询const isSwiftManage location.pathname.indexOf…

NGINX轮询机制的几种形式

前言:总以为轮询就简单的next而已,实际还有几种不同的实现机制。某个客户的源站有几个不同的IP,回源的时候自然是采用的轮询的机制。客户业务上线前,检查源站的联通性发现一个漏网之鱼竟然差点滥竽充数。然而客户的想法确是&#…

事件轮询机制理解

进程与线程 首先简单了解下进程和线程的概念 进程:cpu资源分配的最小的单位,是拥有资源和独立运行的最小单位,程序执行时,会创建一个进程,cpu为其分配资源,并加入进程就绪队列。线程:cpu调度的…

事件轮询机制

事件循环(轮询)机制 js是单线程的所有js代码都是在主线程执行的同步任务进入主线程即会执行异步任务则会进入浏览器的管理模块 (有DOM事件管理模块、ajax请求管理模块、定时器管理模块等)管理模块一直监视异步任务是否满足条件。如果满足条件则会将对应的回调放入回调队列中(c…

IP多播(组播)

一 IP多播的基本概念 IP多播(multicast,也被译为组播),它是一种一对多的通信方式。与单播相比,多播可以大大节约网络资源。 以视频流媒体服务为例说明单播和多播的区别,如图所示: 图1 单播与多播的比较 (a) 中使用的…

多播

19.1 概述 单播地址标识单个接口,广播地址标识子网上的所有接口,多播地址标识一组接口。单播和广播是编制方案的两个极端(要么一个要么全部),多播的目的就在于提供一种折衷方案。多播数据报仅由对该数据报感兴趣的接口接收,也就是…