文章插图
作者 | kunge
责编 | Elle
1.前言递归是算法中一种非常重要的思想,应用也很广,小到阶乘,再在工作中用到的比如统计文件夹大小,大到 google 的 PageRank 算法都能看到,也是面试官很喜欢的考点
最近看了不少递归的文章,收获不小,不过我发现大部分网上的讲递归的文章都不太全面,主要的问题在于解题后大部分都没有给出相应的时间/空间复杂度,而时间/空间复杂度是算法的重要考量!递归算法的时间复杂度普遍比较难(需要用到归纳法等),换句话说,如果能解决递归的算法复杂度,其他算法题题的时间复杂度也基本不在话下 。另外,递归算法的时间复杂度不少是不能接受的,如果发现算出的时间复杂度过大,则需要转换思路,看下是否有更好的解法 ,这才是根本目的,不要为了递归而递归!
本文试图从以下几个方面来讲解递归
- 什么是递归?
- 递归算法通用解决思路
- 实战演练(从初级到高阶)
2.什么是递归简单地说,就是如果在函数中存在着调用函数本身的情况,这种现象就叫递归 。
以阶乘函数为例,如下, 在 factorial 函数中存在着 factorial(n - 1) 的调用,所以此函数是递归函数
public int factorial(int n) {if (n < =1) {return 1;}return n * factorial(n - 1)}
进一步剖析「递归」,先有「递」再有「归」,「递」的意思是将问题拆解成子问题来解决, 子问题再拆解成子子问题,...,直到被拆解的子问题无需再拆分成更细的子问题(即可以求解),「归」是说最小的子问题解决了,那么它的上一层子问题也就解决了,上一层的子问题解决了,上上层子问题自然也就解决了,....,直到最开始的问题解决,文字说可能有点抽象,那我们就以阶层 f(6) 为例来看下它的「递」和「归」 。文章插图
求解问题 f(6), 由于 f(6) = n * f(5), 所以 f(6) 需要拆解成 f(5) 子问题进行求解,同理 f(5) = n * f(4) ,也需要进一步拆分,... ,直到 f(1), 这是「递」,f(1) 解决了,由于 f(2) = 2 f(1) = 2 也解决了,.... f(n)到最后也解决了,这是「归」,所以递归的本质是能把问题拆分成具有相同解决思路的子问题, 。。。直到最后被拆解的子问题再也不能拆分,解决了最小粒度可求解的子问题后,在「归」的过程中自然顺其自然地解决了最开始的问题 。
3.递归算法通用解决思路我们在上一节仔细剖析了什么是递归,可以发现递归有以下两个特点
- 一个问题可以分解成具有相同解决思路的子问题,子子问题,换句话说这些问题都能调用同一个函数
- 经过层层分解的子问题最后一定是有一个不能再分解的固定值的(即终止条件),如果没有的话,就无穷无尽地分解子问题了,问题显然是无解的 。
经过判断可以用递归后,接下来我们就来看看用递归解题的基本套路(四步曲):
- 先定义一个函数,明确这个函数的功能,由于递归的特点是问题和子问题都会调用函数自身,所以这个函数的功能一旦确定了, 之后只要找寻问题与子问题的递归关系即可
- 接下来寻找问题与子问题间的关系(即递推公式),这样由于问题与子问题具有相同解决思路,只要子问题调用步骤 1 定义好的函数,问题即可解决 。所谓的关系最好能用一个公式表示出来,比如 f(n) = n * f(n-) 这样,如果暂时无法得出明确的公式,用伪代码表示也是可以的, 发现递推关系后,要寻找最终不可再分解的子问题的解,即(临界条件),确保子问题不会无限分解下去 。由于第一步我们已经定义了这个函数的功能,所以当问题拆分成子问题时,子问题可以调用步骤 1 定义的函数,符合递归的条件(函数里调用自身)
- 将第二步的递推公式用代码表示出来补充到步骤 1 定义的函数中
- 最后也是很关键的一步,根据问题与子问题的关系,推导出时间复杂度,如果发现递归时间复杂度不可接受,则需转换思路对其进行改造,看下是否有更靠谱的解法
推荐阅读
- 全面介绍茶文化 中国茶茶具茶艺出版
- 徽州区,现代农业生产发展茶产业项目全面完工
- 10条网页设计干货,刚入行也能做好设计
- t恤|春季晴天鱼上浮,钓鱼经常空军,用这种方法野钓,渔获照样满满
- 短视频干货:短视频运营四大误区
- 干货!一次让你弄懂房贷
- 55寸液晶电视怎么选?全面屏、高音画、AI交互一个也不能少
- 我国茶业面临的共同课题是推动行业全面转型升级
- 梦见厕所满满的尿水坑 梦见大坑里的厕所脏水满满的
- 浙江松阳在全省率先全面推行实名制茶叶交易卡