动态规划算法
背包问题,有一个背包,容量为4kg,现在有如下物品:
吉他,重量1kg,价格1500;
音响,重量4kg,价格3000;
电脑,种类3kg,价格2000;
如何将装入背包的物品的总价值最大,并且重量不超出背包的容量,装入的物品不能重复
背包问题的思路分析
背包问题指的是一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。背包问题又分01背包和完全背包
01背包指的是每种物品最多放一个,完全背包指的是每种物品都有无限件可用,而无限背包可以转化为01背包
用一个二维数组来表示背包,二维数组的行表示背包的容量,二维数组的列表示物品的种类
-
对于给定的n个物品,每次遍历到第i个物品,设value[i]为第i个物品的价值,w[i]为第i个物品的重量;C为背包的容量。设v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
-
二维数组v的行代表背包的容量,二维数组v的列代表物品的种类,v[i][0]=0,表示当背包的容量为0时,背包中物品的价值为0;v[0][j]=0,表示当背包中的物品为0时,背包中物品的价值也为0
-
遍历二维数组v的时候,会先确定v[1][1]的值,然后紧接着遍历v[1][i],并执行4,5步的判断与赋值;然后会遍历v[2][i],并执行4,5步的判断与赋值,以此类推
-
当w[i]>j时,表示当准备新增的商品的容量大于当前背包的容量时,就有v[i][j]= v[i-1][j],即使用当前背包容量下的此新增商品添加之前的装入策略
-
当j>= w[i]时,表示当准备新增的商品的容量小于等于当前背包的容量时,有两种装入方式
a) v[i-1][j],就是当前背包容量下的此新增商品添加之前的装入策略
b) value[i]+v[i-1][j-w[i]],value[i]就是此新增商品的价值,v[i-1][j-w[i]],v[i-1]表示的就是在此新增商品添加之前,背包容量为[j-w[i]]时,装入商品的最大值 -
对这两种装入方式取最大值,v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]},即为当前条件下背包问题的最优解,当二维数组v遍历完后,v中的最大值即为背包问题的最优解
代码实现动态规划算法-背包问题的解决
package dynamicProgramming;
public class KnapsackProblem {
public static void main(String[] args) {
int[] w = {1, 4, 3};//物品的重量
int[] value = {1500, 3000, 2000};//物品的价值
int m = 4;//背包的容量
int n = value.length;//物品的个数
/*
* 创建二维数组,行数为物品的个数+1,列数为背包的容量+1
* v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
*/
int[][] v = new int[n + 1][m + 1];
//记录放入商品的情况,使用二维数组来记录
int[][] path = new int[n + 1][m + 1];
//初始化第一行和第一列
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++) {//不处理第一行,所以i从1开始遍历
for(int j = 1; j < v[0].length; j++) {//不处理第一列,所以j从1开始遍历
/*
* 新增的商品的容量大于当前背包的容量时
* 此时w[i-1]才表示新增商品的容量,因为i是从1开始遍历的
*/
if(w[i - 1] > j) {
v[i][j] = v[i-1][j];
}else {
/*
* 新增的商品的容量小于等于当前背包的容量时,即w[i - 1] <= j
* 要注意,因为i是从1开始遍历的,所以数组w和数组value中的参数都为i-1
*/
//v[i][j] = Math.max(v[i-1][j], value[i-1]+v[i-1][j-w[i-1]]);
/*
* 为了记录商品存放到背包的情况,不能简单使用上面的赋值语句
*/
if(v[i-1][j] < value[i-1]+v[i-1][j-w[i-1]]) {
v[i][j] = value[i-1]+v[i-1][j-w[i-1]];
//把当前的商品情况记录到path
path[i][j] = 1;
}else {
v[i][j] = v[i-1][j];
}
}
}
}
//输出v,即遍历二维数组
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循环遍历path来获取path[i][j] = 1的i的情况
* 因为path[i][j] = 1对应的if条件执行了较多次数,所以path[i][j] = 1有较多条
* 最优解对应的商品信息,其实就是path的最后一条数据所对应的商品
* 所以解决思路是逆向遍历path数组
*/
int i = path.length - 1;//path数组行的最大下标
int j = path[0].length - 1;//path数组列的最大下标
// 逆向遍历path数组
while(i > 0 && j > 0) {
/*
* 逆向遍历path[i][j] == 1,只要遍历到第一个满足条件的,那么这个就是问题的最优解
* 如果第一次while循环,path[i][j] == 1,说明path数组的最后一条数据对应的商品就是问题的最优解
* 但是假如不是,就跳过if条件,因为不一定最优解就包含了最后一件商品
* 此时使i--,即从倒数第二件商品进行while循环,并判断path[i][j]是否等于1
*/
if(path[i][j] == 1) {
/*
* 如果满足path[i][j] == 1这个条件,说明此时的i,也是商品下标
* 就是最优解中的一件商品
*/
System.out.printf("第%d个商品放入到背包中", i);
System.out.println();
/*
* 此时要将j减去此时i对应的商品容量
* 因为还要进行while循环,而此时要找的是最优解中的其他商品
* 此时肯定要从减去了已经寻找到的商品的容量的背包中进行寻找
* 这其实相当于逆向的进行一次背包问题的求解
*/
j = j - w[i - 1];
}
/*
* 最后无论if条件是否满足,都要进行i--,即从当前商品的上一件商品进行循环遍历
* 假如if条件不满足,当前的商品不在最优解中,下一次遍历就可以去掉当前商品
* 假如if条件满足,当前的商品在最优解中,下一次遍历还是要去掉当前商品,因为这是01背包,每件商品只能放入一次背包
*/
i--;
}
}
}