01背包、完全背包和多重背包(python版)

序言

对于下面的每一个问题我都会附上一个博客链接,这是我觉得讲的比较的清楚的博客,
而我自己的理解全都在代码的注释中,建议先看理论博客,再看代码中的注释,你会有意想不到的收获。

01背包

最优的解法:一维动态规划
相关博客:01背包

while True:
    try:
        N, V = list(map(int, input().split()))
        dp = [0]*(V+1)
        for _ in range(N):  # 遍历所有的物品
            v, w = list(map(int, input().split()))
            # 对于二维的动态规划,求解第i行时只用到了上一行的结果,所以二维可以压缩成一维
            # 再仔细分析一下,求解dp[i][j]时只用到它的上一格dp[i-1][j]和dp[i-1][j-v](上一格的前面某个格子),
            # 在一维中(把二维的行去掉只保留列,就变成一维)就是求解dp[j]会
            # 用到dp[j](此时就是它的上一格)和dp[j-v](上一格的前面某个格子), 所以我们要从右往左修改一维bp, 
            # 因为在从左往右的时候如果你修改了dp[j],那么在求解dp[j+v]用到dp[j]就已经成了脏数据,dp[j]对dp[j+v]来说
            # 应该是上一行的内容,而这里却已经是当前行的内容了。
            for j in range(V, 0, -1):  # 遍历所有容量的背包,注意是逆序
                if j >= v:
                    dp[j] = max(dp[j], dp[j-v] + w)
        print(dp[V])
    except:
        break

完全背包

最优解法:一维动态规划
相关博客:完全背包
我的理解:01背包问题在求解dp[ i ][ j ]时要比较 i 物品放或者不放到容量为 j 的背包中哪个获得的价值更大,而完全背包问题在求解dp[ i ][ j ]时要比较 j // v 个物品 i 放或者不放到容量为 j 的背包中哪个获得的价值更大,其中v是物品 i 的 体积。对这个问题进一步化简,假设物品的 i 的体积是 v、价值是w,当前仅当 j=v 时刚好放下1个物品 i , j=5,j=6,j=7也只能放下一个物品 i ,当j=2v时刚好存放两个物品 i ,所以计算dp[ i ][ 2v] 的时候我们没必要去算j=2v时可以存多少个i,而是令dp[ i ][ 2v ] = dp[ i ] [2v-v] + w,因为dp[ i ] [2v-v] = dp[ i ][v]表示的就是存放1个物品 i 时的最大价值,此时再加上一个w正好表示j=2v时可以存放两个物品i,所以dp[ i ][ j -v] + w计算的就是容量为 j 的背包存放 j // v个物品 i 时获得的价值,然后容量为 j 的背包不存放物品 i 的价值是dp[ i-1 ][ j ],
所以dp[ i ] [ j ] = max( dp[ i-1 ][ j ],dp[ i ][ j -v] + w),然后我们再把二维动态压缩到一维就行啦,如何压缩我就不啰嗦了。

while True:
    try:
        N, V = list(map(int, input().split()))
        dp = [0]*(V+1)
        for _ in range(N):  # 遍历所有的物品
            v, w = list(map(int, input().split()))
            # 仔细分析一下,二维dp中求解dp[i][j]时用到它的上一格dp[i-1][j]和dp[i][j-v](当前格子的前面某个格子),
            # 这里和01背包的区别就在于dp[i][j-v]是当前行的内容,在dp[i][j]用它之前它需要先被修改,
            # 所以在一维dp中dp[j]的求解顺序是从左往右
            for j in range(1, V+1):  # 遍历所有容量的背包
                if j >= v:
                    dp[j] = max(dp[j], dp[j-v] + w)
        print(dp[V])
    except:
        break

多重背包:

相关博客:多重背包
解法1:暴力拆分 + 一维动态规划

while True:
    try:
        N, V = map(int, input().split())
        dp = [0]*(V+1)
        for _ in range(N):  # 遍历所有物品
            v, w, s = map(int, input().split())
            for _ in range(s):  # 把当前物品看作是s个不同物品(尽管v和w相同), 就比01背包多了一层循环而已
            # 该方法的缺点也显而易见,复杂度是O(V*n的累加和)
                for j in range(V, 0, -1):
                    if j >= v:
                        dp[j] = max(dp[j], dp[j-v] + w)
        print(dp[V])
    except:
        break

优化解法2:树拆分 + 一维动态规划

import math
while True:
    try:
        N, V = map(int, input().split())
        dp = [0]*(V+1)
        for _ in range(N):  # 遍历所有物品
            v, w, s = map(int, input().split())
            # 这里对s个v和w相同的物品进行划分,假设s=13,我们这13个物品划分成几个物品合适呢,
            # 想一想完全二叉树,树的第一层有2^0个节点(根节点),把第一层看作一个物品a,它的体积和价值是v和w
            # 接着树的第二层有2^1个节点,把第二层看成一个物品b,它的体积和价值是2v和2w
            # 接着树的第三层有2^2个节点,把第三层看成一个物品c,它的体积和价值是4v和4w
            # 接着树的第四层,注意我们只有13个物品哦,所以第四层只有s-(2^h-1),这里的h等于3,
            # 因为2^h-1的表示是完全二叉树的前h层有多少节点,所以第四层有13-(2^3-1)=6个节点,
            # 把第四层看成一个物品d,它的体积和价值是6v和6w
            # abcd这4个物品完全可以组成1、2....13个物品,因为我们装a的时就表示把1个体积为v、价值为w的物品装进了背包
            # 当我们装a,b时代表我们把3个体积为v、价值为w的物品装进了背包,因为a是(v,w),b是(2v,2w),
            # 以此类推我们可以用a、b、c、d这4个物品来表示1、2....13个v和w相同的物品
            h = int(math.log(s, 2)) + 1  # h表示树高,树的每一层表示我们划分的一个新物品,
            # 该层的节点个数k就是我们要求出来的系数,因为这个新物品的体积是kv,价值是kw
            for i in range(1, h+1):
                k = 2**(i - 1)
                if i == h and 2**i - 1 != s:  # 如果树的最后一层没满,则要另算该层的节点个数
                    k = s - (2**(i-1) -1)
                for j in range(V, 0, -1):
                    if j >= k*v:
                        dp[j] = max(dp[j], dp[j-k*v] + k*w)
        print(dp[V])
    except:
        break

优化解法3:单调队列
相关博客:多重背包

你可能感兴趣的:(数据结构与算法)