题目
前几天看了一到需要用到递归函数的的题目如下
下面是输入输出样例
递归的解法
由于在题目中明确提到了 递归 二字,我首先的思路也是往递归这方面去靠。
下面就是构造其数学表达式
我们令F(n)表示第n年的母牛总数,则F(n)应该等于上一年母牛总数即F(n-1)加上上一年产出的牛数。显然上一年产出的牛数很难用函数F进行表示,又由于每一年产出的牛数等于其该年可进行生产的母牛数,因此我们考虑引进另外一个函数G(n)表示第n年可进行生产的母牛数。
由上我们可以得到第一个表达式:
F(n) = F(n - 1) + G(n - 1)
然后我们考虑G(n)这个函数怎么构造。
根据题目中的信息,我们知道每头小牛从第四个年头开始进行生产。显然第n年可进行生产的母牛数应该等于n-1年可进行生产的母牛数加上该年刚好发育成熟,可进行生产的牛数。
而新发育成熟的牛数刚好是两年前新生产下来的牛数即 F(n - 2) - F( n - 3)
因此我们得到关于G(n)的表达式:
G(n) = G(n - 1) + F(n - 2) - F(n - 3)
下面列出F(n)和G(n)的前几项数值作为递归的基底:
年份 | F(n) | G(n) |
---|---|---|
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 1 |
4 | 4 | 2 |
然后我们就可以根据以上的思路写出 F(n) 和 G(n) 的代码:
int F(int n){
if(n < 5){
return n;
}else{
return F(n-1)+G(n-1);
}
}
int G(int n){
if(n < 4){
return 1;
}else{
return G(n-1)+F(n-2)-F(n-3);
}
}
然后关于实现题目中的输入输出要求,读者可自行实现。
存在的问题
我在第一次交代码的时候,判题系统显示超时。
下面我们以求第7年牛的总数,分析其时间复杂度。
- 首先在 F(7) 中需要调用 G(6) 与 F(6)
- 在 F(6) 中需要调用 G(5)与 F(5) ;在 G(6) 中需要调用 G(5) 与 F(4) 与 F(3)
- …
- 在调用过程中最后得到可直接返回的 F(n) (即满足n小于5的时候)与 G(n) (既满足n小于4的时候),依次返回
- 逐步返回函数值,最后得到 F(7) 的值
在这个例子中我们可以看到:每次调用F,又要再次调用两个函数,每次调用G,则要调用三个函数。
因此当n比较大时,完成运算的时间十分长。
另外一种解法——循环
循环解法的思路也是根据上面提到的两个表达式:
F(n) = F(n - 1) + G(n - 1)
G(n) = G(n - 1) + F(n - 2) - F(n - 3)
根据这两个表达式的递推关系,我们可以考虑分别构造数组:
int F[60] = {1,2,3,4,6}; //完成数组的构造以及前几项的赋值
int G[60] = {1,1,1,2,3};
由于题目已经规定输入n的范围是 0 < n < 55,在定义数组大小时比55稍大即可。然后就是用for循环依次对数组中的变量进行赋值:
int i;
for (i = 5; i < 60; i++) {
F[i] = F[i - 1] + G[i - 1];
G[i] = G[i - 1] + F[i - 2] - F[i - 3];
}
然后即可得到第n年的牛数F[n - 1]。
小结
通过以上分析我们可以得知,用递归的方法解决问题的关键是找到其递推关系,其余的步骤都十分好下手。但当其输入的参数十分大时,由于函数的反复调用,极易造成时间过长,甚至出现 栈满 的情况。
而用循环的解法,虽然在题目没有给定参数范围的情况下数组的大小难以确定,但其有着线性的时间复杂度,运算快捷的优势,运行效率高于用递归函数的解法。