递归

程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

看到过查字典的比喻挺好的。想象用一本纯英文词典查单词,要查某一个单词的意思,翻到这个单词时,看解释,发现解释中有一个单词不认识,所以,无法明白这个要查的单词是什么意思;这时,再用这本词典(函数本身)查那个不认识的单词,又发现查的第2个单词的解释中又有一个单词不认识,那么,又再用这本词典查第3个不认识的单词,这样,一个一个查下去,直到解释中所有单词都认识,这样就到底了,就明白了最后一个单词是什么意思,然后一层一层倒回来,就知道我最初想查的第1个单词是什么意思了,问题就解决了。

通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。它有如下特点:一个问题的解可以分解为几个子问题的解这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样存在递归终止条件,即必须有一个明确的递归结束条件,称之为递归出口

如果递归没有终止条件?

在程序运行的时候,调用函数是有代价的,那就是,要占用一片叫做栈(stack)的内存空间。当调用函数时,都必须要放一些数据到栈里,当函数运行结束这些数据会从栈里被取出,如果调用了很多函数但是这些函数都不返回,栈就被塞满了。数据没地方放了。这种情况叫做栈溢出错误,对程序运行而言。这是致命的错误,因此程序会被操作系统强行终此。

假设我们用递归来算阶乘 f(n)
在这里插入图片描述在这里插入图片描述

递归的三大要素

第一要素:明确你这个函数想要干什么
例如,我定义了一个函数

// 算 n 的阶乘(假设n不为0)
int f(int n){

}

第二要素:寻找递归结束条件
谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面,如下

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n == 1){
        return 1;
    }
}

第三要素:找出函数的等价关系式
第三要素就是,我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。例如,f(n) 这个范围比较大,我们可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。说白了,就是要找到原函数的一个等价关系式,f(n) 的等价关系式为 n * f(n-1),即f(n) = n * f(n-1)。

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n <= 1){
        return n;
    }
    // 把 f(n) 的等价操作写进去
    return f(n-1) * n;
}

案例1:斐波那契数列

假设 f(n) 的功能是求第 n 项的值,代码如下:

int f(int n){

}

2、找出递归结束的条件
显然,当 n = 1 或者 n = 2 ,我们可以轻易着知道结果 f(1) = f(2) = 1。所以递归结束条件可以为 n <= 2。代码如下:

int f(int n){
    if(n <= 2){
        return 1;
    }
}

第三要素:找出函数的等价关系式
题目已经把等价关系式给我们了,所以我们很容易就能够知道 f(n) = f(n-1) + f(n-2)。

nt f(int n){
    // 1.先写递归结束条件
    if(n <= 2){
        return 1;
    }
    // 2.接着写等价关系式
    return f(n-1) + f(n - 2);
}

案例2:小青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
第一要素:第一递归函数功能
假设 f(n) 的功能是求青蛙跳上一个n级的台阶总共有多少种跳法,代码如下:

int f(int n){

}

第二要素:找出递归结束的条件
求递归结束的条件,你直接把 n 压缩到很小很小就行了,因为 n 越小,我们就越容易直观着算出 f(n) 的多少,所以当 n = 1时,你知道 f(1) 为多少吧?够直观吧?即 f(1) = 1。代码如下:

int f(int n){
    if(n == 1){
        return 1;
    }
}

第三要素:找出函数的等价关系式
每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了。于是写出代码

int f(int n){
    if(n == 1){
        return 1;
    }
    ruturn f(n-1) + f(n-2);
}

当 n = 2 时,显然会有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,按道理是递归结束,不用继续往下调用的,但我们上面的代码逻辑中,会继续调用 f(0) = f(-1) + f(-2)。这会导致无限调用,进入死循环。
这也是我要和你们说的,关于递归结束条件是否够严谨问题,有很多人在使用递归的时候,由于结束条件不够严谨,导致出现死循环。也就是说,当我们在第二步找出了一个递归结束条件的时候,可以把结束条件写进代码,然后进行第三步,但是请注意,当我们第三步找出等价函数之后,还得再返回去第二步,根据第三步函数的调用关系,会不会出现一些漏掉的结束条件。就像上面,f(n-2)这个函数的调用,有可能出现 f(0) 的情况,导致死循环,所以我们把它补上。代码如下:

int f(int n){
    //经过分析,f(2)=2也是一个临界条件。
    if(n <= 2){
        return n;
    }
    ruturn f(n-1) + f(n-2);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值