完全背包问题从简单到复杂

102 篇文章 0 订阅
70 篇文章 0 订阅

题目

有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。放入第 i 种物品的费用是 C i ,价值是 W i 。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。

第一种思路,基于投资问题模型

从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取 0 件、取 1 件、取 2 件…直至取 ⌊V /C i ⌋ 件等许多种。

状态方程为:

F [i, v] = max {F [i − 1, v − kC i ] + kW i | 0 ≤ kC i ≤ v}

基于以上状态方式实现的三种方式,递归,自上而下,自下而上如下:

# another way: F [i, v] = max {F [i − 1, v − kC i ] + kW i | 0 ≤ kC i ≤ v}
def pack_complete_Rec2(N,V,C,W):
    if N ==0:
        return 0    
    k = V // C[N-1]
    result = -1
    for i in range(k+1):
        A = pack_complete_Rec2(N-1,V-i*C[N-1],C,W) + i*W[N-1]
        if A > result:
            result = A
    return result
import numpy as np
# 注意if list[N,V] == -1 的未知,因为它确定了剪枝;
def pack_complete_Top_down2(N,V,C,W):
    list = np.zeros((N+1,V+1),dtype=int)
    list[1:,:] = -1
    
    def complete_Top_down(N,V):
        t = V // C[N-1] 
        if list[N,V] == -1 :
            result = -1000
            for k in range(t+1):
                if N >=1:
                    A = complete_Top_down(N-1,V-k*C[N-1]) + k*W[N-1]
                    if A >= result:
                        result = A
            list[N,V] = result 
        return list[N,V]
    
    return complete_Top_down(N,V)
def pack_complete_Bottom_up2(N,V,C,W):
    list = np.zeros((N+1,V+1),dtype=int)
#    list[1:,:] = -1

    for i in range(1,N+1):
        for j in range(0,V+1):
            t = j // C[i-1]
            result = -1000
            for k in range(t+1):
                A = list[i-1,j-k*C[i-1]] + k*W[i-1]
                if A > result:
                    result = A
            list[i,j] = result         
     
    return list[N,V]

把问题转换成01背包问题,这里面涉及到等效或者泛化物品

将一种物品拆成多件只能选 0 件或 1 件的01背包中的物品,以下是简单实现,基于二进制的实现,在多重背包中实现:

# change complete to 01
def change_complete_to_01(N,V,C,W):
    C_ =[]
    W_ =[]
    
    for i in range(N):
        t = V // C[i]
        for k in range(1,t+1):
            C_.append(C[i])
            W_.append(W[i])

    def pack_0_1_first(N,V,C,W):    
        F =[0]*(V+1)    
        for i in range(1,N+1):
            for v in range(V,C[i-1]-1,-1):
                F[v] = max(F[v],F[v-C[i-1]] + W[i-1])
        return F[V]
            
    N_ = len(C_)
    return pack_0_1_first(N_,V,C_,W_)

更为简单的实现,把第i种物品看成一个整体,针对第i种物品就有2种策略,1不选:可以看成i-1的问题,二选,就可以看成第i类的01背包问题,因为V是一定的,此时的i实际上不是无限的,是受V限制的,就可以把有限的i看成不同的物品,等价于一个01背包问题,状态方程如下:

F [i, v] = max (F [i − 1, v], F [i, v − C i ] + W i )

基于以上状态方式实现的三种方式,递归,自上而下,自下而上如下:

def pack_complete_Rec(N,V,C,W):
    if N ==0:
        return 0
    if V < C[N-1]:
        return pack_complete_Rec(N-1,V,C,W)
    return max(pack_complete_Rec(N-1,V,C,W),pack_complete_Rec(N,V-C[N-1],C,W) + W[N-1])  

import numpy as np
def pack_complete_Top_down(N,V,C,W):
    list = np.zeros((N+1,V+1),dtype=int)
    list[1:,:] = -1
    
    def complete_Top_down(N,V):
        if list[N,V] == -1 and N >=1:
            A = complete_Top_down(N-1,V)
            if V < C[N-1]: 
                return A
            else:
                list[N,V] = max(A,complete_Top_down(N,V-C[N-1])+W[N-1])
                
            
        return list[N,V]
    
    return complete_Top_down(N,V)

def pack_complete_Bottom_up(N,V,C,W):
    list = np.zeros((N+1,V+1),dtype=int)
    list[1:,:] = -1

    for i in range(1,N+1):
        for j in range(0,V+1):
            A = list[i-1,j]  
            if j < C[i-1]: 
                list[i,j] = A  
            else:
                list[i,j] = max(A,list[i,j-C[i-1]]+W[i-1])
     
    return list[N,V]

基于一维数组的简单的实现如下,对比上述的Bottom_up方法,使用一维数组实现,通过初始化,能够有效避免,左边的走法:

def pack_complete_first(N,V,C,W):
    def CompletePack(F,ci,wi):
        for v in range(ci,V+1):
            F[v] = max(F[v],F[v-ci] + wi)
        return F
    
    F =[0]*(V+1)
    
    for i in range(1,N+1):
        CompletePack(F,C[i-1],W[i-1])
    return F[V]

完全背包可行性问题,只取决于初始化,只有F[0,0]才为0时,才可以得到有效解。

def pack_complete_first_yes_or_no(N,V,C):
    def CompletePack_or(F,ci,wi):
        for v in range(ci,V+1):
            F[v] = F[v] or F[v-ci]
        return F
    
    F =[False]*(V+1)
    F[0] = True
    
    for i in range(1,N+1):
        CompletePack_or(F,C[i-1],W[i-1])
    return F[V]

def pack_complete_Bottom_up_yes_or_no(N,V,C):
    list = np.zeros((N+1,V+1),dtype=bool)
    list[0,0] = True

    for i in range(1,N+1):
        for j in range(0,V+1):
            A = list[i-1,j]  
            if j < C[i-1]: 
                list[i,j] = A  
            else:
                list[i,j] = A or list[i,j-C[i-1]]
     
    return list[N,V]

运行结果:

#%%
N = 7
V = 77
C = [1,2,3,9,13,6,7,5]
W = [1,2,9,7,5,11,6,14]
#%%
print pack_complete_first(N,V,C,W)
print pack_complete_Rec(N,V,C,W)
print pack_complete_Top_down(N,V,C,W)
print pack_complete_Bottom_up(N,V,C,W)
print pack_complete_first_yes_or_no(N,V,C)
print pack_complete_Bottom_up_yes_or_no(N,V,C)
print pack_complete_Rec2(N,V,C,W)
print pack_complete_Top_down2(N,V,C,W)
print pack_complete_Bottom_up2(N,V,C,W)
print change_complete_to_01(N,V,C,W)

227
227
227
227
True
True
227
227
227
227
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值