在非负整数集上定义一个函数f,它知足f(0)=0,且f(x)=2f(x-1)+x^2.从这个定义能够看出f(1)=1,f(2)=6,f(3)=21,f(4)=58。当一个函数用自身定义时就称为递归(recursive).即,一个函数直接或间接地调用自身,是为直接或间接递归。C++是容许递归的。但必须记住,C++所作的仅仅是试图遵循递归的思想。不是全部的数学递归函数都能有效的用C++递归模拟来实现。要点在于,递归函数f应该像非递归函数同样只用几行代码就能表示出来。下图给出了函数f的递归实现。面试
1 int f(int x) { 2 if (x == 0) 3 return 0; 4 else 5 return 2 * f(x - 1) + x * x; 6 }
第2行和第3行处理基准状况(base case),即此时函数的值能够直接算出来而不用递归。正如在没有f(0)=0的前提下。声称f(x) = 2f(x - 1) + x^2.在数学上没有意义同样。C++的递归方法若无基准状况也是毫无心义的。第5行执行的是递归调用。算法
编写递归程序的时候,关键是要牢记递归的四条基本法则:编程
递归和循环数据结构
若是咱们要重复地屡次计算相同的问题,一般能够选择用递归或者循环两种不一样的方法。递归是在一个函数的内部调用这个函数自身。而循环这是经过设置计算的初始值及终止条件,在一个范围内重复计算。好比求1+2+3+...+n,咱们能够用递归或者循环两种方式求出结果。对应的代码以下:函数
int AddFrom1ToN_Recursive(int n) { return n <= 0 ? n + AddFrom1ToN_Recursive(n - 1); } int AddFrom1ToN_Iternative(int n) { int result = 0; for (int i = 0; i <= n; ++i) result += i; return result; }
一般递归的代码比较简洁。在上面的例子中,递归的代码只有一个语句。而循环的则须要四个语句。在树的前序、中序、后序遍历算法的代码中,递归的实现明显要比循环简单的多。性能
面试小提示:spa
一般基于递归的代码要比基于循环实现的代码要简洁不少,更加容易实现。若是面试官没有特殊要求,应聘者能够优先采用递归的方法编程。.net
递归虽然有简洁的优势,但它同时也有显著的缺点。设计
递归因为是函数调用自身,而函数调用是有空间和时间的消耗的:每一次函数调用,都须要在内存栈中分配空间以保存参数、返回的地址及临时变量,并且往栈里压入数据和弹出数据都须要时间。这就不难理解上述的例子中递归实现的效率不如循环。code
另外,递归中有可能不少计算都是重复的,从而对性能带来很大的负面影响。递归的本质是把一个问题分解成两个或多个小问题。若是多个小问题存在相互重叠的部分,那么就存在重复的计算。
除了效率之外,递归还有可能引发更严重的问题:调用栈溢出。前面分析中提到须要为每一次函数调用在内存栈中分配空间,而每一个进程的栈的容量是有限的。当递归调用的层级太多时,就会超出栈的容量,从而致使栈溢出。在上述的例子中,若是输入的参数比较小,如10,它们都能返回结果55.但若是输入的参数很大,好比5000,那么递归代码在运行的时间就会出错,但运行循环的代码能获得正确的结果12502500.
相关资料:
1. http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=recursionPt1
2. http://www.nowamagic.net/librarys/veda/detail/2314
参考资料:
1. 《数据结构与算法分析C++描述》 Mark Allen Weiss
2. 《剑指offer》 何海涛