很多算法都可以用递归和循环两种不同方式实现,通常基于递归的实现方法代码会比较简洁,但性能不如基于循环的实现方法。
递归是在一个函数内部调用这个函数自身。循环是通过设置计算的初始值及终止条件,在一个范围内重复运算。如求1+2+…+n:
#include <iostream>
using namespace std;
int AddFrom1ToN_Recursive(int n) {
return n ? (n + AddFrom1ToN_Recursive(n - 1)) : n;
}
int AddFrom1ToN_Iterative(int n) {
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += i;
}
return sum;
}
int main() {
cout << AddFrom1ToN_Iterative(50) << " " << AddFrom1ToN_Recursive(50) << endl;
return 0;
}
递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。以上例子中,递归效率不如循环。除了效率外,递归还有可能引起更严重的问题:调用栈溢出,当调用的层级太多时,就会超出栈的容量,造成栈溢出。
通常应用动态规划解决问题时我们都是用递归的思路分析,但由于递归分解的子问题中存在大量重复,因此我们总是用自下而上的循环来实现代码。
面试题10:斐波那契数列。(1)写一个函数,输入n,求斐波那契数列的第n项。
很多人会用递归来解:
#include <iostream>
using namespace std;
int Fibonacci(int n) {
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main() {
cout << Fibonacci(2) << endl;
return 0;
}
但这种解法效率极低,当我们求解f(10)时,我们需要先求f(9)和f(8),在求解f(9)时,需要求解f(8)和f(7),分析后不难发现,很多值我们求了多次(如上例的f(8)),而且重复的计算会随着n的增加急剧增大。用递归方法计算的时间复杂度是以n的指数方式递增的。
改进方法:从下往上计算,时间复杂度为O(n):
#include <iostream>
using namespace std;
int Fibonacci(int n) {
int result[] = { 0,1 };
if (n <= 1) {
return result[n];
}
int a = 0, b = 1, FibN;
for (int i = 0; i < n - 1; ++i) {
FibN = a + b;
a = b;
b = FibN;
}
return FibN;
}
int main() {
cout << Fibonacci(4) << endl;
return 0;
}
还有一种时间复杂度为O(logn)的解法,这个解法需要用到一个公式:
有了这个公式,求出方阵的n-1次方即可。如果简单地从0开始循环,n次方需要n次运算,时间复杂度仍为O(n),但我们可以考虑当n-1为偶数时,只需要求(n-1)/2次方的平方即可,这样可以用递归的思路实现。
(2)青蛙跳台阶问题。一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。
首先考虑最简单的情况,如果只有一级台阶,只有一种跳法;如果有两级台阶,有两种跳法。当台阶数为n时,青蛙能从第n-1阶台阶或n-2阶台阶跳上来,此时跳法数等于f(n-1) + f(n-2),这就是斐波那契数列了。
在青蛙跳台阶的问题中,如果条件改成一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,…他也可以跳上n级台阶,此时青蛙跳上n级台阶的方法数为2n-1:
f(n) = f(n-1) + f(n-2) + ... + f(1) + 1
f(n-1) = f(n-2) + f(n-3) + ... + f(1) + 1
f(n) - f(n-1) = f(n-1)
即f(n) = 2f(n-1)
又因为f(1) = 1,因此方法总数为2^(n-1)。
以上是一个2*1的矩形和一个2*8的矩形,求用8个2*1的小矩形无重叠地覆盖2*8的大矩形时,有多少种方法。
我们可以横着覆盖或竖着覆盖,当我们竖着覆盖最左边时,右边还剩下2*7的矩形,这种情形下覆盖方法记为f(7);当我们横着覆盖左上角时,左下角也就确定是横着覆盖,右边还剩下2*6的矩形,此时覆盖方法数为f(6)。因此f(8)=f(6)+f(7),仍然是斐波那契数列问题。