图片来源于网络
一上来你肯定觉得读这句话好绕,好吃力。
其实你用递归来读就很简单了:
递归要有一个终点(小鲤鱼)
当递归尚未达到终点的时候,函数会反复调用自己。
显然,输出"我的小鲤鱼"这句话是递归终止条件。
那么写成代码就是:
#include <stdio.h> void Recursion(int depth){ printf("抱着"); if (!depth) printf("我的小鲤鱼"); else Recursion(--depth); printf("的我"); } int main(){ printf("吓得我抱起了\n"); Recursion(2); putchar('\n'); }目前我找到的对递归最恰当的比喻,就是查词典。我们使用的词典,本身就是递归,为了解释一个词,需要使用更多的词。当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词,可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。。。
看了楼上很多答案,大多偏重于描述递归的现象,而没说明为什么要用递归,递归的思想到底是什么。前阵子刚好看了点东西,试着整理下,如有错误之处,请不吝指正。
- 什么是递归?
Wiki [1]:Recursion is the process of repeating items in a self-similar way.具体到计算机中去 [2]:
递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
英文的Recursion从词源上分析只是"re- (again)" + "curs- (come, happen)" 也就是重复发生,再次重现的意思。 而对应的中文翻译 ”递归“ 却表达了两个意思:”递“+”归“。 这两个意思,正是递归思想的精华所在。从这层次上来看,中文翻译反而更达意。
2. 跟循环的区别
单看上面wiki的定义,貌似跟通常所说的无限死循环很像,他们的区别在哪?递归是静中有动,有去有回。
循环是动静如一,有去无回。
举个例子,给你一把钥匙,你站在门前面,问你用这把钥匙能打开几扇门。
递归:你打开面前这扇门,看到屋里面还有一扇门(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开,。。。, 若干次之后,你打开面前一扇门,发现只有一间屋子,没有门了。 你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这钥匙开了几扇门。
循环:你打开面前这扇门,看到屋里面还有一扇门,(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,(前面门如果一样,这门也是一样,第二扇门如果相比第一扇门变小了,这扇门也比第二扇门变小了(动静如一,要么没有变化,要么同样的变化)),你继续打开这扇门,。。。,一直这样走下去。 入口处的人始终等不到你回去告诉他答案。
3. 递归思想
递归就是有去(递去)有回(归来)。
具体来说,为什么可以”有去“?
这要求递归的问题需要是可以用同样的解题思路来回答类似但略有不同的问题(上面例子中的那一把钥匙可以开后面门上的锁)。
为什么可以”有回“?
这要求这些问题不断从大到小,从近及远的过程中,会有一个终点,一个临界点,一个baseline,一个你到了那个点就不用再往更小,更远的地方走下去的点,然后从那个点开始,原路返回到原点。
递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。
4. 什么时候需要用递归?
当有些问题的定义本身就是递归形式的时候,最是适合用递归来解决。
计算机专业的同学最最熟悉的莫过于”树“的定义了[4,5]。还有一些定义,比如阶乘,Fibonacci数列[6],等等。用递归来解决这些问题,往往几行代码就搞定了一些看起来相当”吓人“的问题。 当然,递归的性能问题是另一回事,栈的分配,函数调用代价都是在具体工程实践中要考虑的。 但现在只是讨论递归思想的话,不妨先放下那些,欣赏下递归的美。
但是递推有时候真的比递归快啊~(逃