动态规划 0-1背包

  动态规划(Dynamic Programming DP)将每个求解过的子问题的解记录下来,以后再遇到时不用重复求解自底向上(递推),一步一步找到全局最优解。(递归是自顶向下)

  动规五步曲

1 确定dp数组及下标含义

2 确定递推公式

3 确定递推边界:dp数组初始化

4 确定遍历顺序

5 打印dp数组Debug,举例验证

  背包问题:有n种物品,每种物品的重量为w[i]价值为v[i],现有一个容量为m的背包,问如何选取物品放入背包,使得背包内物品的总价值最大根据每个物品的数量可以分为:

0-1背包:每种物品都只有一个(选or不选);(掌握)

完全背包:每种物品都有无穷个(选or选几个);(掌握)

多重背包:不同的物品数量不同;(竞赛)

分组背包:按组打包,每组最多选一个;(竞赛)

  贪心:从平均价值最大的开始选,贪心允许分割零售,但是背包不允许分割,所以最后可能放不下去,不一定是价值最大的。

回溯:暴力枚举,每件取或不取,共n个阶段,复杂度O(2n),很多重复计算。遍历所有情况,当满足情况就停止遍历(剪枝)

0-1背包

动态规划做法O(nm)携带研究材料

1 dp[i][j]表示从下标1~i的物品中任意取,装入容量为j的背包中,所能获得的最大价值

2 确定递推公式:对第i件物品的选择策略有两种:

不放物品i,即dp[i][j]=dp[i-1][j]。即物品i无法放进背包中。

放物品i,当前容量变为j-w[i],问题转化为前i-1件物品装入容量为j-w[i]的背包中所能获得的最大价值加上这个物品的价值,即dp[i][j]=dp[i-1][j-w[i]]+v[i]. 注意如果j-w[i]<0,说明容量不足,不能放,转为第一种策略(放了也不一定最大,因为要减去重量)

递推公式:dp[i][j]=max{dp[i-1][j],dp[i-1][j-w[i]]+v[i]} (1≤i≤n,0≤j≤m)

3 dp初始化:如果装入0件物品 背包容量为0,价值都为0,即dp[i][0]=0;dp[0][j]=0;

4 确定遍历顺序:一般先遍历物品,再遍历背包。

public void bagProblem(int n, int[] w, int[] v, int m) {
    // 默认w[0]v[0]无意义,从1开始,具体题目具体分析
    int[][] dp = new int[n + 1][m + 1];
    // 初始化dp数组 dp[i][0]=0; dp[0][j]=0; 默认即可
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (j < w[i]) {  // 容量不够,不放
                dp[i][j] = dp[i - 1][j];
            } else {  // 容量够了,取放和不放的较大值。
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);  
            }
        }
    }
  }

一般面试题都是在nmwivi处做出修改,把逻辑弄明白。

  1分割等和子集:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。思路:只要找到集合里能够出现sum/2的子集总和,就可以分割成两个相同元素和子集了。

  

例子模拟

例子模拟int n=5; int[] w={0,2,2,6,5,4}; int[] v={0,6,3,5,4,6}; int m=10;

初始化dp[i][0]=0; dp[0][j]=0; (表中已省略) 然后从上往下,从左往右,一行一行的计算。

第一行dp[1][2],不放价值0,放价值6,选择放。同理dp[1][?],就一件物品,都是选择放。

第二行dp[2][2],不放dp[1][2]=6,放的话dp[1][2-2]+3=3,选择不放。dp[2][3],不放dp[1][3]=6,放dp[1][3-2]+3=3,选择不放。dp[2,4],不放价值6,放dp[1][4-2]+3=9,选择放。其他同理。

第三行dp[3][2],不放价值6,放dp[2-6]容量不足。dp[3][6],不放价值9,放dp[2][6-6]+5=5,选择不放。dp[3][8],不放价值9,放dp[2][8-6]+5=11选择放。

......

最终得到dp[5][10]=15,即最优解。

根据最优解回溯找出解的组成

1 dp[i][j]=dp[i-1][j]时,说明没有选择第i个物品,则回到dp[i-1][j]

2 dp[i][j]=dp[i-1][j-w[i]]+v[i]时,说明选择了第i个物品,该物品是最优解组成的一部分,回到放该物品之前,即回到dp[i-1][j-w[i]]

3 一直遍历到i0结束为止,所有解的组成都会找到。

如最优解dp[5][10]=15,不等于dp[4][10],而是等于dp[4][10-4]+6,说明物品5被选择。

回到dp[4][6]=9,等于dp[3][6]=9,物品4没有被选择。

回到dp[3][6]=9,等于dp[2][6]=9,物品3没有被选择。

回到dp[2][6]=9,不等于dp[1][6]=6,而是等于dp[1][6-2]+3,物品2被选择。

回到dp[1][4]=6,不等于dp[0][2]=0,而是等于dp[0][2-2]+6,物品1被选择。

public static String bagContent(int n, int m, int[] w, int[][] dp) {
    ArrayList<Integer> res = new ArrayList<>();
    for (int j = m, i = n; j > 0 && i > 0; ) {
        if (dp[i][j] == dp[i - 1][j]){
            i--;
        }else {
            res.add(i); i--; j -= w[i];
        }
    }
    return res.toString();
}

  

完全背包

每种物品都有无穷件携带研究材料

动态规划做法O(nm):除了递归公式有区别,其他同01背包:

放物品i,容量剩余j-w[i],价值增加v[i],但是i件物品仍然可以放,不是转移到dp[i-1][j-w[i]],而是转移到dp[i][j-w[i]],即dp[i][j]=dp[i][j-w[i]]+v[i]

多重背包

每种物品有k[i]携带矿石资源

如果将其转化为0-1背包,即每种物品均被视为k种重量和价值都相同的不同物品,时间O(mΣki)

// 默认w[0]v[0]无意义,从1开始,具体题目具体分析
int[][] dp = new int[n + 1][m + 1];
// 初始化dp数组 dp[i][0]=0; dp[0][j]=0; 默认即可
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        // 把k个物品当成单个物品,遍历
        for (int k = 0; k <= nums[i]; k++) {
            if (j < k * w[i]) {  // 容量不够,终止循环
                break;
            } else {  // 容量够了,取放和不放的较大值。
                // k=0时,dp[i][j]=dp[i-1][j],即不放的最大值。
                // k!=0时,dp[i][j]为 不放 和 放k个 的最大值。
                dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);
            }
        }
    }
}

降低ki将会大大降低时间复杂度,进一步优化:可以将数量为k的每种物品二进制拆分成若干组,将每组物品视为一件物品,其重量、价值为该组所有物品总和。每组物品的数量为1,2,4,8,16,...,k-前面和可以通过若干新物品的不同组合,得到0k之间的任意件物品的价值重量和,然后对所有这些新物品做0-1背包,即可得到多重背包的解,时间O(mΣlog2(ki))

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值