python 回溯法 子集树模板 系列 —— 3、0-1背包问题

问题

给定N个物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该如何选择装入背包的物品,使得放入背包的物品的总价值为最大?

分析

显然,放入背包的物品,是N个物品的所有子集的其中之一。N个物品中每一个物品,都有选择不选择两种状态。因此,只需要对每一个物品的这两种状态进行遍历。

解是一个长度固定的N元0,1数组。

套用回溯法子集树模板,做起来不要太爽!!!

代码

'''0-1背包问题'''

n = 3            # 物品数量
c = 30           # 包的载重量
w = [20, 15, 15] # 物品重量
v = [45, 25, 25] # 物品价值

maxw = 0  # 合条件的能装载的最大重量
maxv = 0  # 合条件的能装载的最大价值
bag = [0,0,0] # 一个解(n元0-1数组)长度固定为n
bags = []     # 一组解
bestbag = None # 最佳解


# 冲突检测
def conflict(k):
    global bag, w, c
    
    # bag内的前k个物品已超重,则冲突
    if sum([y[0] for y in filter(lambda x:x[1]==1, zip(w[:k+1], bag[:k+1]))]) > c:
        return True
    
    return False


# 套用子集树模板
def backpack(k): # 到达第k个物品
    global bag, maxv, maxw, bestbag
    
    if k==n: # 超出最后一个物品,判断结果是否最优
        cv = get_a_pack_value(bag)
        cw = get_a_pack_weight(bag)
        
        if cv > maxv : # 价值大的优先
            maxv = cv
            bestbag = bag[:]
            
        if cv == maxv and cw < maxw: # 价值相同,重量轻的优先
            maxw = cw
            bestbag = bag[:]
    else:
        for i in [1,0]: # 遍历两种状态 [选取1, 不选取0] 
            bag[k] = i  # 因为解的长度是固定的
            if not conflict(k): # 剪枝
                backpack(k+1)


# 根据一个解bag,计算重量
def get_a_pack_weight(bag):
    global w
    
    return sum([y[0] for y in filter(lambda x:x[1]==1, zip(w, bag))])
    
    
# 根据一个解bag,计算价值
def get_a_pack_value(bag):
    global v
    
    return sum([y[0] for y in filter(lambda x:x[1]==1, zip(v, bag))])
    
    
# 测试
backpack(0)
print(bestbag, get_a_pack_value(bestbag))

效果图

img_46d23876aff7049a31713f01c72e2df3.jpg

### 01背包问题中的回溯法分支限界法区别 #### 解决策略的不同 对于01背包问题回溯法旨在找到解空间树中满足约束条件的所有可能解。相比之下,分支限界法则专注于快速定位至少一个可行解,在某些情况下可能是最优解。这种差异源于两者不同的搜索目标设定[^4]。 #### 搜索模式对比 - **回溯法**:采用深度优先遍历策略构建解空间树。这意味着算法会尽可能深入探索每一个潜在解决方案路径直到达到叶子节点或遇到不满足条件的情况才会返回上一层继续尝试其他可能性[^2]。 - **分支限界法**:通常采取广度优先或者基于成本估计的优先级队列来进行层次化扫描。这种方法允许程序在早期阶段就能接触到多个候选方案,并通过剪枝操作排除那些不可能产生更优结果的部分子集。 #### 扩展节点处理机制 - 在回溯过程中,每当某个中间状态无法进一步发展成有效解答时就会被标记为“死结点”,随后立即退回到最近未完全展开的状态重新考虑新的前进路线; - 对于分支限界而言,任何时刻每个活跃(即尚未完成全部后代生成)的位置仅能作为单一扩展起点来创建其直接下层元素集合;而且这些新产生的成员会被加入到等待评估的数据结构里以便后续按需选取最有潜力者推进计算流程。 #### 存储需求考量 由于上述工作原理上的差别,导致两者的内存占用特性也有所区分。具体来说,因为分支限界倾向于保存更多关于当前正在考察范围的信息,所以它往往需要更大的辅助存储资源支持整个运算周期内的数据管理活动。相反地,当面临硬件环境限制特别是可用RAM不足的情形下,利用较少额外开销即可运作良好的回溯技术或许更具优势。 ```python def backtrack_knapsack(items, capacity): best_value = [0] def dfs(index, current_weight, value): if index >= len(items) or current_weight > capacity: return # 不选第index个物品 dfs(index + 1, current_weight, value) # 选第index个物品 item_weight, item_value = items[index] if (current_weight + item_weight <= capacity and value + item_value > best_value[0]): best_value[0] = value + item_value dfs(index + 1, current_weight + item_weight, value + item_value) dfs(0, 0, 0) return best_value[0] from queue import PriorityQueue class Node(object): def __init__(self, level, profit, weight): self.level = level self.profit = profit self.weight = weight def __lt__(self, other): return self.profit / float(self.weight) > other.profit / float(other.weight) def branch_and_bound_knapsack(weights, values, max_capacity): n = len(values) q = PriorityQueue() start_node = Node(-1, 0, 0) q.put(start_node) u = start_node v = start_node while not q.empty(): u = q.get() next_level = u.level + 1 if next_level == n: break v = Node(next_level, u.profit, u.weight) # Not taking the nth item. q.put(v) w = weights[next_level] p = values[next_level] if(u.weight + w <= max_capacity): child_v = Node(next_level, u.profit+p ,u.weight+w ) if(child_v.profit > v.profit ): v=child_v q.put(child_v ) return v.profit items = [(2, 3), (3, 4), (4, 5), (5, 8)] # (weight, value) capacity = 5 print(f'Backtrack Method Result: {backtrack_knapsack(items, capacity)}') print(f'Branch And Bound Method Result: {branch_and_bound_knapsack([i[0] for i in items], [i[1] for i in items], capacity)}') ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值