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 背包问题求解算法:蛮力法、回溯法和分支限界法。通过代码实现和时间复杂度分析,展示了不同算法的性能差异,并提供可视化的图表展示结果。对于较小的物品数量,蛮力法和回溯法可能足够高效,但对于较大的物品数量,分支限界法通常是更好的选择。

0/1 背包问题求解算法比较:蛮力法、回溯法和分支限界法

原文地址: https://www.cveoy.top/t/topic/fwu8 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录