📜  使用最小计数分支和界限的01背包(1)

📅  最后修改于: 2023-12-03 14:49:55.464000             🧑  作者: Mango

使用最小计数分支和界限的 01 背包

背景

在算法竞赛中,01 背包问题是一个经典问题。给定一组物品,每个物品有一个重量和一个价值,现在需要从中选择一些物品放入一个容量为 $V$ 的背包中,使得选中的物品重量不超过 $V$,而且价值最大。

最小计数分支和界限算法

最小计数分支和界限(MCKP)算法是经典的 01 背包问题的一种求解方法。它将 01 背包问题转化为具有多个约束的最小费用流问题,并利用动态规划的思想解决。

MCKP 算法的核心是计算界限(Lower Bound),它可以帮助我们确定一个上限,从而剪枝掉一些不必要的搜索分支。具体来说,MCKP 算法会将物品按照单位重量价值(价值除以重量)从大到小排序,然后从前往后依次选择物品。如果一个物品不能被完整地放入背包中,我们就尽可能多地放入,直到背包被填满为止。这个过程中选择的物品和它们的部分重量构成了一个上界,我们将这个上界称为界限。

通过计算每个物品被选择的概率和以当前状态为前缀的最大价值,我们可以得到一个下界。这个下界是所有可能的选择中的最大值。如果界限小于当前最优解,我们就可以停止往下搜索了。

代码实现:

def lower_bound():
    """
    计算当前状态的界限

    :return: 当前状态的界限
    """
    bound = sum(p[v] for v in range(n) if x[v] < c[v])  # 只计算没选满的物品的价值

    cur_wt = sum(x[v] * w[v] for v in range(n))  # 当前背包的重量
    for v in range(n):
        if x[v] == c[v]:
            continue
        wt = cur_wt + (c[v] - x[v]) * w[v]  # 加入该物品剩余部分的重量
        if wt > W:
            bound += (W - cur_wt) * p[v] / w[v]  # 超过背包容量,按比例添加
            break
        bound += c[v] * p[v]
        cur_wt = wt

    return bound

接下来,我们只需要在搜索过程中维护一个最小界限即可。如果下一个状态的界限小于当前最优解,则可以将其剪枝掉。

完整代码实现:

def mckp(i, value, weight, count):
    """
    从第 i 个物品开始做决策,当前状态的价值为 value,重量为 weight,个数为 count

    :param i: 从第 i 个物品开始做决策
    :param value: 当前状态的价值
    :param weight: 当前状态的重量
    :param count: 当前状态的个数
    :return: 从第 i 个物品开始的子问题的最优解
    """
    nonlocal ans

    if weight > W or i == n:  # 超过背包容量或遍历完所有物品,更新答案
        if value > ans:
            ans = value
        return

    if lb + value <= ans:  # 当前状态的界限小于上界,剪枝
        return

    if count == c[i]:  # 当前物品已经选满,直接跳过
        mckp(i + 1, value, weight, 0)
        return

    for t in range(c[i] - count + 1):  # 枚举选择数量
        x[i] = count + t
        if weight + t * w[i] > W:  # 超过背包容量,跳过后面的选择
            break
        lb = lower_bound()
        mckp(i + 1, value + t * p[i], weight + t * w[i], count + t)
总结

最小计数分支和界限算法是一种优秀的求解 01 背包问题的方法。它通过计算每个状态的界限剪枝掉不必要的搜索分支,从而大大提高了搜索效率。同时,它的实现也很简单,只需要在动态规划的框架下加入界限计算和剪枝即可。