动规
动规就是动态规则的意思。前面讲到
分治算法是把大问题分解成多个独立的小问题来求解,但很多问题分解后子问题并不是独立的,或者说如果要分解成独立的子问题,子问题太多,这样反而降低了性能。在这些不独立的子问题中,有很多数据被重复计算,其实这些重复计算的数据可以通过一个表达式来保存,就可以避免很多重复的计算。
动规用一个表来记录计算过的子问题的答案,通过填表的方式来求得最终的节点。在填表的过程中,需要用到前面的结果,通过最优值的表达式来计算下一步的结果。动态规划可以解决很多最优值的问题,动规问题的基本求解过程如下
(1)根据最优解的性质,构造求解表达式
(2)确定范围
(3)自底向上计算最优值
(4)根据求得的最优值,构造最优解
其中最难的就是确定最优值的关系,最优值的关系构造时基本都是基于递归的,然后从低向上构造最优解。要用动规来解决问题,就必须要构造最优值之间的关系,但不是所有的问题的最优值都能建立起关系,只有具有最优子结构的问题才能构造出最优值之间的关系。最优子结构是指如果用子问题求出的最优解来求父问题的解一定也是最优的。比如说求从A到C的最短路径,而A到C必须经过B,那么求出A到B的最短路径和B到C的最短路径就一定能求出A到C的最优路径。
用动规解决问题还需要问题满足重叠子问题的性质。前面讲到动态规划解决的子问题之间可以并不是独立的,也就是有些子问题是被重复计算的,那么如果把这些重复的子问题保存下来,要用时直接取而不是重新计算的话,就能大大节约效率。这种子问题会重复计算的特性就叫重叠子问题。
对于0-1背包问题,如果背包不能分割,就不能使用贪心算法,而要使用动规来解决。0-1背包问题这样描述:有n种物品和一背包,物品i的重量是wi,价值为vi,背包的容量为c,怎么选择物品来使疚装入背包中的物品总价值最大。对于0-1背包问题,每个物品只有两种状态,要为装入背包、要么不装入背包。设xi为0表示物品i不装入背包,xi为1表示物品i装入背包,则背包中装入物品的总价值为v0x0+v1x1+v2x2+...+v(n-1)i(n-1)中的最值,崦w0x0+w1x1+w2x2+...+w(n-1)x(n-1)<=c
设d(i,j)表示背包容量为j,可选物品为i,i+1,i+1,...,n是背包的最大价值,那么d(i,j)=max{d{i-1,j},d(i-1,j-wi)+vi}[j>=wi],d(i,j)=d(i-1,j)[0<=j<wi],第一个式子表示如果如果背包的剩余容量为wi时,也就是还可以放入i物品时,如果可以还物品i的价值为选择或者不选择i中的较大的值,这个d(i,j)的值是可以动态变化的,这也是动规的名字的由来,动规的每个值都是根据当前的情况选择最优的解,如果后来发现当前值不是最优时,就会动态地修改。d(i,j)表示在选择i物品时,背包为j的最大价值。
#pragma once #ifndef DP_H #define DP_H namespace algorithm { void ZeroOnePackage(int *vs,int *ws,int c,int n,int **d) { for (int j = 1;j <= c;j++) { if(j>=ws[0]) { d[0][j] = vs[0]; } } for (int i = 1;i < n;i++) { for (int j = 1;j <= c;j++) { if (j<ws[i]) { d[i][j] = d[i - 1][j]; } else { if(d[i-1][j-ws[i]]+vs[i]>d[i-1][j]) { d[i][j] = d[i - 1][j - ws[i]] + vs[i]; } else { d[i][j] = d[i - 1][j]; } } } } } } #endif
// Algorithm.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include "dp.h" using namespace std; using namespace algorithm; int main() { int n = 5; int ws[] = {2,2,6,5,4}; int vs[] = {6,3,5,4,6}; int **d = new int*[n]; int c = 10; for (int i = 0;i < n;i++) { d[i] = new int[c+1]; } ZeroOnePackage(vs, ws, c, n, d); cout << d[n-1][c]; system("pause"); return 0; }