zoukankan      html  css  js  c++  java
  • 递归

    函数调用原理

      在理解递归之前,不得不先了解一下函数调用的工作原理。

      在程序运行期间调用一个函数的时候,在运行被调用函数之前,需要完成三项任务。

      1.将所有的实参、返回地址等信息传递给被调用函数。

      2.为被调用函数的局部变量分配存储区。

      3.将控制转移到被调用函数的入口。

      函数执行完返回之前,应该完成下列三项任务。

      1.保存当前函数的最终计算结果。

      2.释放被调函数的数据区。

      3.根据返回地址将控制转移到上层,也就是调用方。

      在函数之中调用函数也是同样的道理,如A函数中调用B函数,B函数中调用C函数。

      那么C函数执行完之后程序的控制会回到B函数中调用C的地方继续往下执行,B执行完后会回到A中继续执行,整个流程的运行规则是后调用先返回。

      能够实现后进先出的数据结构是栈,此时的内存管理实行的就是 “栈式管理” ,这是栈的一个典型应用。

    递归函数

      一个直接调用自己或间接调用自己的函数,称为递归函数。

      递归必须要满足两个元素,一是递归出口,二是递归表达式。

      递归出口即为返回条件,没有递归出口的递归那是死循环。 递归表达式即是直接或者间接调用自身的行为,如果你不调用自身,那么这个算法也不能够叫做递归。

      所谓间接调用自己,意思就是你调用别的函数,别的函数里面又在调用你。

      我们设计递归函数可以根据几种情况考虑:

      第一种情况,问题的定义是递归的,比如数学中的阶乘,像这样子的问题我们直接就可以写出递归函数。

      第二种情况,有些数据结构比如二叉树广义表,由于结构本身存在递归特性,则可以使用用递归函数来描述。

      第三种情况,问题本身没有明显的递归特征,但使用递归求解更简单,比如Hanoi塔问题。

      前两种情况比较直观,只要我们一遇到,就会很直接的想到用递归来处理,第三种情况则需要更多的经验来判断是否应该使用递归。

    阶乘函数

      一个正整数的阶乘是所有小于或等于该数的正整数的乘积,0的阶乘为1。

      比如5的阶乘 = 5 * 4 * 3 * 2 * 1 = 120;

    递归代码:

    function fact($n){
        if(0 == $n){
            return 1;//递归出口
        }
        $temp = $n * fact($n-1);//递归表达式
        return $temp;
    }
    
    $result = fact(5);

    如果描绘出递归工作栈的变化情况会非常的直观:

    Hanoi塔问题

      Hanoi塔 也叫做汉诺塔。

    题目:

    如图所示,假设有三个分别命名为X、Y和Z的塔座,在X上插的有N个直径大小各不相同,并从大到小依次堆叠的圆盘,现要求将X塔上的所有盘子移动到Z塔上,并且堆叠的顺序保持不变。

    圆盘移动时必须遵循以下规则:

    1、每次只能移动一个圆盘
    2、圆盘可以放到X,Y,Z中任一塔座上
    3、任何时候不能将一个较大的圆盘压在较小的圆盘之上

      在我们不知道递归之前,按照常规做法,我们都会使用递推的方式,比如1个圆盘的情况怎么解决,2个圆盘的情况怎么解决,3个圆盘的情况怎么解决,将多种情况递推出来,然后找规律。

      我们将盘子的个数称为N,盘子大小即为n的大小。

      当N=1时,直接将1号盘子搬到Z号塔就结束了。

      当N=2时,将1号盘子搬到Y上,再将2号盘子搬到Z上,再将1号盘子从Y搬到Z上。

      当N=3时,将1号盘子搬到Z上,2号盘子搬到Y上 ,1号盘子从Z搬到Y上,3号盘子搬到Z上,1号盘子搬到X上,2号盘子从Y搬到Z上,1号盘子从X搬到Z上。

      .........

      我们发现,根据N的不同,盘子的搬法是不一样的,使用递推的方法,很难找到规律。

      据说国外有个皇帝曾经为了解这个题,叫手下的人实际操作搬盘子,然而搬了N多天也没个结果。 因为他们的盘子有点多,用了64个盘子。

      后来得出的结论是,移动盘子的次数是2的N次方-1,就算1秒钟移动一次,要搬完这64个盘子,需要5845.54亿年,那时候地球早已经毁灭了。

      既然常规思路走不通,我们就换个思路考虑问题。

      我们可以把问题分解成下面三步

      第一步,我们想办法把N-1个盘子从X搬到Y上。

      第二步,把X上的最后一个盘子搬到Z上。

      第三步,把Y上的N-1个盘子搬到Z上。

      这三步实现的话,那么整个事情也就完成了。

      不管你的N有多大,我们只把它拆分为N-1和1两个规模,当剩下的盘子等于1时,那么我们就直接搬到Z上就行了。

      那么我们面临的最大的问题,就是第一步和第三步中的搬动N-1个盘子,要怎么搬?

      此时此刻,我们面临的这个问题,和最开始的问题完全一样。但问题规模不一样了,原来是N个盘子的问题,现在我们把它变成了N-1个盘子的问题。

      同样,N-1的问题,不就等于N-2的问题吗?不就等于N-3的问题吗?

      那么我们进一步分析

      第一步中,把N-1个盘子从X搬到Y上,此时起始位置是X,目标位置是Y,中间要借助Z。

      第二步就简单了,直接把X上的盘子移动到Z上。

      第三步中,再把Y上的N-1个盘子搬到Z上,此时起始位置是Y,目标位置是Z,中间要借助X。

      他们的每一次移动,都要遵守这三步的规则,做法都是一样,唯有传递的不同。

      有了上面这些思考,我们再来看代码,配合着注释,我想更容易帮助理解。

    /**
     * @param $n 盘子编号
     * @param $x 起始位置
     * @param $y 辅助塔
     * @param $z 目标位置
     */
    function hanoi($n,$x,$y,$z){
        if($n==1){
            move($x,1,$z); //将最后一个圆盘从x搬到z上
        }else{
            hanoi($n-1,$x,$z,$y);//将x上N-1个盘子从x搬到y上  z是辅助塔
            move($x,$n,$z);//将编号为N的盘子从x搬到z上
            hanoi($n-1,$y,$x,$z);//将y上N-1个盘子从y搬到z上  x是辅助塔
        }
    }
    
    function move($x,$n,$z){
        echo '移动第'.$n.'号盘子 从'.$x.'-->'.$z.'<br>';
    }
    
    hanoi(5,'x','y','z');

      友情提示:不要去想具体的移动,你一但去想,就陷入了递推的思维中,那样你会疯掉的。对于递归的理解最重要的是放弃你在脑海中推导细节的冲动,汉诺塔问题永远只有两层,就是N-1和1,当N>1时他们以同样的方式移动。

    递归思维

      在讨论Hanoi塔问题的时候我们面临了两种思维的选择,递推与递归,这里进行比较一下,这两种思维到底有什么区别。

      以登山为例,如华山9000级台阶,如何登上这9000级台阶?

      递推思维:我先抬脚登上1级台阶,就还剩8999级台阶,然后再登1级台阶,就还剩8998级台阶,登一级台阶又不费力,只需重复这个动作,最终即可登顶。

      递归思维:如果我现在在第8999级台阶上,我只需要抬脚一步就可以登顶。那么我怎样才能到第8999级台阶上呢? 如果我现在在第8998级台阶上,我只需要抬脚一步就可以到达8999级台阶,以此类推。

      虽然说,最终实践起来都是一步一步往上爬,没什么区别,但两种思维讨论问题的出发点不一样,递推从易到难,递归从难到易,编写出来的算法程序是两个派别。

  • 相关阅读:
    UEditor 在 Layer 模态框中无法使用问题
    mysql FORMAT() 格式化后的数字运算出错
    dede 对 单个 字段 编辑
    SW线路中串联1K电阻的作用
    示波器 如何用示波器正确测量电源纹波
    Excel工作表忘记密码如何破解?
    excel小技巧
    CPU核心电压与VID电压
    电容有什么作用?为什么cpu电源引脚都并联一个电容?
    APU的Vsense引脚的作用
  • 原文地址:https://www.cnblogs.com/fengyumeng/p/11285387.html
Copyright © 2011-2022 走看看