动态规划也是一种分治思想,与分治法不同的是:分治法是把原问题分解为若干子问题,由上而下解决,求解子问题,合并子问题的解从而得到原问题的解。动态规划也是一种自顶向下把原问题分解为子问题,不同的是,然后自底向上,向求解最小子问题,把结果粗出在表格中,在求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高效率。
例如有一道例题
每次可以上1阶台阶,也可以上2阶台阶,求走一个n级台阶共有多少种走法。
我们先用分治法实现。
假设要走5级台阶,按照分治法思想,最后一步走1阶,前4级有多少种走法;或者左后一步走2阶,前3级有多少种走法。
我们将求n级台阶共有多少种走法用f(n)表示,则f(n)=f(n-1)+f(n-2)
由上可得 f(5) = f(4) +f(3)
对于边界情况 f(1)=1 f(2) = 2
#include<iostream>
using namespace std;
int WalkCout(int n) {
if (n < 0) return 0;
if (n == 1) return 1; //一级台阶,一种走法
else if (n == 2) return 2; //二级台阶,两种走法
else { //n 级台阶, n-1 个台阶走法 + n-2 个台阶的走法
return WalkCout(n - 1) + WalkCout(n - 2);
}
}
int main(void) {
cout << "5级台阶共有:" << WalkCout(5) << "种走法" << endl;
system("pause");
return 0;
}
运行结果
当我们将台阶级数改为40,并且增加一个时间戳计算一下时间
#include<time.h>
time_t begin, end;
time(&begin);
cout << "5级台阶共有:" << WalkCout(40) << "种走法" << endl;
time(&end);
cout << "用时为:" << end - begin << endl;
运行结果
这种计算方法会有很多的重复计算
例如在进行迭代时 f(4) =f(3) +f(2) =f(2) +f(1) +f(2) f(3) =f(2) +f(1) 。f(2) +f(1)在之后的每次计算中都会被重复计算一次
如果我们从下往上推
f(1) =1
f(2) =2
f(3) = f(1) + f(2) =3
f(4) = f(3)+f(2) =3+2=5
#include<iostream>
#include<time.h>
using namespace std;
int WalkCout(int n) {
if (n < 0) return 0;
if (n == 1) return 1; //一级台阶,一种走法
else if (n == 2) return 2; //二级台阶,两种走法
int* value = new int[n + 1];
value[0] = 0;
value[1] = 1;
value[2] = 2;
for (int i = 3; i <= n; i++)
value[i] = value[i - 1] + value[i - 2];
int ret = value[n];
delete value;
return ret;
}
int main(void) {
time_t begin, end;
time(&begin);
cout << "5级台阶共有:" << WalkCout(40) << "种走法" << endl;
time(&end);
cout << "用时为:" << end - begin << endl;
system("pause");
return 0;
}
运行结果
什么时候要用动态规划?
如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子 问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。
怎么使用动态规划?
五步曲解决:
1. 判题题意是否为找出一个问题的最优解
2. 从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题
3. 从下往上分析问题 ,找出这些问题之间的关联(状态转移方程)
4. 讨论底层的边界问题
5. 解决问题(通常使用数组进行迭代求出最优解)