算法入门----动态规划之背包购物车问题
每天多学一点点~
话不多说,这就开始吧…
1.前言
算法真特么难,这里分享博主自己整理的两个动态规划的算法题(入门级,虽然很简单,但是我觉得好难)~
武汉加油~
2.背包问题
小偷去某商店盗窃,背有一个背包,容量是50kg,现在有以下物品(物品不能切分,且只有一个),请问小偷应该怎么拿才能得到最大的价值?
物品1 10kg 60元
物品2 20kg 100元
物品3 40kg 120元
-
贪心算法思路**
重量 价值
物品1 10kg 60元 60 / 10 = 6
物品2 20kg 100元 100/20 = 5
物品3 40kg 120元 120/40 = 3
性价比最高:贪心的策略,按性价比排序,得到的最大价值是 60+100=160,背包装了30kg
很显然:40+10(kg)=120+60=180,不对 -
排列组合
三个物品 123 132 213 231 312 321
10个物品 有多少排列组合?O(10!) 时间复杂度太高 -
枚举
遍历它:每个物品只有2个选择就是拿与不拿吧
时间复杂度O(2^n),还是太大
- 贪心算法
为了方便思考,先全部/10
5kg的袋子
物品有3个:
钱:6 10 12
Kg:1 2 4
我们把5kg的袋子,拆分成1kg,1kg这样子计算,里面的表格就表示当前重量下能装的最多的钱。表格的数列就表示是要装的物品
我们把5kg的袋子,拆分成1kg,1kg这样子计算,里面的表格就表示当前重量下能装的最多的钱。表格的横列就表示是要装的物品。
1kg (w) | 2kg | 3kg | 4kg | 5kg | |
---|---|---|---|---|---|
加入物品1(n) | 6 | 6 | 6 | 6 | 6 |
加入物品2(n) | 6 | 10 | 16 | 16 | 16 |
加入物品3(n) | 6 | 10 | 16 | 16 | 18 |
加入物品1
l kg: 能装的最大的钱 6元(因为只有物品1,且袋子只能装1kg)
2kg: 同上(因为只有物品1)
3kg: 同上
4kg: 同上
5kg: 同上
加入物品2
l kg: 能装的最大的钱 6元(因为袋子只能装1kg,只能放物品1)
2kg: 能装的最大的钱 10元(袋子有2kg,能放物品1或者2,物品2加个10元,选择物品2)
3kg: 能装的最大的钱 16元(袋子有3kg,正好选择物品1+物品2)
4kg: 同上(多余的1kg也不能选择其他物品,因为每个物品只能选择一次)
5kg: 同上
加入物品3
l kg: 能装的最大的钱 6元(因为袋子只能装1kg,只能放物品1)
2kg: 能装的最大的钱 10元(袋子有2kg,能放物品1或者2,物品2加个10元,选择物品2)
3kg: 能装的最大的钱 16元(袋子有3kg,正好选择物品1+物品2)
4kg: 同上(可以选择物品3,但是3只有12元 <物品1+物品2共16元)
5kg: 能装的最大的钱 18元(袋子有5kg,选择物品1+物品3共6+12=18元)
上面这一个递推过程总结起来就是一个东西------状态转移方程:
能装的时候 每次和上面的比较,大我就装,否则就不装。
Max(
money[i]+res[i-1][w-weight[i]],
res[i-1][w]
);
方程 | 解释 |
---|---|
money[i]+res[i-1][w-weight[i]] | 装这个物品 |
w-weight[i] | 表示装完还剩下的空间 |
res[i-1][w-weight[i]] | 表示装完后剩下的空间还能装的最大值,取上一次的结果。 |
Res[i-1][w] | 表示不装这个物品的值 |
代码
/**
* 动态规划 时间复杂度位 o(m*n)
* 钱:6 10 12
* Kg:1 2 4
*/
public class PacketDp{
public static void main(String[] args) {
int money[] = {6, 10, 12,15}; // 每个商品 的 钱
int weigth[] = {1, 2, 4,5}; // 每个商品的质量 购物车那个问题 只需要一个价值就行了,重量都都没有。
int n = 4; // 商品的数量
int w = 6; // 背包的重量
int dp[][] = new int[n + 1][w + 1]; // n表示是物品,w表示重量,初始化全是0
for (int i = 1; i <= n; i++) { // 每次加的物品
for (int j = 1; j <= w; j++) { //j 当前分割的背包的重量 从1 开始 到 50
if (weigth[i - 1] <= j) { // weigth[i - 1] 当前物品的重量 因为i是从1 开始的
dp[i][j] = Math.max(
// money[i-1] 当前物品的钱 因为i是从1 开始的
// j-weigth[i-1] 表示 背包总重量-当前重量= 装完还剩下的空间
// dp[i-1] 上一次场景
// dp[i-1][j-weigth[i-1]] 装完后剩下的空间还能装的最大值(取上一次的结果)
money[i - 1] + dp[i - 1][j - weigth[i - 1]], // 本次装 这个物品 的最大的 值
dp[i - 1][j] // 表示不装这个物品的值
);
} else {
dp[i][j] = dp[i - 1][j]; //不能装
}
System.out.println();
}
}
// 输出 分别 加进来 几个商品
System.out.println("能装的最大的价值 :" + dp[n][w]); // n=3 w=5
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}
// 遍历最后一列
System.out.println("具体的物品 ");
for (int i = n; i > 1; i--) {
if (dp[i][w] == dp[i - 1][w]) {
// 没有加进来
} else {
System.out.println("选择了 第"+i +"个 物品 "+ ":" + money[i - 1]);
w = w - weigth[i - 1]; // 每次减去 背包w的重量
}
}
if (w != 0) {
System.out.println("选择了 第"+1 +"个 物品 "+ ":" + money[0]);
}
}
}
3.购物车问题
看完上面的背包问题,举一反三
购物车.双十一马上就要来了,小球心目中的女神在购物车加了N个东西,突然她中了一个奖可以清空购物车5000元的东西(不能找零),每个东西只能买一件,那么她应该如何选择物品使之中奖的额度能最大利用呢?如果存在多种最优组合你只需要给出一种即可。
这个比背包稍微简单点,只有价值,没有重量,思路也是一样的
public class ShoppingCartDp {
public static void main(String[] args) {
//{1,2,3,4,5,9}
int money[] = {44, 2, 3, 4, 5, 8,9}; //购物车那个问题 只需要一个价值就行了,重量都都没有。
int n = 7; // 商品的数量
int m = 10; // 表示购物车 可以 清空的钱
int dp[][] = new int[n + 1][m + 1]; //n表示是物品,w表示重量,初始化全是0
for (int i = 1; i <= n; i++) { //每次加的物品
for (int cw = 1; cw <= m; cw++) { //分割的背包
if (money[i - 1] <= cw) { //表示这个物品可以装进去
dp[i][cw] = Math.max(
money[i - 1] + dp[i - 1][cw - money[i - 1]],
dp[i - 1][cw]
);
} else {
dp[i][cw] = dp[i - 1][cw]; //不能装
}
}
}
// 输出 分别 加进来 几个商品
System.out.println("能装的最大值:" + dp[n][m]);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}
m = dp[n][m]; // 这一步很关键 因为是按照 钱 的 尺度 计算 的
System.out.println("------------------");
// 遍历最后一列
System.out.println("具体的物品 ");
for (int i = n; i > 1; i--) { // i>0 就不用 m!=0 的操作
if (dp[i][m] == dp[i - 1][m]) {
// 没有加进来
} else {
System.out.println("第"+i +"个 物品 "+ ":" + money[i - 1]);
m = m - money[i - 1]; // 减去 加进来的 钱
}
}
if(m!=0){
System.out.println(1 + ":" + money[0]);
}
}
}
4.动态规划与遍历和贪心的比较
- 和遍历的比较及优化
遍历每次在物品加进来的时候都会保存选择与不选择两种状态那么这样下去越到后面状态保存的就越多其实就是2^n次,因为每个物品都有选与不选两种情况。而动态规划是每次都会把当前情况下的最优解计算出来,层层递推,下一层的最优解都是基于它上一次结果存下来的,所以最后一个结果就一定是最优解。
其实也是把问题都分解成了一个子问题,然后通过子问题去求解全局最优解。 - 动归和贪心的比较
贪心是只管眼前不会管后的情况,而动归不一样,它的每次递推都是基于上一次的最优解进行。所以往往动归是一定能求出最优解的,而贪心不一定,这也是贪心算法的缺点,但是动归的时间复杂度是O(n*m)而贪心是O(nlogn),所以贪心算法的是高效的,动归如果子问题太多的话 就容易算不出结果,而且能用动归的问题往往用贪心都能解决一部分,甚至很大一部分。因此如果在实际项目中要求不是特别严的话 博主建议先使用贪心算法求最优解,其实我们很多时候并不用保证100%的准确,能尽量准确就可以了,贪心恰恰是符合这个规则的。
5.结语
世上无难事,只怕有心人,每天积累一点点,fighting!!!加一句,头好大~~~