什么是动态规划算法
动态规划算法和分治法相似,它的核心思想也是把一个大问题划分为一些小问题,然后一步步进行最优解的算法。但是与分治法不同的是,经过动态规划算法所划分而成的小问题往往不是独立的,也就是下一个问题的求解是建立在上一个问题的解决上。动态规范问题我们一般可以通过填表是方式来逐步推进,从而得到最优解。
什么是背包问题
背包问题是指如果有一个给定容量的背包和若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。
我们引入一个案例:
有一个背包容量有10 kg,有4个物品,它们的重量分别是2,3,4,7 kg,价值分别是1,3,5,9 软妹币。要求在背包里面装入总价值最高的几件商品。而且装入的物品不能重复。
最后一句话 装入的物品不能重复 确定了这个问题是一个01背包问题。除此之外还有一种背包问题是完全背包问题,完全背包问题是不计较数量的,而且完全背包问题可以通过转换变成01背包问题。
我们可以提取到算法的主要思想是:
每遍历到一个物品,就根据它的重量和价值来确定是否将它放入背包内。即对于给定的n个物品,设w[i],v[i]分别为第i个物品的价值和重量,C为背包的容量。再令v[i][i]表示在前i个物品中能够装入容量为j的背包中的最大价值。
用填表的方式解决背包问题
首先我们写出来要用的值:
物品重量w[i] = {2,3,4,7};
物品价值v [i] = {1,3,5,9};
背包容量b = 10;
并创建这样一个表最上面的横行表示背包的特定容量,从0增长到10。
物品 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10kg |
---|---|---|---|---|---|---|---|---|---|---|---|
无物品 | |||||||||||
物品1 | |||||||||||
物品2 | |||||||||||
物品3 | |||||||||||
物品4 |
我们知道当背包容量为0或者无物品放入背包的时候背包物品的总价值都为0.所以表格可以变成这样子,用代码表示就是v[i][0]=v[0][j]=0;//表示填入表第一行和第一列是0(非常重要,代码实现用)。
物品 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10kg |
---|---|---|---|---|---|---|---|---|---|---|---|
无物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | ||||||||||
物品2 | 0 | ||||||||||
物品3 | 0 | ||||||||||
物品4 | 0 |
我们先放入第一个物品重量为2价值为1。
物品 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10kg |
---|---|---|---|---|---|---|---|---|---|---|---|
无物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
物品2 | 0 | ||||||||||
物品3 | 0 | ||||||||||
物品4 | 0 |
再放入第二个物品重量为3价值为3。我们可以通过表格发现小于第二个物品重量的部分和上一行表格一样代码表示val[i][j]=v[i-1][j](非常重要,代码实现用);大于等于第二个物品重量的部分发生了改变,仔细观察可以发现它变成了当前物品的价值加上背包剩余空间物品的最大总价值。当然我们要先与上一行表格进行一次比较反正新的物品的性价比不如以前的物品。用代码表示就是val[i][j]=Math.max(v[i-1][j], v[i]+v[i-1][j-w[i]])(非常重要,代码实现用)。
物品 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10kg |
---|---|---|---|---|---|---|---|---|---|---|---|
无物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
物品2 | 0 | 0 | 1 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
物品3 | 0 | ||||||||||
物品4 | 0 |
现在根据我们所知道的内容填写表格得出最后的最大总价值是12。
物品 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10kg |
---|---|---|---|---|---|---|---|---|---|---|---|
无物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
物品2 | 0 | 0 | 1 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
物品3 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 8 | 8 | 9 | 9 |
物品4 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 9 | 9 | 10 | 12 |
案例代码与运行截图
通过填写表格了解了算法的具体过程后,我们就可以直接编写代码
package knapsackProblem;
public class KnapsackProblem {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] w = {2,3,4,7};//物品重量
int[] v = {1,3,5,9};//物品价值
int b = 10;//背包容量
int n = v.length;//物品个数
//二维数组
int[][] val = new int[n+1][b+1];//背包总价值
int[][] cord = new int[n+1][b+1];//用于记录物品放入
//初始化
//设置第一行为0
for (int i = 0; i < val.length; i++) {
val[i][0] = 0;
}
//设置第一列为0
for (int i = 0; i < val[0].length; i++) {
val[0][i] = 0;
}
//动态规划
for (int i = 1; i < val.length; i++) {
for (int j = 1; j < val[0].length; j++) {
//如果新的物品重量大于现在的容量则直接使用上一行的值
if (w[i-1]>j) {
val[i][j]=val[i-1][j];
}
//否则比较上一行的值与装入新物品后的最大值
else {
val[i][j]=Math.max(val[i-1][j], v[i-1]+val[i-1][j-w[i-1]]);
cord[i][j]=1;
}
}
}
//输出表格
for (int i = 0; i < val.length; i++) {
for (int j = 0; j < val[0].length; j++) {
System.out.print(val[i][j]+" ");
}
System.out.println();
}
//输出记录
int i = cord.length-1;
int j = cord[0].length-1;
while (i>0 && j>0) {
if (cord[i][j] == 1) {
System.out.println("第"+i+"个物品放入背包");
j=j-w[i-1];
}
i--;
}
}
}
运行截图
总结
通过自己的一步步梳理,是不是感觉到这个算法的精髓:把一个大问题划分为一些小问题,然后一步步进行最优解。学习算法个人感觉思想为主,所以在写个人理解时大多数都在写如何去思考这个问题。希望对看到这里的诸位有所帮助,如果喜欢这篇个人理解不妨点一个赞,谢谢~。