递归算法实例应用(四)
爬楼梯 (POJ 4017)
Description
树老师爬楼梯,他可以每次走1级或者2级,输入楼梯的级数,求不同的走法数
例如:楼梯一共有3级,他可以每次都走一级,或者第一次走一级,第二次走两级
也可以第一次走两级,第二次走一级,一共3种方法。
Input
输入包含若干行,每行包含一个正整数N,代表楼梯级数,1 <= N <= 30
Output
不同的走法数,每一行输入对应一行输出
Sample Input
5
8
10
Sample Output
8
34
89
算法思想:
首先,假设n充分大时,从分析第一步应如何走开始,第一步可以分为两种情况,即上一层台阶和上两层台阶;接下来考虑第二步应该怎么走,第二步与第一步一样也是可以分为两种情况,但是剩余台阶数相比于第一步减少了一层或两层;第三步同理,直至某一步时,使得台阶正好走完。
从上面分析可知,随着每一步的走法确定之后,问题规模的台阶数就会变少一层或两层。所以该问题就是一个非常明显的用递归将问题规模分解为规模更小的子问题进行求解的问题。
那么该问题的递归公式可以表示为:
n级台阶的走法 = 先走1级后n-1阶台阶的走法 + 先走2级后n-2级台阶的走法
接下来考虑递归的终止条件,输入数据规模为1 <= N <= 30,通过递归公式的逐层递归推导,可以发现几种不同的终止情况
f(n)=0,n<0
f(n)=1,n=0
对于每一个n,要么由上一层的N-1而来,要么由N-2而来:
- 当n<0时,说明上一层的N-1或N-2的走法不成立,所以应返回0,表明不可行,即N-1或N-2有0种走法。
- 当n=1时,说明上一层N-1或N-2走法使得仅剩下一层台阶要走,所以应仅有一种走法,即一步一个台阶。
f(n)=1,n=1
f(n)=1,n=0
同理,对于每一个n,要么由上一层的N-1而来,要么由N-2而来:
- 当n=0时,说明上一层的N-1或N-2的走法正好走完整个楼梯,所以该层已经不需要走了,故应返回0。
- 当n=1时,说明上一层N-1或N-2走法使得仅剩下一层台阶要走,所以应仅有一种走法,即一步一个台阶。
f(n)=1,n=1
f(n)=2,n=2
同理,对于每一个n,要么由上一层的N-1而来,要么由N-2而来:
- 当n=1时,说明上一层N-1或N-2走法使得仅剩下一层台阶要走,所以应仅有一种走法,即一步一个台阶。
- 当n=2时,说明上一层N-1或N-2走法使得仅剩下两层台阶要走,所以应仅有两种走法,即一步一个台阶,或一步两个台阶。
以上便列举了三种不同的终止条件,读者选择一种作为函数终止条件即可。对于终止条件的分析,应从解的递归函数出发,分析其可能终止或异常的几种状态,罗列下终止状态后,判断是否涵盖在此递归函数中所有可能出现的终止或异常状态。因为算法应涵盖在题设的输入条件下,所有可能的解的情况,保证函数的健壮性。
代码逻辑:
本题的递归递归函数和终止条件均较为简单,所以代码逻辑不再赘述。
代码整合:
int main() {
//爬楼梯
int n;
while (scanf("%d", &n)) {//读入每一个输入数据
printf("%d\n", Stairs(n));//每读入一个数据就输出该问题规模下的解
}
return 0;
}
int Stairs(int n) {
//终止条件
if (n < 0)
return 0;
if (n == 0)
return 1;
//递归公式
return Stairs(n - 1) + Stairs(n - 2);
}
放苹果 (POJ 1664)
Description
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
Input
第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。
Output
对输入的每组数据M和N,用一行输出相应的K。
Sample Input
1
7 3
Sample Output
8
算法思想:
由题设输入可分为两种情况,
- 盘子数n > 苹果数m,此时必定至少有n-m个盘子为空,所以问题规模此时就变为将n个苹果放在n个盘子里。
盘子数n ≤ 苹果数m,此时苹果有两种放法:
有空盘放法
- 若有一个空盘,则问题规模变为,将m个苹果放在n-1个盘子里
- 若有两个空盘,则问题规模变为,将m个苹果放在n-2个盘子里
- 若有..个空盘,则问题规模变为,将m个苹果放在...个盘子里
- 若有n-1个空盘,则问题规模变为,将m个苹果放在1个盘子里
无空盘放法
- 把n个盘子各放一个苹果,再将剩余的m-n个苹果放在n个盘子里,问题规模再次缩减,此时剩余的m-n个苹果可以在n个盘子里随便放。
- 所以,综上当盘子数≤苹果数时,放法个数为又空盘放法与无空盘放法之和。
从以上分析可知,该问题也是一个用递归将问题规模分解为规模更小的子问题进行求解的问题。
若用f(m,n)表示m个苹果放在n个盘子里的放法,那么该问题的递归公式可以表示为:
$$ f(m,n)= \begin{cases} f(m,m),& n>m \\ \\ f(m,n-1)+f(m-n,n),& n\le m \\ \end{cases}\tag1 $$
接下来考虑递归的终止条件,通过递归公式的逐层递归推导,可以发现几种不同的终止情况
$$ f(m,n)= \begin{cases} 1,& m=0 \\ \\ 0,& n=0 \\ \end{cases}\tag2 $$
即,当盘子数为0时,没有放法可以盘子题设要求,故解为0,当苹果数为0时,只有每个盘子都不放苹果这一种解法。
而且苹果为0时的边界优先级较盘子为0时的边界判定更高!
代码逻辑:
本题的递归递归函数和终止条件均较为简单,所以代码逻辑不再赘述。
代码整合:
int main() {
//放苹果
int m, n, count;
scanf("%d", &count);
while (count--) {
scanf("%d%d", &m, &n);
printf("%d\n", SetApple(m, n));//每读入一组数据就输出该问题规模下的解
}
return 0;
}
int SetApple(int m, int n) {
if (n > m) {
return SetApple(m, m);//盘子数大于苹果数,简化问题规模
}
if (m == 0) {//苹果数为0,只有一种全不放的放法,到达递归终止点
return 1;
}
if (n == 0) {//盘子数为0,此时无解,到达递归终止点
return 0;
}
//当盘子数≤苹果数时,为有空盘子放法与无空盘子放法之和
return SetApple(m, n - 1) + SetApple(m - n, n);
}
By Ss1Two 2023/01/17