如果需要重复的多次计算相同的问题,通常会选择递归和循环两种方法。 递归是在函数内部调用该函数本身, 循环则是设置计算的初始值和终止条件,在一个范围内重复计算,如计算连续N个数之和。
递归
int add(int n){
return n<=0?0:n+add(n-1)
}
循环
int add(int n){
int result = 0;
for(int i=1;i<=n;i++){
result +=i;
}
return result;
}
从代码上可以看出递归相较于循环,代码更加的简介,更容易实现,但其缺点显著。由于递归是函数调用自身,而函数调用是有时间和空间的消耗的,每一次调用都需要在内存栈中分配空间已保存参数、返回地址及临时变量,且往栈里面压入数据和弹出数据都需要时间,这就不难理解递归的效率不如循环。
此外,递归中有可能很多计算都是重复计算,递归的本质是将一个问题分解成多个问题,如果多个问题存在相互重叠的部分,就会存在重复计算,接着以经典问题–斐波拉契数列 来往下进行。
问题:求斐波拉契数列的第N项。
写一个函数,输入N,求斐波那契数列的第N项。斐波拉契数列定义如下:
当N=0时,F(N) = 0
当N=1时,F(N) = 1
当N>1时,F(N) = F(N-1) + F(N-2)
递归解法:
long long Fibonacci(int n){
if(n<=0)
return 0;
if(n == 1)
return 1;
return Fibonacci(n-1) + Fibonacci(n-2)
}
过程分析:
当N=10时,若要求得F(10),需先求得F(9) 和 F(8)。同样,要求得F(9),则需先求F(8)和F(7),以此类推。为了清晰直观,以树来描述,基于递归求斐波拉契数列的第10项的调用过程如下:
从图中不难发现,这个树中的很多节点都是重复的,且重复的节点会随着N的增大而急剧增加。
优化思路-循环,从下往上计算,先根据F(0)和F(1)计算出F(2),再根据F(1)和F(2) 计算出F(3),以此类推就可以计算出F(N),时间复炸度为O(n)。
long long Fibonacci(int n){
int result[2] = {0,1};
if(n<2)
return result[n];
long long fibOne = 1; //N的前一项
long long fibTwo = 0; //N的前二项
long long fibN = 0;
for(int i=2;i<n;i++){
fibN = fibOne + fibTwo;
fibTwo = fibOne;
fibOne = fibN;
}
return fibN;
}
扩展1:青蛙跳台阶问题
一只🐸一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法。
分析: 假设只有一级台阶,那么,🐸只有一种跳法,若是两级台阶,那就有两种跳法,N级台阶呢,N级考虑两种情况:
一是第一次只跳一级,此时跳法数目等于剩下的n-1阶台阶的跳法数目;
二是第一次跳两级,此时跳法数目等于剩下的n-2阶台阶的跳法数目;
将其抽象为数学模型即为:
当N=1时,F(N) = 1
当N=2时,F(N) = 2
当N>2时,F(N) = F(N-1) + F(N-2)
可以看出,这就是一个斐波拉契数列问题。
扩展2:用2 × 1的小矩形覆盖一个2 × N的大矩形,总共有多少种方法。
此题也是斐波拉契数列的一种演变。