首先介绍下动态规划算法,他和分治法有点相似但又不同,两者都是将问题划分为若干个子问题进行结决,但是动态规划算法划分的子问题之间是有关联的,后一步的问题的解会建立在之前解决的问题解之上。就是后面的结果需要参照前面的结果,我们用最简单的01背包问题带入。
01背包问题,一个主播要进行直播带货,但是他只能带4000磅的货,多了库存放不下,然后每种商品只能选择一件,有1.圆珠笔一件1000磅,卖15k, 2.皮包一件4000磅,30k, 3.红酒一件,20k,然后求最优解。
首先分析问题,这个问题你做的话很简单但是要让计算机处理就有点难度了,动态规划算法,就是将问题划分成小问题,那我们就可以将其变成先求加第一件物品的最大值,然后将第二件物品加入的最大值,以此类推。最后就会得到我们将3件商品同时加入时的最大值。
我们通过填表来分析
我们来解释一下为什么这么写,首先第一横行第一竖行为0,无争议。
然后加入第一件商品圆珠笔
然后因为只有自己一个所以1000磅填自己,2000,3000,4000,还填自己的价格。
然后加入第二件商品皮包。但是皮包有4000磅前3个值都超重了,所以继承上个子问题的解,最后因为皮包价格大于圆珠笔的价格这里填皮包价格。
最后关键部分来了,我们的第三件商品加入,前两个因为超重继承上代子问题的值,3000磅时因为有更优解所以填入20k的红酒,最后一步,此时我们只关心与上代子问题的解30k谁更大的问题,明显4000=3000+1000所以可以将红酒和圆珠笔同时装入,得到35k>30k得到此问题的最优解。
那么能不能转换成数学语言描述呢?也是可以的,我们就根据填表过程制订一个最高值令,这个表示是不是与我们的二维数组极其相似。所以我们就用二维数组来表达它:
(1)定义一个二维数组v[ i ][ j ],i为当前物品代号,j为重量,表示我们寻求最优解值的数组,w[ ]数组表示当前物品的重量,val[]数组表示当前物品的价值,然后v[0][ j ]=v[i][0]=0;
-----------将数组第一行第一列初始化为0;(为了我们代码好写)
(2)w[i]>[j],v[i][j]=v[i-1][j]
(3)w[i]<=j, v[ i ][ j ]=max(v[i-1][ j ],val[i]+v[i - 1] [ j - w[ i ] ])
分析第二步内容,若当前的重量小于将要放入物品的重量,则继承他的上一步的子问题的解
分析第三部,若当前重量大于或等于将要放入的物品的重量,则将他的价格加上上一步子问题刨去它的重量的价格与上一步在此重量下的子问题进行比较,谁大填谁。可以根据这3步尝试去自己填表,看能否得到这个表,那么表填完之后我们的任务其实就算完成大半了(已经有最优解了)。剩下的就是将装入策略提取。
装入策略提取,我们再建立一个二维数组作为记录数组,p[ i ][ j ]; 之后将第三步每一次的最优解都存入这个数组。最后有意思的就来了,我们要怎么把它作为记录数组取出来呢?首先我们分析这个数组,他最后放入的元素必定是最优解,因为他的判定是 v[ i ][ j ]=max(v[i-1][ j ],val[i]+v[i - 1] [ j - w[ i ] ])只有比这个值大才会存进数组,所以他的最后一个不为空的元素必定是这个问题的最优解。所以我们通过逆向遍历该数组就会得出最优解。
那么我们通过java代码详细描述一下这个问题的解法;
public static void main(String[] args) {
int[] w = {1, 4, 3};//表示物品重量单位k
int[] val = {15, 20, 30};//物品价值单位k
int m = 4;//为仓库最大容量单位k
int n = val.length;
int[][] v = new int[n + 1][m + 1];
int[][] p = new int[n + 1][m + 1];
//初始化我们不做了,因为创建结束后数组内容里面本来就是0
/*最高值令第一步初始化数组
for(int i=0;i<v.length;i++){
v[i][0]=0;
}
for(int i=0;i<v[0].length;i++){
v[0][i]=0;
}
*/
//第二步和第三步
for (int i = 1; i < v.length; i++) {
for (int j = 1; j < v[0].length; j++) {
//2.若当前物品重量大于当前填表所能放入的重量直接继承上个子问题的值
if (w[i - 1] > j) {
v[i][j] = v[i - 1][j];
}
//第三步
//比较当前物品加上减去当前物品重量所得装入策略最优子问题的值与上个此处最优值进行比较
//如果得到现在策略的值大于上个子问题的值,则将其存入记录数组,否则不存入
else {
if (v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
p[i][j] = 1;//只要有个值就行,毕竟是记录数组
} else {
v[i][j] = v[i - 1][j];
}
}
}
}
//打印我们填表的内容,可注释
for (int i = 0; i < v.length; i++) {
for (int j = 0; j < v[0].length; j++) {
System.out.print(v[i][j] + " ");
}
System.out.println();
}
//将记录数组打印更好的理解我们取装入策略的思路;
for(int i=0;i< p.length;i++)
{
for (int j=0;j<p[0].length;j++)
{
System.out.print(p[i][j]+"");
}
System.out.println();
}
//取出装入策略
int i = p.length - 1;
int j = p[0].length - 1;
while (i > 0) {
if (p[i][j] == 1) {
System.out.printf("第%d个放入了仓库\n", i);
j -= w[i - 1];
}
i--;
}
}