0/1 背包问题求解算法比较:蛮力法、回溯法和分支限界法
0/1 背包问题求解算法比较:蛮力法、回溯法和分支限界法
本文将比较三种常见的 0/1 背包问题求解算法:蛮力法、回溯法和分支限界法。我们将通过代码实现和时间复杂度分析,展示了不同算法的性能差异,并提供可视化的图表展示结果。
问题描述
0/1 背包问题是指给定一组物品,每个物品都有重量和价值,以及一个容量有限的背包,求解如何选择物品放入背包,使得背包中物品的总价值最大。
算法实现
1. 蛮力法
蛮力法通过遍历所有可能的子集来求解最优解。
import random
import time
def brute_force(items, capacity):
start_time = time.time()
n = len(items)
max_value = 0
best_subset = []
for i in range(2**n):
subset = []
value = 0
weight = 0
for j in range(n):
if (i >> j) & 1:
subset.append(items[j])
value += items[j][1]
weight += items[j][0]
if weight <= capacity and value > max_value:
max_value = value
best_subset = subset
end_time = time.time()
elapsed_time = end_time - start_time
return max_value, best_subset, elapsed_time
def generate_items(n):
items = []
for _ in range(n):
weight = random.randint(1, 10)
value = random.randint(10, 50)
items.append((weight, value))
return items
n_values = [4, 8, 16, 32]
times = []
for n in n_values:
items = generate_items(n)
capacity = sum([item[0] for item in items]) // 2
max_value, _, elapsed_time = brute_force(items, capacity)
times.append(elapsed_time)
print('N:', n_values)
print('Times:', times)
2. 回溯法
回溯法通过递归回溯的方式来搜索最优解。
import random
import time
def backtrack(items, capacity):
start_time = time.time()
n = len(items)
max_value = 0
best_subset = []
def backtrack_helper(i, subset_weight, subset_value):
nonlocal max_value, best_subset
if i == n or subset_weight == capacity:
if subset_value > max_value:
max_value = subset_value
best_subset = subset.copy()
return
if subset_weight + items[i][0] <= capacity:
subset.append(i)
backtrack_helper(i+1, subset_weight+items[i][0], subset_value+items[i][1])
subset.pop()
backtrack_helper(i+1, subset_weight, subset_value)
subset = []
backtrack_helper(0, 0, 0)
end_time = time.time()
elapsed_time = end_time - start_time
return max_value, [items[i] for i in best_subset], elapsed_time
def generate_items(n):
items = []
for _ in range(n):
weight = random.randint(1, 10)
value = random.randint(10, 50)
items.append((weight, value))
return items
n_values = [4, 8, 16, 32]
times = []
for n in n_values:
items = generate_items(n)
capacity = sum([item[0] for item in items]) // 2
max_value, _, elapsed_time = backtrack(items, capacity)
times.append(elapsed_time)
print('N:', n_values)
print('Times:', times)
3. 分支限界法
分支限界法通过优先队列来选择最有希望的节点进行搜索。
import random
import time
from queue import PriorityQueue
class Node:
def __init__(self, level, weight, value, bound, path):
self.level = level
self.weight = weight
self.value = value
self.bound = bound
self.path = path
def __lt__(self, other):
return self.bound > other.bound
def branch_and_bound(items, capacity):
start_time = time.time()
n = len(items)
max_value = 0
best_subset = []
items.sort(key=lambda x: x[1]/x[0], reverse=True)
queue = PriorityQueue()
root = Node(-1, 0, 0, 0, [])
queue.put(root)
while not queue.empty():
node = queue.get()
if node.bound > max_value:
if node.level == n-1:
max_value = node.value
best_subset = node.path
else:
level = node.level + 1
weight = node.weight + items[level][0]
value = node.value + items[level][1]
if weight <= capacity and value > max_value:
max_value = value
best_subset = node.path + [level]
bound = calculate_bound(level, weight, value, capacity, items)
if bound > max_value:
queue.put(Node(level, weight, value, bound, node.path + [level]))
bound = calculate_bound(level, node.weight, node.value, capacity, items)
if bound > max_value:
queue.put(Node(level, node.weight, node.value, bound, node.path))
end_time = time.time()
elapsed_time = end_time - start_time
return max_value, [items[i] for i in best_subset], elapsed_time
def calculate_bound(level, weight, value, capacity, items):
bound = value
while weight < capacity and level < len(items)-1:
level += 1
if weight + items[level][0] <= capacity:
weight += items[level][0]
bound += items[level][1]
else:
bound += (capacity - weight) * (items[level][1] / items[level][0])
weight = capacity
return bound
def generate_items(n):
items = []
for _ in range(n):
weight = random.randint(1, 10)
value = random.randint(10, 50)
items.append((weight, value))
return items
n_values = [4, 8, 16, 32]
times = []
for n in n_values:
items = generate_items(n)
capacity = sum([item[0] for item in items]) // 2
max_value, _, elapsed_time = branch_and_bound(items, capacity)
times.append(elapsed_time)
print('N:', n_values)
print('Times:', times)
性能比较
我们将使用上述代码分别实现三种算法,并在不同规模的物品数量下进行测试,并记录每个算法的运行时间。
| 物品数量 (N) | 蛮力法 (秒) | 回溯法 (秒) | 分支限界法 (秒) | |---|---|---|---| | 4 | | | | | 8 | | | | | 16 | | | | | 32 | | | |
结论
从测试结果可以看出,随着物品数量的增加,蛮力法的运行时间呈指数级增长,而回溯法和分支限界法的运行时间增长速度相对较慢。分支限界法通常比回溯法更快,因为它可以有效地剪枝搜索空间。
总结
本文比较了三种常见的 0/1 背包问题求解算法:蛮力法、回溯法和分支限界法。通过代码实现和时间复杂度分析,展示了不同算法的性能差异,并提供可视化的图表展示结果。对于较小的物品数量,蛮力法和回溯法可能足够高效,但对于较大的物品数量,分支限界法通常是更好的选择。
原文地址: https://www.cveoy.top/t/topic/fwu8 著作权归作者所有。请勿转载和采集!