从汉诺塔问题看递归实质(用递归解决的两个小问题霍纳法则,排列问题)
来源:互联网 发布:红色警戒2网络打不开 编辑:程序博客网 时间:2024/06/05 04:52
汉诺塔问题
汉诺塔问题发源于古老的梵天寺之塔意识。传说I世界诞生的时候,有一座摞了64个黄金碟子的钻石塔(记为A),碟子从小到大的次序自底向上的摞在塔上,除此之外还有两个钻石塔(B和C),从世界诞生之时起,梵天寺的僧侣们就在通过塔C从塔A挪到塔B,因为碟子非常中,每次只能挪一块。另外,任意时候都不能大碟子在小碟子上。根据传说,当僧侣们完成任务的时候就是世界的末日。
首先,我们从简单的例子开始分析,然后再总结出一般规律。
当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柱。
不难给出这样的解法
第一步:将n-1个盘子从A柱移动至B柱(借助C柱为过渡柱)
第二步:将A柱底下最大的盘子移动至C柱
第三步:将B柱的n-1个盘子移至C柱(借助A柱为过渡柱)
C++代码如下:
void TowerOfHanoi(int n,tower x,tower y,tower z){ if(n){ TowerOfHanoi(n-1,x,z,y); std::cout<<"move top disk from tower "<<char(x)<<" to top of tower "<<char(y)<<std::endl; TowerOfHanoi(n-1,z,y,x); }}这个问题,可能是递归最简单的一个小例子了,那么递归到底是什么呢?
递归的实质
简单的来说,递归就是一个函数直接或间接地调用自身,是为直接或间接递归。一般来说,递归需要有边界条件、递归前进段和递归返回段(其实就是进栈出栈的操作)。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。用递归需要注意以下两点:(1) 递归就是在过程或函数里调用自身。(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
递归 缺点明显,递归解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,因此递归次数过多容易造成栈溢出。
从方法论意义上说,递归方法是一种从简单到复杂、从低级到高级的可连续操作的解决问题的方法。它的每一步骤都是能行可操作的,各步骤之间是连续转换的。递归定义是用简单的、自明的要素描述、构造、说明复杂的整体。递归方法是通过解决简单的问题来解决复杂 的问题。在人们的思维过程中,普遍存在着递归机制。递归方法是一种处理问题的精致技巧、解决问题的有效方法。从哲学方法论角度研究递归方法,具有重要的意义。
递归包含着还原。所谓“还原”就是找到最基本的初始元素和最基本的操作步骤。递归方法与可操作性问题相关。可递归的,就是可还原的,也就是可操作的。某些问题的解决是一个有始有终、环环紧密相联的过程中,要得到最终结果,必须从第一步做起,任何一步都不能缺少;任何相邻的两步,前一步完成之后才能完成后一 步。这样的问题就必须用递归方法来解决。
递归定义具有这样的特点:要证明由归纳定义所确定的某一类对象中的每一个都有某个性质,只需证明满足定义中陈述的规则的对象都有该性质。例如,要证明一个形式系统的每一定理都有性质P,只需要证明满足对定理的定义规则的公式都有性质P。
再从递归的实质回到具体问题中,我们来看这样几个小问题
1.排列问题
给定n个不同的元素,列出所有可能的排列数。(以int数组为例)
void Perm(int *a,int k,int n){ if(k==n){//输出排列 for(int i=0;i<n;i++) std::cout<<a[i]<<" "; std::cout<<std::endl; } else//a[k:n]中包含多个排列,递归生成这些排列 for(int i=k;i<n;i++){ int t=a[k]; a[k]=a[i]; a[i]=t; Perm(a,k+1,n); //a[k+1:n]的所有排列 t=a[k]; a[k]=a[i]; a[i]=t; }}2.霍纳法则
假设有n+2个实数a0,a1,…,an,和x的序列,要对多项式Pn(x)= anxn+an-1xn-1+…+a1x+a0求值,直接方法是对每一项分别求值,并把每一项求的值累加起来,这种方法十分低效,它需要进行n+(n-1)+…+1=n(n+1)/2次乘法运算和n次加法运算。有没有更高效的算法呢?答案是肯定的。通过如下变换我们可以得到一种快得多的算法,即Pn(x)= anxn +an-1xn-1+…+a1x+a0=((…(((anx +an-1)x+an-2)x+ an-3)…)x+a1)x+a0,这种求值的安排我们称为霍纳法则。
//以三个项为例
int LEN=3int hornor(int list[],int n,int x)//利用递归实现霍纳规则{ if(n == LEN-1) { return list[LEN-1];//递归出口 } else { return hornor(list,n+1,x)*x+list[n];//递归过程 }}int main() { int a[3]={1,1,1};//数组表示多项式的系数 int x=3;//多项式的自变量值 int result=0;//存放结果 result = hornor(a,0,3); std::cout<<result<<std::endl; return 0;}
- 从汉诺塔问题看递归实质(用递归解决的两个小问题霍纳法则,排列问题)
- 从汉诺塔问题看递归
- 递归解决全排列问题
- 递归解决全排列问题
- 递归:递归法解决排列问题
- 用递归解决蓝桥杯的一道排列问题
- 递归思想解决小问题
- 递归求排列问题
- 递归 全排列 问题
- 递归-排列问题
- 排列问题(递归)
- 递归之排列问题
- 递归---排列问题
- 递归-排列问题
- 递归分治解决全排列问题
- java使用递归解决全排列问题
- 利用递归解决全排列问题
- Java递归解决全排列问题
- Spring事务管理(详解+实例)
- linux学习2(文件与目录管理)
- 字符串操作函数知识框图
- 5G:认识5G关键技术
- OGNL表达式
- 从汉诺塔问题看递归实质(用递归解决的两个小问题霍纳法则,排列问题)
- 交叉编译OpenSSL和paho.mqtt.c
- hdu-1434-幸福列车
- Android -- BroadcastReceiver 中启动Activity
- FTP 命令 PASV jdk7 IP bug 引起无法使用FTP
- tf.add_to_collection、tf.get_collection、tf.add_n用法浅析
- 对“从直线变换为平面”的理解
- Swift 【对构造器自动继承的几点理解】
- G