爬楼梯问题
有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?
一个可以运行的程序,解释在后边:
#include "stdio.h"
int f(int x){
if(x==1)
return 1;
if(x==2)
return 2;
return (f(x-1)+f(x-2));
}
int main(){
int n;
scanf("%d",&n);
printf("%d",f(n));
return 0;
}
方法一,直接思考问题:
设f(x)=【剩x阶时,迈楼梯的方法总数】。首先迈出第一步,如果一次迈一阶,剩下x-1阶,方法总数为f(x-1);如果一次迈两阶,剩下x-2阶,方法总数为f(x-2);这里f(x-1) f(x-2)都是我们不知道的。这里只是找出一个递推关系式。即f(x)=f(x-1)+f(x-2)。容易发现,f(1)=1,f(2)=2。
方法二,抽象成数学问题,找出递推关系式:
如果x为1时,只可以迈一步,共有一种方法。
如果x为2时,可以一次迈一步,也可以一次迈两步,共有两种方法。
如果x为3时,可以先1,后2;可以先2,后1;也可以1,1,1;共有三种方法。满足f(1)+f(2)。
如果x为4时,1,1,1,1; 1,2,1; 1,1,2; 2,1,1; 2,2; 共有五种方法。满足f(2)+f(3)。
......
发现规律:f(x)=f(x-2)+f(x-1);
总结:
做递归函数时,我们不妨先从较小的数开始做实验,找出潜在的数学规律,抽象成数学问题。【即找出递推关系式】。这样比直接思考问题要简单一些。
一个楼梯有20级,每次走1级或两级,请问从底走到顶一共有多少种走法?
分析:假设从底走到第n级的走法有f(n)种,走到第n级有两个方法,一个是从(n-1)级走一步,另一个是从第(n-2)级走两步,前者有f(n-1)种方法,后者有f(n-2)种方法,所以有f(n)=f(n-1)+f(n-2),还有f(0)=1,f(1)=1.
递归编程实现
程序1
#include <stdio.h>
int f(int n)
{
if(n==0 || n==1) return 1;
else return f(n-1)+f(n-2);
}
int main()
{
printf("%d\n",f(20));
return 0;
}
现在来说说动态规划的基本思想
动态规划的关键是发现子问题和怎么记录子问题,以上面的例子说明
(1)对子问题可递归的求解,当n>1时,f(n)=f(n-1)+f(n-2);否则,f(1)=f(0)=1;
(2)这些子问题是有重叠的,即求解某个问题时,某些子问题可能需要求解多次。例如求解f(5)时,f(2)就被求解了3次。
在上面两个条件下,用动态规划的方式来求解会高效很多。就是把子问题记录下来,每个子问题只求解一次,从而提高了效率。
程序2
#include <stdio.h>
int result[100];
int f(int n)
{
int res;
if(result[n]>=0) return result[n];
if(n==0 || n==1) res=1;
else res=f(n-1)+f(n-2);
result[n]=res;
return res;
}
int main()
{
int i;
for(i=0;i<=20;i++)
result[i]=-1;
printf("%d\n",f(20));
return 0;
}
程序3
#include <stdio.h>
int f[100];
int main()
{
int i;
f[0]=1;
f[1]=1;
for(i=2;i<=20;i++)
f[i]=f[i-1]+f[i-2];
printf("%d\n",f[20]);
return 0;
}
程序3是否让你想起了那个兔子繁殖的问题呢?
小结一下
动态规划,采用分治的策略,把求最优解问题分解为求若干子问题的最优解,记录子问题的解,化繁为简,很实用,也很高效。