python汉诺塔递归算法_Python文摘:汉诺塔问题与递归算法

历史传说:

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

不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f⑴=1,f⑵=3,f⑶=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。n=64时,f(64)= 2^64-1=18446744073709551615。假如每秒钟一次,共需多长时间呢?一个平年365天有 31536000 秒,闰年366天有31622400秒,平均每年31556952秒,计算一下,18446744073709551615/31556952=584554049253.855年

这表明移完这些金片需要5845亿年以上,而地球存在至今不过45亿年,太阳系的预期寿命据说也就是数百亿年。真的过了5845亿年,不说太阳系和银河系,至少地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。

1453074-20181117230835884-529949468.png

***********************************************************************************************************************************************************************************

汉诺塔问题的限制条件:

1.在小圆盘上不能放大圆盘。

2.在三根柱子之间一回只能移动一个圆盘。

3.只能移动在最顶端的圆盘。

首先,我们从简单的例子开始分析,然后再总结出一般规律。

当n = 1的时候,即此时只有一个盘子,那么直接将其移动至C即可。移动过程就是 A -> C

当n = 2的时候,这时候有两个盘子,那么在一开始移动的时候,我们需要借助B柱作为过渡的柱子,即将A柱最上面的那个小圆盘移至B柱,然后将A柱底下的圆盘移至C柱,最后将B柱的圆盘移至C柱即可。那么完整移动过程就是A -> B , A -> C , B -> C

当n = 3的时候,那么此时从上到下依次摆放着从小到大的三个圆盘,根据题目的限制条件:在小圆盘上不能放大圆盘,而且把圆盘从A柱移至C柱后,C柱圆盘的摆放情况和刚开始A柱的是一模一样的。所以呢,我们每次移至C柱的圆盘(移至C柱后不再移到其他柱子上去),必须是从大到小的,即一开始的时候,我们应该想办法把最大的圆盘移至C柱,然后再想办法将第二大的圆盘移至C柱......然后重复这样的过程,直到所有的圆盘都按照原来A柱摆放的样子移动到了C柱。

那么根据这样的思路,问题就来了:

如何才能够将最大的盘子移至C柱呢?

那么我们从问题入手,要将最大的盘子移至C柱,那么必然要先搬掉A柱上面的n-1个盘子,而C柱一开始的时候是作为目标柱的,所以我们可以用B柱作为"暂存"这n-1个盘子的过渡柱,当把这n-1的盘子移至B柱后,我们就可以把A柱最底下的盘子移至C柱了。

而接下来的问题是什么呢?

我们来看看现在各个柱子上盘子的情况,A柱上无盘子,而B柱从上到下依次摆放着从小到大的n-1个盘子,C柱上摆放着最大的那个盘子。

所以接下来的问题就显而易见了,那就是要把B柱这剩下的n-1个盘子移至C柱,而B柱作为过渡柱,那么我们需要借助A柱,将A柱作为新的"过渡"柱,将这n-1个盘子移至C柱。

*********************************************************************************************************************************************************************

作者:Adherer

来源:CSDN

原文:https://blog.csdn.net/liujian20150808/article/details/50793101

版权声明:本文为博主原创文章,转载请附上博文链接!

*********************************************************************************************************************************************************************

该问题可以分解成以下子问题:

第一步:将n-1个盘子从A柱移动至B柱(借助C柱为过渡柱)

第二步:将A柱底下最大的盘子移动至C柱

第三步:将B柱的n-1个盘子移至C柱(借助A柱为过渡柱)

解:(1)n == 1

第1次 1号盘 A---->C sum = 1 次

(2) n == 2

第1次 1号盘 A---->B

第2次 2号盘 A---->C

第3次 1号盘 B---->C sum = 3 次

(3)n == 3

第1次 1号盘 A---->C

第2次 2号盘 A---->B

第3次 1号盘 C---->B

第4次 3号盘 A---->C

第5次 1号盘 B---->A

第6次 2号盘 B---->C

第7次 1号盘 A---->C sum = 7 次

不难发现规律:1个圆盘的次数 2的1次方减1

2个圆盘的次数 2的2次方减1

3个圆盘的次数 2的3次方减1

。 。 。 。 。

n个圆盘的次数 2的n次方减1

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

n的阶乘问题

再说一个例子:计算n的阶乘

f(n) = n!

其递归算法如下:

int factorial(int n){

if(n == 1)

return 1;

else

return n * factorial(n-1);

}

这段程序加载到内存的分配图如下:

0?wx_fmt=png

(图片来源于“码农翻身”公众号)

由于递归是函数自身调用自身,所以程序被编译后代码段中只有一份代码。递归调用是如何进行的呢?注意看堆栈中的栈帧啊, 每个栈帧就代表了被调用中的一个函数, 这些函数栈帧以先进后出的方式排列起来,就形成了一个栈, 栈帧的结构如下图所示:

0?wx_fmt=png

(图片来源于“码农翻身”公众号)

相信大家还记得《数据结构》(严蔚敏版)一书中提到的“工作记录”就是指函数栈帧。栈顶指针被称为“当前环境指针”。忽略到其他内容, 只关注输入参数和返回值的话,阶乘函数factorial(4)的工作栈如下图所示:

0?wx_fmt=png

(图片来源于“码农翻身”公众号)

其计算过程如下图所示:

0?wx_fmt=png

(图片来源于“码农翻身”公众号)

0?wx_fmt=png注意, 每个递归函数必须得有个终止条件, 要不然就会发生无限递归了, 永远都出不来了。

当然针对于此递归算法,对于n的值是有限制的。因为堆栈容量是有限的,如果n值太大程序会崩掉。

该如何解决呢?从上面的代码中可以知道“factorial(n) = n * factorial(n-1 )” ,这个计算式是整个程序的核心。 图中每个栈帧都需要记录下当前的n的值, 还要记录下一个函数栈帧的返回值, 然后才能运算出当前栈帧的结果。 也就是说使用多个栈帧是不可避免的。

可以使用下面的递归算法:

copycode.gif

int factorial(int n,int result){

if(n == 1){

return result;

}

else{

return factorial(n-1,n * result);

}

}

copycode.gif

注意函数的最后一个语句, 就不是 n * factorial(n-1) 了, 而是直接调用factorial(....) 这个函数本身, 这就带来了巨大的好处。

计算过程如下:

0?wx_fmt=png

当执行到factorial(1, 24)的时候直接就可以返回结果了。这就是妙处所在了,计算机发现这种情况,只用一个栈帧就可以搞定这些计算,无论n有多大。

0?wx_fmt=png

(图片来源于“码农翻身”公众号)

这就是所谓的“尾递归”了, 当递归调用是函数体中最后执行的语句并且它的返回值不属于表达式一部分时, 这个递归就是尾递归。

现代的编译器就会发现这个特点, 生成优化的代码, 复用栈帧。 第一个算法中因为有个n * factorial(n-1) , 虽然也是递归,但是递归的结果处于一个表达式中,还要做计算, 所以就没法复用栈帧了,只能一层一层的调用下去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/501004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python print格式化输出类型_第一讲:Python print() 格式化输出

由于书本上少了这个知识,所以总结如下,各同学看完后完成对应的试题:格式符为真实值预留位置,并控制显示的格式。格式符可以包含有一个类型码,用以控制显示的类型,如下:%s 字符串 (采用str()的显示)%r …

转-递归教学

作者:帅地 链接:https://www.zhihu.com/question/31412436/answer/683820765 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 递归专题连续刷题半年,从小白到学会了套路&#xff…

android游戏编程之从零开始_纯C语言程序员写的编程新手入门基础小游戏之最炫酷推箱子...

很多编程爱好者都编写过推箱子游戏编程吧,最近有好些朋友看见我以前的推箱子程序后,问我是怎么做的。我一直想把这个程序的整个过程写一份详细的东西,与各位编程爱好者分享,一直没空。正好现在放假了,而且离回家还有几…

数据库新增幂等操作_使用数据库唯一键实现事务幂等性

幂等性概念在分布式系统中,幂等性是一致性方面的一个重要概念。幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。所谓“影响相同”&#xff…

python re模块compile_Python re模块的match方法

pattern re.compile("\d") 将正则表达式编译成一个Pattern规则对象 pattern.match() 从开始位置开始往后查找,返回第一个符合规则的对象 pattern.search() 从任何位置开始往后查找,返回第一个符合规则的对象 pattern.findall() 所有的全部匹配…

考研数学(180°为什么等于π)

之所以要定义弧度制,是因为它的单位相比角度制有很大的优越性. 弧度的大小是两个长度之比,长度的单位是统一的,所以相比以后,可以认为弧度的单位为1,即以实数单位为单位. 弧度可以看做导出单位. 而角度制则不然,角度制单位是1/360周角,然而,1/360是怎么来的?为什么是周角除360…

c++ h cpp文件如何关联_C++核心准则SF.5: .cpp文件必须包含定义它接口的.h文件

SF.5: A .cpp file must include the .h file(s) that defines its interfaceSF.5: .cpp文件必须包含定义它接口的.h文件Reason(原因)This enables the compiler to do an early consistency check.这样可以让编译器尽早进行一致性检查。Example, bad(反面示例)// foo.h:void f…

常用的python脚本_五个python常用运维脚本面试题实例

一、用Python写一个列举当前目录以及所有子目录下的文件,并打印出绝对路径 #!/usr/bin/env python import os for root,dirs,files in os.walk(/tmp): for name in files: print (os.path.join(root,name)) os.walk() 原型为:os.walk(top, topdownTrue, …

JAVA进阶教学之(IO流)

目录 1、什么是IO流 2、流的分类 3、流的四大家族首领 4、java.io.*包下需要掌握的16个流 5、FileInputStream的实用方法 6、FileOutputStream的方法 7、文件复制/拷贝 8、FileReader的使用 9、FileWriter的使用 10、复制普通文本文件 11、BufferedReader带有缓冲区…

sonar 代理_Sonar

关于Sonar费用:免费;更新频率:持续更新;搜索来源:亚马逊;关键词总数:美国站约1亿,全球约1.8亿;关键词反查:支持;PPC关键词查询:支持;其他功能:关键词翻译。功能1、亚马逊PPC管理&…

devtools安装_R语言如何批量安装软件包

1. 为什么要批量安装R语言包当你在新的环境下, 安装R语言时,你需要安装很多包,比如tidyverse,比如data.table,这里你可以写一个函数,将所有需要的包写进去,然后进行批量安装2. 程序如下&#xf…

JAVA进阶教学之(序列化和反序列化)

目录 1、序列化Serialize和反序列化的概念 2、序列化和反序列化的代码演示: 3、序列化多个对象(序列化集合) 4、transient关键字将部分属性不参与序列化 1、序列化Serialize和反序列化的概念 在内存和硬盘的数据交互过程中,将…

java如何实现e的次方_Java开发如何更改MySQL数据库datadir目录之MySQL数据库索引实现...

引言MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System&#xff0c…

pythonjieba分词_$好玩的分词——python jieba分词模块的基本用法

jieba(结巴)是一个强大的分词库,完美支持中文分词,本文对其基本用法做一个简要总结。 安装jieba pip install jieba 简单用法 结巴分词分为三种模式:精确模式(默认)、全模式和搜索引擎模式&…

局部变量和成员变量的区别

1.定义的位置不一样【重点】 局部变量:在方法的内部成员变量:在方法的外部,直接写在类当中 2.作用范围不一样【重点】 局部变量:只有方法当中才可以使用,出了方法就不能再用了成员变量:整个类都可以通用 …

pytorch 训练过程acc_Pytorch之Softmax多分类任务

在上一篇文章中,笔者介绍了什么是Softmax回归及其原理。因此在接下来的这篇文章中,我们就来开始动手实现一下Softmax回归,并且最后要完成利用Softmax模型对Fashion MINIST进行分类的任务。在开始实现Softmax之前,我们先来了解一下…

进程调度实验_Linux应用编程之进程的PID与PPID

关注、星标公众号,直达精彩内容ID:嵌入式情报局作者:情报小哥1进程PID首先介绍PID的相关知识,为后面介绍fork函数进行铺垫。01PID与PPID PID不是控制理论的PID算法,而是Prcess ID的简写。进程PID是当操作系统运行进程时…

操作Windows文件夹时,弹出文件夹正在使用,操作无法完成【解决】

在windows系统上,有时候在删除系统文件或文件夹时出现弹框,提示操作无法完成。这种情况的出现是因为你要删除的文件或文件夹被打开,或者被系统占用。遇到这种情况要怎么处理呢,本文介绍下具体的操作方法来帮助你解决这个问题。 方…

邀请合作如何表达_适时表达想法 才有利于彼此的合作

丹尼跟珍妮合作主持一个podcast节目,两人对这个节目兴致勃勃,并花很多时间投入,珍妮想邀请自己身边朋友一起参加,认为特别来宾可以增加节目的丰富度;丹尼却觉得现在节目才刚开始起步,要建立好两人的节目定位…

python泰坦尼克号数据预测_使用python预测泰坦尼克号生还

简介Titanic是Kaggle竞赛的一道入门题,参赛者需要根据旅客的阶级、性别、年龄、船舱种类等信息预测其是否能在海难中生还,详细信息可以参看https://www.kaggle.com/,本文的分析代码也取自 kaggle 中该竞赛的 kernal。数据介绍给出的数据格式如…