python算法----背包九讲

# 背包九讲1
def Hello():
    print("01背包问题\n完全背包问题\n多重背包问题\n多重背包问题(二进制优化)\n多重背包问题(单调队列优化)\n混合背包问题\n二维背包问题\n分组背包问题")


Hello()
'''
    01背包问题:
        有N件物品,一个容量为V的背包,第i件物品的价值为value[i-1],重量为weight[i-1];
        在体积不超过背包容量的情况下,返回价值最高的方法;
        每种物体只有一件,可以选择放还是不放。
    
    f[i][j] 表示只看前i件物品,总体积是j的情况下,最大价值是多少。
    
    result = max(f[0~N][0~V])
    
    1. 不选第i件物品:
        f[i][j] == f[i-1][j]
    2. 选第i件物品:(因为选择了第i件物品,所以要处理上下标为i-1的物品信息)
        f[i][j] == f[i-1][j-weight[i-1]]+value[i-1]
    f[i][j] == max(1、2)
    
    初始化:f[0][0] == 0,i==0和j==0 时都为0
'''


def process1(value, weight, N, V):
    # dp[0~N][0~V]
    dp = [[0] * (V + 1) for _ in range(N + 1)]
    for i in range(1, N + 1):
        for j in range(V + 1):
            ans1 = dp[i - 1][j]
            ans2 = 0
            if j - weight[i - 1] >= 0:
                ans2 = dp[i - 1][j - weight[i - 1]] + value[i - 1]
            dp[i][j] = max(ans1, ans2)
    return dp[N][V]


'''
    01背包问题优化:
        f[i] 的数据只和 f[i-1] 有关,所以我们没有必要将所有层的数据都表示出来;
        而且 f[i][j] 有可能依赖的 f[i-1][j] 和 f[i-1][j-weight[i-1]] 都是在 f[i][j]的同一侧;
        所以我们就能将这个二维数组压缩成从一维数组,并且这个一维数组是从依赖方向的反方向开始填写的;
        
        例如,f[i][j]需要 f[i-1][j] 和 f[i-1][j-weight[i-1]]的数据;
            我们在第i层循环的时候自然不能从j为0开始填补这个一维数组;
            因为如果在i层循环时填上了这个数据就会将原本 f[i-1][j-weight[i-1]]的数据覆盖成 f[i][j-weight[i-1]]
'''


def process2(value, weight, N, V):
    # dp[0~V]
    dp = [0] * (V + 1)
    for i in range(1, N + 1):  # 仍然需要N层循环,只能节省空间,不能节省时间
        for j in range(V, -1, -1):  # 需求方向从0开始,所以反方向填补
            ans1 = dp[j]
            ans2 = 0
            if j - weight[i - 1] >= 0:
                # 在这里可以把判断放到j循环里面, 写为for j in range(V,weigth[i-1]-1,-1), 可以节省一点时间,但是为了方便理解暂时牺牲一点点时间
                ans2 = dp[j - weight[i - 1]] + value[i - 1]
            dp[j] = max(ans1, ans2)
    return dp[V]


'''
    完全背包问题  (直接讲优化版本):
        有N种物品,一个容量为V的背包,第i件物品的价值为value[i-1],重量为weight[i-1];
        在体积不超过背包容量的情况下,返回价值最高的方法;
        每种物体有无限件。
    
    f[i][j] 表示只看前i件物品,总体积是j的情况下,最大价值是多少。
    
    1. 不选第i件物品:    
        f[i][j] == f[i-1][j]
    2.1 选1件第i件物品:
        f[i][j] == f[i-1][j-weight[i-1]] + value[i-1]    
    2.2 选2件第i件物品:
        f[i][j] == f[i-1][j-2*weight[i-1]] + 2*value[i-1]
    ....
    f[i][j] == max(1、2.1、2.2、2.3)
    
    现在我们来分析完全背包问题的依赖关系:
        f[i-1][j-weight[i-1]] 表示 不考虑第i种的物品时,重量为j-weight[i-1]的最优解
        f[i][j-weight[i-1]]   表示 考虑第i种物品时。重量为j-weight[i-1]的最优解
        所以我们可以把 f[i][j] (前i种物品,体重为j的最优解) == max
                     f[i-1][j] (考虑第i种物品但是不要)
                     f[i][j-weight[i]]+value[i]  (考虑第i种物品,再要一个,要完刚好装满)
    
    这里说一下和01背包问题的区别:
        f[i][j] (前i个物品,体重为j的最优解) == max
        f[i-1][j] (考虑第i个物品但是不要)
        f[i-1][j-weight[i]]+value[i]  (考虑第i种物品,再要一个,要完刚好装满)
    
    这俩个的区别就是:f[i][j-weight[i]]+value[i] 只在乎它是最后一个,而不是它到底要了多少个;
                    01背包问题不能不在乎要了多少个,必须只能要一个
    
    结合之前对01背包问题的优化:将j从大到小遍历,避免将 f[i-1][j-weight[i-1]]的数据覆盖成 f[i][j-weight[i-1]];
    所以完全背包问题只要从低到高遍历就行。
'''


def process3(value, weight, N, V):
    # dp[0~V]
    dp = [0] * (V + 1)
    for i in range(1, N + 1):  # 仍然需要N层循环,只能节省空间,不能节省时间
        for j in range(V + 1):
            ans1 = dp[j]
            ans2 = 0
            if j - weight[i - 1] >= 0:
                # 在这里可以把判断放到j循环里面, 写为for j in range(weigth[i-1],V+1), 可以节省一点时间,但是为了方便理解暂时牺牲一点点时间
                ans2 = dp[j - weight[i - 1]] + value[i - 1]
            dp[j] = max(ans1, ans2)
    return dp[V]


'''
    01背包和完全背包的总结:
        1. 01背包每件物品最多只能用一次;完全背包每种物品能用无数次;
        2. 01背包:    必须要考虑物品被使用的次数,所以只能从考虑i物品之前的情况 f[i-1][j-weigth[i-1]],通过加上一个 value[i-1]来获取依赖;
        3. 完全背包:   可以不用考虑物品被使用的次数,所以可以从考虑i物品之后的情况 f[i][j-weight[i]],通过加上最后一个 value[i-1]来获取依赖;
        4. 在代码层面: 第一层循环是一样的,
                       01背包为了保证每一次获取的 f[j-weigth[i-1]]都是i-1层遍历计算出来的,所以从大到小遍历;
                       完全背包为了保证每一次获取的 f[j-weigth[i-1]]都是i层遍历计算出来的,所以从小到的大遍历。
                        ( [0,1,2,3,4],j-weigth[i-1]是向左边获取值;
                          在新的一轮中,如果想要获取的值是上一轮的旧值,就从左往右遍历,也就是j从大到小;
                          在新的一轮中,如果想要获取的值是本一轮的新值,就从左往右遍历,也就是j从小到大)
'''

'''
    多重背包问题:
        有N种物品,一个容量为V的背包,每种物品最多有S件;(限制了有多少件,所以需要关注物品被使用的次数)
        第i件物品的费用为value[i],价值为weight[i];
        在体积不超过背包容量的情况下,返回价值最高的方法;
        每种物体有无限件。
        
    f[i][j] == max(f[i-1][j], f[i-1][j-1*weight[i-1]]+1*value[i-1], f[i-1][j-2*weight[i-1]]+2*value[i-1]....)
    
'''


def process4(value, weight, N, V, S):
    # dp[0~V]
    dp = [0] * (V + 1)
    for i in range(1, N + 1):  # 仍然需要N层循环,只能节省空间,不能节省时间
        for j in range(V, -1, -1):  # 需求方向从0开始,所以反方向填补
            for s in range(S + 1):
                # 增加一个循环就行,因为需要掌控数量,所以从j从大到小循环
                if j - s * weight[i - 1] < 0:
                    break
                dp[j] = max(dp[j], dp[j - s * weight[i - 1]] + s * value[
                    i - 1])  # dp[j - 0 * weight[i - 1]] + 0 * value[i - 1] == dp[j]
    return dp[V]


'''
    多重背包问题优化:    
    
    方法一:假设 S==2,value=[2,4,4,5],weight=[1,2,3,4];
           将物品拆成S份,转化为N=S*N规模的01背包问题:value=[2,2,4,4,4,4,5,5],weight=[1,1,2,2,3,3,4,4];
           可以减少一轮循环。
    
    但是方法一面临的问题是:当S很小时,value和weight扩容的代价不算高;但是当S比较高时,扩容S倍的代价就不容小觑了;
    
    方法二 (二进制优化):
        例如,S==10,value=[...2...],weight=[...1...];
        方法一:value=[...2,2,2,2,2,2,2,2,2,2...],weight=[...1,1,1,1,1,1,1,1,1,1...],这样扩容可以组成0~10的每一种情况;
        可以发现扩容的代价很容易失控;
        方法二:只要保证拆的方法能组成0~S的每一种情况,就代表方法是可行的; 
               所以,将物品拆成价值为value[i]*2^k,重量为weight[i]*2^k的若干份物品;
               如:value=[...2,4,8,6...],weight=[...1,2,4,3...]
               可以发现这样也能组成0~10的情况。
'''


def process5(value, weight, N, V, S):
    newvalue = []
    newweight = []
    k = 0
    while True:
        S = S - 2 ** k
        k += 1
        if S - 2 ** k < 0:
            break
    for i in range(N):
        for j in range(k):
            newvalue.append(value[i] * (2 ** j))
            newweight.append(weight[i] * (2 ** j))
        newvalue.append(value[i] * S)
        newweight.append(weight[i] * S)
    N = len(newweight)
    dp = [0] * (V + 1)
    for i in range(N + 1):
        for j in range(V, newweight[i - 1] - 1, -1):
            dp[j] = max(dp[j], dp[j - newweight[i - 1]] + newvalue[i - 1])
    return dp[V]


'''
    多重背包问题  (单调队列优化)
        二进制优化后的时间复杂度为 N*log(S)*V ,那么还可以更优化吗?
        有没有办法可以优化为 N*V的时间复杂度;
    
    来看看未优化的多重背包问题:
        def process(value, weight, N, V, S):
           dp = [0] * (V + 1)
            for i in range(N + 1):
                for j in range(V, weight[i - 1] - 1, -1):
                    for k in range(S):
                        if j - k * weight[i - 1] < 0:
                            break
                        dp[j] = max(dp[j], dp[j - k * weight[i - 1]] + k * value[i-1])
            return dp[V]
            
    可以发现多重背包问题的第一层循环还是哪一个物品,第二层是容量,第三层是决策;
        物品和容量是无法优化的,那么能否使用某种方法优化决策呢?
    令w=weight[i-1],v=value[i-1]
    调度策略依赖关系就变成了    dp[j-w]+v、dp[j-2*w]+2*v.....dp[j-k*w]+k*v
    可以发现    f[j]的问题就变成了在 f[j-0*w]+0*v,f[j-1*w]+1*v,f[j-2*w]+2*v,f[j-3*w]+3*v....之中找到一个最大值;
    
    f[j]:max(f[j-0*w]+0*v,f[j-1*w]+1*v,f[j-2*w]+2*v,f[j-3*w]+3*v....f[j-S*w]+S*v)
    f[j-w]:max(f[j-1*w],f[j-2*w]+1*v,f[j-3*w]+2*v,f[j-4*w]+3*v....f[j-(S+1)*w]+S*v)
    ....
    即问题:
        for k in range(S):
            if j - k * weight[i - 1] < 0:
                break
            dp[j] = max(dp[j], dp[j - k * weight[i - 1]] + k * value[i-1])
    可以从x (x<=S)次求最大值的问题,优化为一个滑动窗口求最大值的问题
    从余数 r开始:    r、r+w、r+2w....j-2w、j-w、j每次跨度为w
    即例如,f[9]可能只通过f[9]、f[7]、f[5]...即一定跨度的信息,这个类的区别就是通过 r的不同来区分的;
    
    现在我们知道了相同r的重量所依赖都是同一组,那么如果能在一次遍历的时候就获得所有weight的最大价值,就能实现加速;
    
    在这之前我们先引入一个副本,用于保存i-1层的数据:
        def process(value, weight, N, V, S):
            dp = [0] * (V + 1)
            for i in range(N + 1):
                copy_file = dp.copy()
                for j in range(1, V + 1):
                    for k in range(S):
                        if j - k * weight[i - 1] < 0:
                            break
                        dp[j] = max(copy_file[j], copy_file[j - k * weight[i - 1]] + k * value[i - 1])
            return dp[V]
'''


def process6(value, weight, N, V, S):
    dp = [0] * (V + 1)
    for i in range(1, N + 1):
        copy_file = dp.copy()
        for j in range(weight[i - 1]):
            queue = []
            for k in range(j, V + 1, weight[i - 1]):
                if queue != [] and queue[0] < k - S * weight[i - 1]:
                    # 同类型的物品不能使用超过S次,因为每一次下标跳跃weight[i-1]格,所以可以用这个方法检测判断队头是否不在窗口内
                    queue.pop(0)
                if queue != []:
                    # 判断不选的情况和选了的情况中最好的情况
                    # (k - queue[0]) / weight[i - 1] * value[i - 1]:因为k一次跳跃weight[i-1]格,所以表示 跳跃长度/单位跳跃长度==跳跃次数*value[i-1]
                    dp[k] = int(max(copy_file[k], copy_file[queue[0]] + (k - queue[0]) / weight[i - 1] * value[i - 1]))
                while queue != [] and copy_file[k] >= copy_file[queue[len(queue) - 1]] + (k - queue[len(queue) - 1]) / \
                        weight[i - 1] * value[i - 1]:
                    # 始终保持queue头是能选择第i物品时,最好的情况
                    queue.pop()
                queue.append(k)
    return dp[V]


'''
    混合背包问题:
        有N件物品,一个容量为V的背包,
        物品一共有三类:
            1. 只能使用1次,s==-1    (01背包) 
            2. 可以使用无限次,s==0  (完全背包)
            3. 只能使用S次,s>0    (多重背包)
        第i件物品的价值为value[i-1],重量为weight[i-1];
        在体积不超过背包容量的情况下,返回价值最高的方法;
    
    因为01背包问题和完全背包问题都能方便计算,所以将多重背包问题转化为01背包问题解决
'''


def process7(value, weight, limit, N, V):
    transaction = []
    for s in range(N):
        if limit[s] == -1:
            # 如果是01背包问题,直接放入
            transaction.append([value[s], weight[s], -1])
        elif limit[s] == 0:
            # 如果是完全背包问题,直接放入
            transaction.append([value[s], weight[s], 0])
        else:
            # 如果是多重背包问题,二进制优化拆分为01背包问题
            S = limit[s]
            k = 0
            while True:
                S = S - 2 ** k
                k += 1
                if S - 2 ** (k) < 0:
                    break
            for i in range(k):
                transaction.append([value[s] * (2 ** i), weight[s] * (2 ** i), -1])
            if S != 0:
                transaction.append([value[s] * S, weight[s] * S, -1])
    # 再遍历所有的物品
    dp = [0] * (V + 1)
    for i in transaction:
        print(i)
        if i[2] == -1:
            # 01背包问题,从高到低遍历
            for j in range(V, i[1] - 1, -1):
                dp[j] = max(dp[j], dp[j - i[1]] + i[0])
        else:
            # 完全背包问题,从低到高遍历
            for j in range(i[1], V + 1):
                dp[j] = max(dp[j], dp[j - i[1]] + i[0])
    return dp[V]


'''
    二维费用背包问题:
        有N件物品和一个容量为V的背包,背包的最大承重为M;
        第i件物品的价值为value[i-1],重量为weight[i-1],体积为volume[i-1];
        在两个条件都不超过的情况下,返回最高的价值;
'''


def process8(value, weight, volume, N, V, M):
    dp = [[0] * (V + 1) for _ in range(M + 1)]
    for i in range(1, N + 1):
        for j in range(M, weight[i - 1] - 1, -1):
            for k in range(V, volume[i - 1] - 1, -1):
                dp[j][k] = max(dp[j][k], dp[j - weight[i - 1]][k - volume[i - 1]] + value[i - 1])
    return dp[M][V]


'''
    分组背包问题:
        有N组物品和一个容量为V的背包;
        每组物品中的物品最多只能选择一个;
    
    求将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大,返回最大价值;
    
    value=[[2,4],[2,6],[1]] 表示三个组
'''


def process9():
    getone = input().split(" ")
    N = int(getone[0])
    V = int(getone[1])
    dp = [0] * (V + 1)
    for i in range(N):  # 分组层
        S = int(input())
        weight = []
        value = []
        for j in range(S):  # 输入数据
            getone = input().split(" ")
            weight.append(int(getone[0]))
            value.append(int(getone[1]))
        for j in range(V, -1, -1):  # 从高往低
            for k in range(S):  # 选择A或者B,因为每一轮都是修改的dp的V方向,所以不会对后续的其他物品选择造成影响
                if j >= weight[k]:
                    dp[j] = max(dp[j], dp[j - weight[k]] + value[k])
    print(dp[V])

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值