动态规划(dynamic programming)与分治法类似。分治策略将问题划分为互不相交的
子问题,递归求解子问题,再将子问题进行组合,求解原问题。动态规划应用于子问题重叠
的情况,在这种情况下,分治法将会对重叠问题进行多次重复求解,而动态规划对每个子问题只求解一次
动态规划方法常用于求解**最优化问题(optimization problem)
**。这类问题可能有多个解,每个解有一个值,我们希望寻找具有最优值的解。
设计动态规划算法步骤:
例:钢条切割问题
假设钢条价格表
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格p | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
现在对于一根长度为n的钢条,求它能获得的最大效益r
注意到,每一次切割后将得到两根钢铁,下一步,又将这两根钢条看作两个独立的钢铁切割问题。通过组合子问题的最优解,选取组合收益最大者,构成原问题的最优解。钢条切割问题满足最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题都可以独立求解
。
对钢条问题,我们可以再进行简化。对于每次切割后得到的两段钢条,我们只对第二段进行再次切割,对第一段不再切割。
int CUD_ROD(int *p, int n) {
if (n == 0)return 0;
int q = INT_MIN;
for (int i = 1; i <= n;i++) {
if (p[i] + CUD_ROD(p, n - i) > q)q = p[i] + CUD_ROD(p, n - i);
}
return q;
}
输出价格数组p以及钢条长度n,返回最大收益r。
当n变大时,程序的运行时间会成倍增长,这是由于程序在递归调用时,对相同参数值进行了反复求解。例如,当n=5时,将会调用CUD_ROD(p, n=0 to 4)
,求解CUD_ROD(p, n=4)
时,又会调用CUD_ROD(p, n=0 to 3)
。整个程序中存在大量的重复求解过程。
朴素递归算法效率之所以低,是因为他在反复求解子问题。动态规划方法安排求解顺序,对每个子问题只进行一次求解,将其结果保存下来,再次遇到相同子问题时,只需要查询结果,而不需要重新计算。这是典型的时空权衡(time-memory trade-off)
。
仍按照自然递归的顺序进行编写,但过程中会保存子问题的解于数组r中。过程中首先查询子问题的解是否已经存在,若存在,直接返回解,不存在,则按常规方式进行求解。
int MEMOIZED_CUT_ROD_AUX(int *p, int n, int *r) {
if (r[n] >= 0) return r[n];
if (n == 0)return 0;
int q = INT_MIN;
for (int i = 1; i <= n; i++)
{
if (p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r) > q)
q = p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r);
}
r[n] = q;
return q;
}
自底向上法依次求解出r[1...n]
int BOTTOM_UP_CUT_ROD(int *p, int n) {
int *r1;
r1 = (int*)malloc(n * sizeof(int));
r1[0] = 0;
for (int j = 1; j <= n; j++) {
int q = INT_MIN;
for (int i = 1; i <= j; i++)
{
if (p[i] + r1[j-i] > q) q = p[i] + r1[j - i];
}
r1[j] = q;
}
return r1[n];
}
#include
#include
#include
#include
using namespace std;
int p[100]= { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };
int CUD_ROD(int *p, int n) {
if (n == 0)return 0;
int q = INT_MIN;
for (int i = 1; i <=n;i++) {
if (p[i] + CUD_ROD(p, n - i) > q)q = p[i] + CUD_ROD(p, n - i);
}
return q;
}
int* MEMOIZED_CUT_ROD(int n) {
int *r;
r = (int*)malloc( n * sizeof(int));
for (int i = 0; i <= n; i++)
{
r[i] = INT_MIN;
}
return r;
}
int MEMOIZED_CUT_ROD_AUX(int *p, int n, int *r) {
if (r[n] >= 0) return r[n];
if (n == 0)return 0;
int q = INT_MIN;
for (int i = 1; i <= n; i++)
{
if (p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r) > q)
q = p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r);
}
r[n] = q;
return q;
}
int BOTTOM_UP_CUT_ROD(int *p, int n) {
int *r1;
r1 = (int*)malloc(n * sizeof(int));
r1[0] = 0;
for (int j = 1; j <= n; j++) {
int q = INT_MIN;
for (int i = 1; i <= j; i++)
{
if (p[i] + r1[j-i] > q) q = p[i] + r1[j - i];
}
r1[j] = q;
}
return r1[n];
}
int main() {
int n = 18;
cout <<"朴素自顶向下递归法:" <<CUD_ROD(p, n) << endl;;
int *r=MEMOIZED_CUT_ROD(n);//初始化备忘 r
cout << "带备忘自顶向下法:" << MEMOIZED_CUT_ROD_AUX(p, n, r) << endl;
cout << "自底向上法:" << BOTTOM_UP_CUT_ROD(p, n);
system("pause");
return 0;
}