如何最优利用背包空间:贪心算法与动态规划的解题策略

本文探讨了多背包问题的解决方案,分别使用贪心算法和动态规划结合整数规划来求解。贪心算法通过每次尽可能填充背包达到局部最优,而动态规划则确保全局最优解,但求解速度较慢。动态规划首先找到所有可能的背包装满方案,然后用整数规划找出最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多背包问题的求解方法

在物流或资源分配的场景中,我们常常会遇到“多背包问题”,即如何将一批物品合理装入尽可能少的背包中。本文将探讨如何使用贪心算法结合整数规划动态规划结合整数规划两种方法来解决这一问题,并对它们的效果进行对比。

问题描述

假设我们需要运送一批物品,每种物品的质量和需求数量如下:

  • 质量: 26, 30, 40, 56, 60, 76, 80, 86, 90, 100, 110, 116, 120, 176, 180, 200
  • 数量: 2, 10, 8, 16, 80, 6, 32, 4, 36, 4, 10, 8, 18, 40, 16, 8

我们的目标是求解出在背包承重为 600 的情况下,最少需要多少个背包才能装下所有物品。

方法一:贪心算法 + 整数规划

思路

贪心算法的核心在于局部最优,即每次尽可能将一个背包装满。通过贪心策略,我们先用整数规划求解出一个背包中可以装入的最大物品组合,然后重复这一过程直到所有物品都被装入。

  • 贪心算法:
      每次将背包最大程度的装满
  • 整数规划:
      数量, x 1 , x 2 , . . . , x 22 x_1,x_2,...,x_{22} x1,x2,...,x22
      maximize:   26 ∗ x 1 + 29 ∗ x 2 + . . . + 201 ∗ x 22 26 * x_1 + 29 * x_2 + ... + 201 * x_{22} 26x1+29x2+...+201x22
      S.T.:
         26 ∗ x 1 + 29 ∗ x 2 + . . . + 201 ∗ x 22 ≤ 600 26 * x_1 + 29 * x_2 + ... + 201 * x_{22} \leq 600 26x1+29x2+...+201x22600
         0 ≤ x 1 ≤ 2 , i n t e g e r 0 \leq x_1 \leq 2, integer 0x12,integer
         0 ≤ x 2 ≤ 10 , i n t e g e r 0 \leq x_2 \leq 10, integer 0x210,integer
        …
         0 ≤ x 22 ≤ 8 , i n t e g e r 0 \leq x_{22} \leq 8, integer 0x228,integer
实现步骤
  1. 对每个背包使用整数规划,计算出在不超过 600 重量限制的情况下,可以装入的最大物品数量。
  2. 重复上述步骤,直到所有物品都被装入背包。
优缺点
  • 优点: 实现简单,求解速度快,适用于问题规模较小或对解的精度要求不高的场景。
  • 缺点: 由于每次仅考虑局部最优解,最终可能需要更多的背包。
import numpy as np
import pulp as lp


def integer_program(weights, nums, max_weight):
    """
    整数规划
    input:
        weights: 物品质量集
        nums: 物品需求数量集
        max_weight: 背包承重
    output:
        单个背包装入数量集
    """
    prob = lp.LpProblem("The GY Problem", lp.LpMaximize)
    x = []
    for i, n in enumerate(nums):
        x.append(lp.LpVariable("x_%03d" % i, lowBound=0, upBound=n, cat="Integer"))

    prob += lp.lpSum([weights[i] * x[i] for i in range(len(x))]), "Total weight"
    prob += lp.lpSum([weights[i] * x[i] for i in range(len(x))]) <= max_weight, "Max weight"
    prob.solve()
#     print(prob)
#     print("\tStatus:", lp.LpStatus[prob.status])
    return np.array([v.varValue for v in prob.variables()]).astype("int32")


# 初始化变量
weights_array = np.array(
    [26, 30, 40, 56, 60, 76, 80, 86, 90, 100, 110, 116, 120, 176, 180, 200])
nums_array = np.array(
    [2, 10, 8, 16, 80, 6, 32, 4, 36, 4, 10, 8, 18, 40, 16, 8])
bag_weight = 600

# 理论最少背包数
bags_num_min = np.ceil(np.sum(weights_array * nums_array) / bag_weight)
print("理论最少", bags_num_min)

# 贪心算法
bags_num = 0
bag_nums_list = []   # 每个背包装入物品方案集合
while np.any(nums_array > 0):
    nums_array_ = integer_program(weights_array, nums_array, bag_weight)
    nums_array -= nums_array_
    bags_num += 1
    bag_nums_list.append(nums_array_)
print("实际最少", bags_num)
理论最少 49.0
实际最少 51

最终,在这个案例中,贪心算法结合整数规划的结果为51个背包,虽然接近最优,但未必是全局最优解。

方法二:动态规划 + 整数规划

思路

动态规划的思想是先求出所有可能的“填满”背包的组合,然后通过整数规划从这些组合中找到最优解。这种方法确保了我们找到了全局最优的解决方案。

  • 动态规划:
      求解所有可能的背包装满方案(不能再放入任一物品)。
  • 整数规划:
      方案, c 1 , c 2 , . . . , c n c_1,c_2,...,c_n c1,c2,...,cn
      每个方案采用数量, x 1 , x 2 , . . . , x n x_1,x_2,...,x_n x1,x2,...,xn
      各个方案物品分布数量, A 1 i , A 2 i , . . . , A n i A_{1i},A_{2i},...,A_{ni} A1i,A2i,...,Ani
      每种物品需求数量, b i bi bi
      minimize:   x 1 + x 2 + . . . + x n x_1 + x_2 + ... + x_n x1+x2+...+xn
      S.T.:
         A 1 i ∗ x 1 + A 2 i ∗ x 2 + . . . + A n i ∗ x 22 ≥ b i A_{1i} * x_1 + A_{2i} * x_2 + ... + A_{ni} * x_{22} \geq {bi} A1ix1+A2ix2+...+Anix22bi
         0 ≤ x 1 , i n t e g e r 0 \leq x_1, integer 0x1,integer
         0 ≤ x 2 , i n t e g e r 0 \leq x_2, integer 0x2,integer
        …
         0 ≤ x n , i n t e g e r 0 \leq x_n, integer 0xn,integer
实现步骤
  1. 使用动态规划生成所有可能的“填满”背包的方案,即每个方案都是在背包不能再放入更多物品的情况下的组合。
  2. 使用整数规划确定如何组合这些方案,以最小化所需的背包数量。
优缺点
  • 优点: 保证全局最优解,适用于对解的精度要求高的场景。
  • 缺点: 计算复杂度高,求解时间较长。
import numpy as np
import pulp as lp


def bag_program(weights, item_counts, max_weight):
    """
    动态规划,获取所有可能的背包装满方案
    input:
        weights: 重量数组
        item_counts: 物品数量数组
        max_weight: 最大重量
    output:
        背包装满方案
    """
    combinations = [[] for _ in range(max_weight + 1)]
    combinations[0].append([0] * len(weights))

    for index, weight in enumerate(weights):
        for current_weight in range(max_weight + 1):
            if combinations[current_weight]:
                if current_weight + weight > max_weight:
                    break
                current_combinations = [
                    comb[:] for comb in combinations[current_weight]
                ]
                new_combinations = []
                for comb in current_combinations:
                    comb[index] += 1
                    if comb[index] <= item_counts[index]:
                        new_combinations.append(comb)
                combinations[current_weight + weight].extend(new_combinations)

    return combinations[-weights[0] :]


def integer_program(num_arrs, nums):
    """
    input:
        num_arrs: num set
        nums: minimize num
    output:
        result
    """
    num_arrays_list = []
    for vals in num_arrs:
        num_arrays_list.extend(vals)

    nums_mat = np.array(num_arrays_list)
    print("\t", nums_mat.shape)

    A = nums_mat.T
    b = nums

    prob = lp.LpProblem("The_GY_Problem", lp.LpMinimize)
    x = [
        lp.LpVariable("x_%06d" % i, lowBound=0, cat="Integer")
        for i in range(A.shape[1])
    ]

    prob += lp.lpSum(x), "Total Number"
    for i in range(len(b)):
        prob += (
            lp.lpSum([A[i][j] * x[j] for j in range(A.shape[1])]) >= b[i],
            "lb%04d" % weights_array[i],
        )
    prob.solve()
    print("\tStatus:", lp.LpStatus[prob.status])

    res = []
    for v in prob.variables():
        if v.varValue:
            res.append((A[:, int(v.name[2:])], v.varValue))
    return res


# 初始化变量
weights_array = np.array(
    [26, 30, 40, 56, 60, 76, 80, 86, 90, 100, 110, 116, 120, 176, 180, 200]
)
nums_array = np.array([2, 10, 8, 16, 80, 6, 32, 4, 36, 4, 10, 8, 18, 40, 16, 8])
bag_weight = 600

# 动态规划获取所有可能的背包装满方案(不能再放入任一物品)
weight_num_arrays = bag_program(weights_array, nums_array, bag_weight)

# 理论最少背包数
bags_num_min = np.ceil(sum(weights_array * nums_array) / bag_weight)
print("理论最少", bags_num_min)

# 迭代进行整数规划
# 迭代?方案数量较多,未知数多,求解慢
# 从较少方案集入手,慢慢增加方案集,直至获得最优解
for i in range(1, len(weight_num_arrays) + 1):
    print(i)
    # 方案数量为0,与上次迭代没变化
    if not weight_num_arrays[-i]:
        continue
    result = integer_program(weight_num_arrays[-i:], nums_array)
    bags_num = sum([n[-1] for n in result])
    print("\t", bags_num)
    # 当达到理论最小数量时,已获得最优解决方案
    if bags_num == bags_num_min:
        break
print("实际最少", sum([n for _, n in result]))

# # 承重验证
# np.all([sum(v * weights_array) <= bag_weight for v,_ in result])
# # 数量验证
# np.all(np.sum(np.array([v * n for v, n in result]), axis=0) - nums_array >= 0)
理论最少 49.0
1
	 (7122, 16)
	Status: Optimal
	 50.0
2
3
	 (16896, 16)
	Status: Optimal
	 49.0
实际最少 49.0

通过动态规划结合整数规划的求解方法,最终得到了49个背包的最优解,达到了理论最小值。

结论

在解决多背包问题时,贪心算法结合整数规划是一种快速有效的近似解法,适合处理规模较小的问题。而动态规划结合整数规划则能保证找到全局最优解,尽管计算复杂度较高。

根据具体的应用场景和对解的精度要求,可以选择合适的算法来进行求解。在此案例中,动态规划加整数规划的方案最终实现了最优解,并有效地减少了背包的数量。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值