目录
一、什么是递归
递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。也就是说,递归算法是一种直接或者间接调用自身函数或者方法的算法。
通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。
二、递归的基本原理
第一:每一级的函数调用都有自己的变量。
第二:每一次函数调用都会有一次返回。
第三:递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序。
第四:递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反。
第五:虽然每一级递归都有自己的变量,但是函数代码并不会得到复制。
三、递归的优缺点
1、优点:实现简单,可读性好。
2、缺点:递归调用,占用空间大;递归太深,易发生栈溢出;可能存在重复计算。
四、体会:一个累加递归函数
1、要求:求某个数的累加值。如add(5),求1-5的累加值
2、分析:1-5的累加值,可以分解为:
(1)add(5) = 5+add(4); //则计算公式为:$sum = $i + add($i -1)
(2)add(4) = 4+add(3);
(3)add(3) = 3+add(2);
(4)add(2) = 2+add(1);
(5)add(1) = 1+add(0); //终止条件 $>0
从上述分析可知,可以累加问题可以分解,并且解决思路一致。
3、程序代码如下:
function add($i)
{
if ($i > 0) { //终止条件
$sum = $i + add($i - 1); //调用递归函数
return $sum;
}
}
$result = add(5);
4、为体会调用过程,可用此程序体会:
<?php
/**
* 递归函数要点
* 1、一个问题可以分解为几个子问题的解。
* 2、这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一致。
* 3、存在递归终止条件。
*/
function add($i)
{
if ($i > 0) {//终止条件
echo '递归前:$i=' . $i . "<br>";
$sum = $i + add($i - 1); //调用递归函数
echo '<hr>';
echo '递归后:$i=' . $i . "<br>";
echo '递归后:$sum=' . $sum . "<br>";
return $sum; //此return语句可以去掉,看一下效果
/**如果递归函数过程涉及某种计算,
* 则需加return返回计算的结果,
* 否则每层递归返回数据。
* 如只是输出显示数据,则可不需要return证句
*/
}
}
$result =add(5);
运行结果如下:
五、总结一下:
通过学习和动手实践,对递归函数有了初步的认识,但尚在初浅阶段。
递归函数:
(1)函数体内调用函数本身,这说明函数完成的功能是一致的,区别是处理的数据不同而已;
(2)return语句可以不用,一般用在函数计算结果需要返回时。如果无结果返回,只是递归显示数据则可以不需要return语句。
(3)对递归的过程:
执行到递归函数调用的时候,则进入:递归体1->递归体2->递归体3->递归体4->……->递归体n,
直到递归条件终止时,逐层返回:递归体n->递归体n-1->递归体n-2->递归体n-3->……->递归体1,
再来看看这张图片,可能更好理解一点。
(4)通过递归的三大要素来深入理解递归函数的应用
- 第一要素:明确你这个函数想要干什么。先不管函数里面的代码什么,而是要先明白,你这个函数的功能是什么,要完成什么样的一件事。
- 第二要素:寻找递归结束条件。我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。
- 第三要素:找出函数的等价关系式。我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。
关于递归的优化
1、考虑是否重复计算
如果你使用递归的时候不进行优化,是有非常非常非常多的子问题被重复计算的。因此,使用递归的时候,必要须要考虑有没有重复计算,如果重复计算了,一定要把计算过的状态保存起来。
2、考虑尾递归
对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。
不过,有时候当 n 比较大的时候,例如当 n = 10000 时,那么必须要往下递归10000层直到 n <=1 才将结果慢慢返回,如果n太大的话,可能栈空间会不够用。这个时候,就可以用尾递归优化来解决。
顾名思义,尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量。直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去。
案例:
求斐波那契数列递归算法
<?php
function fib($num)
{
if ($num == 0) {
// echo $num . '->';
return 0;
} elseif ($num == 1) {
// echo $num . '->';
return 1;
} else {
$list = fib($num - 1) + fib($num - 2);
return $list;
}
}
echo '<pre>';
for ($i = 0; $i < 10; $i++) {
echo fib($i).'->';
}
echo '</pre>';
1.普通的递归斐波那契数列
function fac($n){
if($n == 1 || $n == 2) return 1;
else return fac($n-2) + fac($n-1)
2.尾递归:在使用递归的情况下,不爆栈
普通的递归,运行栈会被函数的递归调用占满了
因此在要求使用递归的情况下,可以使用尾递归
每次调用函数时不生成新的运行栈,利用上一次的结果
/**
* $a充当收集器,收集上一次运行栈的返回值,之后栈空间会被回收
* $a和$b参与每次的计算
* $n是斐波那契数列执行的次数
*/
function fac1($a,$b,$n){
if($n>2) return fac1($a+$b,$a,$n-1);
return $a;
}
3.记忆化搜索:减少不必要的重复计算,自上而下
在原本的斐波那契递归中,总会像如下图一样递归到最后再返回结果
其中,例如 以5为例,3 往下的部分,就会被重复计算两次,2往下的部分会被重复计算3次
如果数据量大的情况下,记忆化搜索减少的计算量是十分可观的
//用该方法可以大大优化斐波那契数列解决速度
class Solution{
private $memory = []; //记录当前数字是否已经求出解
public function fac($n){
if($n == 0) return 0;
if($n == 1) return 1;
if(empty($this->memory[$n])) //若没有对应的解,则继续进行递归
$this->memory[$n] = $this->fac($n-1) + $this->fac($n-2);
return $this->memory[$n]; //有解则直接返回该斐波那契数列
}
}
$q = new Solution();
————————————————
版权声明:本文为CSDN博主「YY-帆S」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010365335/article/details/86408252
4.动态规划:自上而下,非递归
非递归,减少了系统栈的建立,比记忆化搜索还快
将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案
function fac($n){
if($n == 1 || $n == 2) return 1;
$a = 1;
$b = 1;
$res = 0;
for($i = 3;$i<=$n;++$i){
$res = $a + $b;
$a = $b;
$b = $res;
}
return $res;
}