巴黎高师近似算法笔记-全-
巴黎高师近似算法笔记(全)
001:课程导论 🎯


在本课程中,我们将学习近似算法的基本概念、设计动机以及其在解决组合优化问题中的应用。我们将探讨为什么需要近似算法,并介绍本课程第一部分将涵盖的五个核心问题及其对应的算法设计技术。
组合优化问题在日常生活中频繁出现。例如,安排课程表时,需要避免学生所选课程的时间冲突,确保教室资源充足,并满足学校的各种约束条件。又或者,经营一家货运公司时,需要规划卡车的配送路线,以最小的油耗将所有货物送达客户手中。
不幸的是,大多数组合优化问题都是NP难问题。如果P不等于NP,我们无法指望为所有情况都找到一个高效的最优解。那么,我们该如何应对NP难问题呢?
以下是几种可能的应对方式:
- 放弃:认为问题不可能解决,无法在合理时间内找到好的解决方案。但在处理现实问题时,这通常不是一个可行的选项。
- 暴力求解:尝试枚举所有可能性并选择最佳方案。然而,对于许多稍大规模的问题实例,这种方法将耗费极长的时间,实际上与放弃无异。
- 依赖启发式方法:尝试某种方案并寄希望于运气。例如,随意规划几条配送路线。但对于理论研究者而言,这种方法不可接受,因为你无法保证或控制解决方案的质量。
- 采用近似算法:在多项式时间内,寻找一个足够好的解。这意味着我们放弃寻找绝对最优解,转而寻求一个在可证明的范围内,其目标函数值不会比最优解差太多的解决方案。
那么,我们如何设计近似算法呢?首先,我们认识到现实问题通常过于复杂。为了能够进行理论证明,我们将研究这些问题的理想化版本。例如,背包问题的理想化模型。在这些由少数参数定义的简单理想化问题上,我们有望证明定理。这些定理能为我们提供关于问题难度的新算法见解和结构洞察。
因此,我们的路径是:从现实情境出发,提炼难点以定义基础的理论问题;在这些问题上进行理论研究,设计算法并证明相关定理;在此过程中获得新的思路;最终,将理论成果应用于实际问题。本课程将聚焦于基础问题的近似算法理论。
本课程对问题的选择基于两个考量:第一,选择著名且被深入研究的问题,以丰富大家的知识储备;第二,确保每个问题都能提供一个学习特定近似算法设计或分析技术的机会。
在本课程的第一部分,我们将学习五个章节,对应五个问题及五种不同的技术:
- 顶点覆盖问题:介绍近似算法设计中的一项重要技术——线性规划松弛。
- 背包问题
- 装箱问题
- 集合覆盖问题
- 多路割问题:这是本部分课程中最难的问题。

接下来,我们将从列表中的第一个问题——顶点覆盖问题开始,并借此机会引入近似算法设计中的一项关键技术。
在开始技术部分之前,请允许我介绍为本课程提供帮助的团队成员:助教Vas Coenaad、Fredderic Malmanen和Victor Verdogo。此外,还有我们的导演Nodine Mian和摄像师Joe Parvidi。再次欢迎大家参加近似算法(第一部分)课程。


本节课中,我们一起学习了近似算法产生的背景及其必要性,了解了应对NP难问题的不同策略,并明确了本课程第一部分将围绕五个核心组合优化问题展开,每个问题都将揭示一种特定的算法设计技术。从下一节开始,我们将深入第一个问题:顶点覆盖。
002:顶点覆盖问题定义 🎯

在本节课中,我们将学习近似算法中的一个经典问题:顶点覆盖问题。我们将从定义开始,并通过一个具体例子来理解其核心概念。
概述
顶点覆盖问题是组合优化中最基本的问题之一。给定一个带权重的图,目标是找到一个顶点子集,使得图中的每条边都至少有一个端点在这个子集中,并且该子集中所有顶点的总权重最小。
顶点覆盖的定义
顶点覆盖的定义可以直观地理解为:用一组顶点“覆盖”图中的所有边。这意味着对于图中的每条边,其至少有一个端点属于这组顶点。
以下是顶点覆盖问题的一个具体例子。
考虑图中展示的毯子图案。毯子上的角色(Linus,来自漫画《花生漫画》)正盖住图底部的所有边。毯子下的三个顶点构成了该图的一个顶点覆盖。
我们从一个常用于证明反例的经典图例开始,说明如何构建一个顶点覆盖。
我们需要选择一组顶点。假设首先选择顶部的顶点 A。顶点 A 覆盖了图中的三条边(图中已用红色标出)。目前进展顺利,但图中仍有边未被覆盖,因此需要继续选择顶点。
接下来,可以选择图左下角的顶点 B。顶点 B 覆盖了三条边。现在,需要继续选择更多顶点以覆盖所有边。再选择顶点 C。顶点 C 覆盖三条边,但其中一条边已被顶点 B 覆盖。这没有关系,一条边被两个端点同时覆盖是可以接受的。顶点 C 额外覆盖了两条边。
继续选择顶点 D、E 和 F。至此,我们得到了一个包含六个顶点 A、B、C、D、E、F 的集合。这就是图的一个顶点覆盖。图中的每条边都至少有一个端点在这个覆盖中。例如,边 B-C 被覆盖了两次。总之,每条边都至少有一个端点在覆盖内。
这是一个大小为6的顶点覆盖。如果每个顶点的权重都是1,那么这个覆盖的总权重就是 1+1+1+1+1+1=6。这个解是最优的,不可能用五个顶点覆盖这个图的所有边。
原因如下:观察图的外围部分,它是一个长度为5的环。要覆盖一个五环的所有边,至少需要三个顶点。再看图的内部部分,它是一个五角星。五角星本质上也是五环的另一种画法,因此同样至少需要三个顶点来覆盖内部的边。综上所述,至少需要6个顶点。因此,给出的解 A、B、C、D、E、F 是最优的,它最小化了所选顶点的总权重。
综上所述,顶点覆盖问题的定义是:
- 输入:一个图,其中每个顶点都有一个权重。
- 输出:图的一个顶点子集,满足:
- 覆盖图中的每一条边(每条边至少有一个端点在该子集中)。
- 该子集中所有顶点的总权重最小。
另一种表述:整数规划
上一节我们通过图示和例子介绍了顶点覆盖问题的直观定义。本节中,我们将从更代数的角度,给出该问题的另一种表述——整数规划形式。
顶点覆盖问题可以表述为以下整数规划模型:
设图 G = (V, E),每个顶点 v 有一个权重 w_v。对于每个顶点 v,定义一个决策变量 x_v ∈ {0, 1}:
- x_v = 1 表示顶点 v 被选入覆盖。
- x_v = 0 表示顶点 v 未被选入覆盖。
目标是最小化总权重:minimize Σ_{v ∈ V} w_v * x_v
约束条件是,对于每条边 (u, v) ∈ E,必须满足:x_u + x_v ≥ 1
这个约束保证了每条边的至少一个端点被选中(即对应的变量值为1)。变量 x_v 的0/1取值要求使得这是一个整数规划问题。

总结
本节课中,我们一起学习了顶点覆盖问题的核心内容。我们首先通过一个生动的毯子比喻和具体的图例,直观地理解了什么是顶点覆盖——即用最“轻”的一组顶点覆盖图中所有的边。接着,我们证明了示例中六顶点解的最优性。最后,我们将问题转化为更形式化的整数规划模型,用数学公式 minimize Σ w_v x_v 和约束 x_u + x_v ≥ 1 来精确描述它。这种从具体到抽象的理解方式,是学习组合优化问题的基础。
003:整数规划与线性规划松弛

在本节课中,我们将学习如何将顶点覆盖问题建模为整数规划,并介绍其线性规划松弛。我们将从变量、约束和目标函数三个部分来构建数学模型,并理解它们如何精确地描述问题。
变量定义 🧮
上一节我们介绍了顶点覆盖问题的定义。本节中,我们来看看如何用数学变量来表示一个顶点覆盖。
顶点覆盖是图中顶点的一个子集。为了表示这个子集,我们需要知道每个顶点是否被包含在内。因此,我们为图中的每个顶点定义一个变量。
对于图中的每个顶点 u,我们定义一个变量 x_u。这个变量只能取两个值:0 或 1。
x_u = 1表示顶点u在覆盖中。x_u = 0表示顶点u不在覆盖中。
对于一个有 n 个顶点的图,我们就有了 n 个变量。所有变量的赋值组合共有 2^n 种可能,对应了所有可能的顶点子集。
约束条件 🔗
定义了变量之后,我们需要用数学表达式来刻画“覆盖所有边”这一条件。
让我们考虑图中的一条边,例如连接顶点 A 和 B 的边 (A, B)。我们有变量 x_A 和 x_B。这条边被覆盖,意味着 A 和 B 中至少有一个在覆盖内。用数学语言表达,即:
x_A + x_B >= 1
这个不等式确保了 x_A 和 x_B 不能同时为 0。
要将这个条件推广到整个图,我们只需为图中的每一条边都写出这样一个不等式。
以下是针对图中所有边的约束条件:
- 对于边
(A, B):x_A + x_B >= 1 - 对于边
(A, C):x_A + x_C >= 1 - 对于边
(B, C):x_B + x_C >= 1 - 对于边
(B, D):x_B + x_D >= 1 - 对于边
(C, D):x_C + x_D >= 1 - 对于边
(C, E):x_C + x_E >= 1 - 对于边
(D, F):x_D + x_F >= 1 - 对于边
(E, F):x_E + x_F >= 1 - 对于边
(E, G):x_E + x_G >= 1 - 对于边
(F, G):x_F + x_G >= 1
目标函数 🎯
我们的目标是找到一个总权重最小的顶点覆盖。因此,我们需要定义一个目标函数来最小化覆盖的总成本。
每个顶点 u 有一个权重 w_u。如果 x_u = 1(即顶点在覆盖中),那么它就对总成本贡献 w_u。总成本就是所有在覆盖中的顶点的权重之和。
因此,目标函数是最小化以下表达式:
最小化:1*x_A + 4*x_B + 4*x_C + 2*x_D + 9*x_E + 4*x_F + 3*x_G
在这个例子中,数字 1, 4, 4, 2, 9, 4, 3 分别是顶点 A 到 G 的权重。
整数规划模型总结 📐
综合以上三个部分,我们得到了顶点覆盖问题的整数规划模型。
对于一个一般的图 G=(V, E),顶点权重为 w_u,模型如下:
- 变量:对每个顶点
u ∈ V,有x_u ∈ {0, 1} - 约束:对每条边
(u, v) ∈ E,有x_u + x_v >= 1 - 目标:最小化
∑_{u ∈ V} w_u * x_u
这个整数规划的解(即满足所有约束的 0/1 变量赋值)与图的顶点覆盖之间存在一一对应关系。目标函数的值就是对应顶点覆盖的总权重。
例如,图中绿色顶点 {A, C, D, F, G} 构成一个覆盖。对应的变量赋值为 x_A = x_C = x_D = x_F = x_G = 1,其余为 0。可以验证它满足所有边的约束,并且目标函数值为 1+4+2+4+3 = 14。
线性规划松弛 🌊
整数规划要求变量必须是 0 或 1,这通常很难直接求解。一个常见的技巧是进行“松弛”:我们放宽对变量的整数限制,允许它们取 0 到 1 之间的任何实数。
于是,我们得到了顶点覆盖的线性规划松弛模型:
- 变量:对每个顶点
u ∈ V,有0 <= x_u <= 1 - 约束:对每条边
(u, v) ∈ E,有x_u + x_v >= 1 - 目标:最小化
∑_{u ∈ V} w_u * x_u
这个线性规划比原整数规划更容易求解。它的最优解值(我们称之为 OPT_LP)提供了一个重要的信息:OPT_LP 是原整数规划最优解值(OPT_IP)的一个下界。因为任何可行的顶点覆盖(对应 0/1 解)都自动是这个线性规划的可行解,所以线性规划在更大的解空间中找到的最小值不会比原问题的最优值大,即 OPT_LP <= OPT_IP。
这个下界对于设计和分析近似算法至关重要,我们将在后续课程中看到如何利用它。

本节课中我们一起学习了如何将顶点覆盖问题形式化为整数规划,包括定义 0/1 变量、建立覆盖边的线性不等式约束以及构建最小化权重的目标函数。我们还介绍了通过松弛整数约束得到线性规划的方法,并理解了线性规划最优值是原问题最优值的一个下界。这是设计近似算法的关键第一步。
004:线性规划松弛


在本节中,我们将学习如何从顶点覆盖问题的整数规划出发,构建其对应的线性规划松弛,并理解这一转换如何为设计近似算法提供关键工具。
我们已经了解了顶点覆盖问题的整数规划定义。现在,我们来讨论一个相关的线性规划公式。
什么是整数规划?
整数规划的一般形式如下:
- 目标:最小化或最大化形如
C₁x₁ + C₂x₂ + ... + Cₙxₙ的目标函数。 - 约束:满足一系列约束,例如第一个约束为
a₁₁x₁ + a₁₂x₂ + ... + a₁ₙxₙ ≥ b₁,总共有 M 个约束。 - 变量限制:对于每个变量
xᵢ,必须满足0 ≤ xᵢ ≤ 1,并且xᵢ必须是整数。
正是最后这个整数要求,使得它被称为整数规划。我们为顶点覆盖问题构建了一个完美的整数规划模型。
如果我们能直接使用算法求解整数规划,问题就解决了。但遗憾的是,整数规划是 NP 难问题,无法在多项式时间内高效求解。
线性规划松弛
那么,为什么人们还对整数规划如此感兴趣呢?因为它们为设计相关问题的更好算法提供了灵感和初始工具。具体来说,让我们来看一个相关的线性规划。
这个线性规划与整数规划几乎相同:目标函数相同,约束条件也相同,除了最后一行。变量 xᵢ 不再必须是整数,而可以是任何实数。
正是这个看似微小、无害的修改,彻底改变了问题的复杂度。线性规划可以在多项式时间内求解。这一发现大约在30年前,自此,线性规划成为了组合优化领域中无数问题解决方案的基础工具。
线性规划的两种表示法
线性规划有两种等价的表示方式,一种更详细,一种更抽象。
详细形式:明确写出所有系数 C₁, C₂, ..., Cₙ, a₁₁, a₁₂, ..., b₁, b₂, ... 等。
抽象形式:
- 目标:最小化向量 C 与向量 X 的点积,即
C·X。其中 C 是Rⁿ中的向量,坐标为(C₁, C₂, ..., Cₙ);X 也是Rⁿ中的向量,坐标为(x₁, x₂, ..., xₙ)。 - 约束:使用一个 M 行 N 列的矩阵 A(元素为
aᵢⱼ)来紧凑地表示约束,即A·X ≥ B。这里 B 是Rᴹ中的向量(b₁, b₂, ..., bₘ),“≥”表示逐坐标比较。 - 变量范围:
X位于超立方体[0,1]ⁿ内,即每个xᵢ在 0 和 1 之间,且X是Rⁿ中的向量(每个分量是实数)。
这两种表示法完全等价,没有优劣之分,可以根据个人喜好选择使用哪一种。
顶点覆盖的线性规划松弛
因此,对于任何一个整数规划,我们都可以得到一个看起来几乎相同的线性规划,唯一的区别是将“xᵢ 为整数”的约束替换为“xᵢ 为实数”。复杂度随之发生剧变:整数规划是 NP 难的,而对应的线性规划可在多项式时间内求解。
我们有为顶点覆盖设计的整数规划,因此可以写出对应的线性规划松弛:
- 对于每个顶点
u,对应变量x_u满足0 ≤ x_u ≤ 1。 - 对于每条边
(u, v),必须满足x_u + x_v ≥ 1。 - 目标函数与原始的整数规划相同。
现在,我们得到了一个与顶点覆盖整数规划相关的线性规划。
接下来是关键的一步:我们将利用这个线性规划来为顶点覆盖问题设计一个优秀的近似算法。

总结

本节课中,我们一起学习了线性规划松弛的核心概念。我们从顶点覆盖的整数规划出发,通过将整数变量约束松弛为实数变量约束,得到了一个可在多项式时间内求解的线性规划。这一转换是设计近似算法的重要基础,它为我们提供了原问题最优解的一个下界,并指引我们如何构造一个可行的整数解。在下一节中,我们将具体探讨如何利用这个线性规划的解来构建顶点覆盖的近似算法。
005:基于线性规划的顶点覆盖近似算法


欢迎回到近似算法课程。让我们继续学习顶点覆盖问题。我们已经为顶点覆盖设计了一个线性规划松弛,现在来看看如何利用它来设计一个算法。
算法概述
在本节中,我们将学习如何通过求解线性规划松弛,并对解进行舍入,来构造顶点覆盖问题的一个近似解。我们将分析该算法的正确性和近似比。
线性规划松弛回顾
首先,我们回顾一下之前设计的线性规划松弛。对于图中的每个顶点,我们有一个变量,其值在0到1之间。对于图中的每条边,其两个端点对应变量的值之和必须至少为1。目标是最小化所有变量的加权和。
用公式表示如下:
最小化:∑_{v ∈ V} w(v) * x_v
约束条件:
- 对于所有边 (u, v) ∈ E:x_u + x_v ≥ 1
- 对于所有顶点 v ∈ V:0 ≤ x_v ≤ 1
算法步骤
以下是算法的完整步骤:
- 求解线性规划:在多项式时间内求解上述线性规划,得到一组最优解值 x_v*(对于每个顶点 v)。
- 舍入解:对于每个顶点 v,根据其对应的 x_v* 值,将其舍入为整数 z_v。
- 如果 x_v* ≥ 0.5,则令 z_v = 1。
- 如果 x_v* < 0.5,则令 z_v = 0。
- 输出顶点覆盖:输出所有满足 z_v = 1 的顶点 v 构成的集合。
算法分析
上一节我们介绍了算法的步骤,本节中我们来分析其正确性和运行效率。
运行时间
该算法在多项式时间内运行。求解线性规划可以在多项式时间内完成(作为一个黑盒使用)。对解进行舍入和输出结果只需要线性时间。
正确性证明
我们需要证明算法输出的顶点集合确实覆盖了图的所有边。
考虑图中的任意一条边 (u, v)。根据线性规划的约束条件,我们有:
x_u* + x_v* ≥ 1
由此可以推断,x_u* 和 x_v* 中至少有一个大于或等于 0.5。因为如果两者都小于 0.5,那么它们的和将小于 1,这与约束矛盾。
根据我们的舍入规则:
- 如果 x_u* ≥ 0.5,则 z_u = 1,顶点 u 被加入覆盖集。
- 如果 x_v* ≥ 0.5,则 z_v = 1,顶点 v 被加入覆盖集。
由于 x_u* 和 x_v* 中至少有一个 ≥ 0.5,因此 z_u 和 z_v 中至少有一个等于 1。这意味着边 (u, v) 的至少一个端点被包含在输出的顶点覆盖中。


上述论证对图中的每一条边都成立。因此,算法输出的集合覆盖了所有边,是一个正确的顶点覆盖。
本节总结
本节课中,我们一起学习了如何为顶点覆盖问题设计一个基于线性规划松弛和舍入的近似算法。我们详细介绍了算法的三个步骤,并证明了该算法是正确的,即其输出总是一个有效的顶点覆盖。关于这个算法输出解的质量(即近似比),我们将在后续章节中进行探讨。
006:顶点覆盖算法分析 🧮

在本节课中,我们将学习如何分析之前设计的顶点覆盖近似算法的质量。我们将证明该算法的输出成本最多是最优解的两倍,并探讨这个分析是否是最优的。
上一节我们证明了算法的正确性和多项式时间复杂度。本节中,我们来分析算法输出解的质量。
我们需要将输出解的成本与未知的最优顶点覆盖成本联系起来。我们将从输出解开始,逐步推导其成本上界。
以下是分析步骤:
首先,输出解的成本可以表示为所有被选中顶点的权重之和。设输出顶点集为 U,对应的取整变量为 zᵤ(取值为0或1),则输出成本为:
输出成本 = Σᵤ (wᵤ * zᵤ)
接下来,我们需要将 zᵤ 与线性规划(LP)的解 xᵤ* 联系起来。回顾一下,zᵤ 是通过对 xᵤ* 进行取整得到的(若 xᵤ* ≥ 0.5,则 zᵤ = 1;否则为0)。因此,对于任何顶点,始终有:
zᵤ ≤ 2 * xᵤ*
这个因子2在 xᵤ* = 0.5 时达到。
然后,我们需要将线性规划的解与最优整数解(OPT)联系起来。线性规划是原整数规划(IP)的松弛版本,其约束更少。因此,线性规划的最优值(即 Σᵤ (wᵤ * xᵤ)*)不会大于整数规划的最优值(OPT):
Σᵤ (wᵤ * xᵤ*) ≤ OPT
最后,结合以上所有观察,我们可以完成证明:
输出成本 = Σᵤ (wᵤ * zᵤ) ≤ Σᵤ (wᵤ * 2 * xᵤ*) = 2 * Σᵤ (wᵤ * xᵤ*) ≤ 2 * OPT
由此我们证明了一个定理:我们算法的输出是一个顶点覆盖,其成本最多是最优解的两倍。这是一个2倍近似算法。
但这个分析是否紧致?这个因子2是否不可避免?答案是肯定的。
在某些情况下,算法确实会达到2倍的近似比。考虑一个包含8个顶点的环,每个顶点权重为1。最优顶点覆盖可以选择环上间隔的4个顶点,成本为4。然而,线性规划可能给出所有 xᵤ* = 0.5 的解。取整后,所有顶点都被选中,输出成本为8。此时,输出成本恰好是最优成本的2倍。因此,我们的分析是紧致的,该算法在最坏情况下就是一个2倍近似算法。
不过,我们讨论的是最坏情况。在实际应用中,该算法的性能通常接近最优解(例如在10%以内)。该算法的一个优点是,当输出解确实接近最优时,我们可以通过比较输出成本与线性规划的值来验证这一点。因为线性规划的值是已知的,如果输出成本接近它,那么我们就知道对于该特定实例,输出解也接近最优解。


在本节课中,我们一起完成了对顶点覆盖近似算法的分析。我们证明了该算法是一个2倍近似算法,并通过实例说明了该近似比是紧致的。我们还了解到,尽管最坏情况性能有保证,但算法在实际中的表现通常更好。
007:课程方法论与顶点覆盖回顾 🎯

在本节课中,我们将回顾之前学习的内容,总结设计近似算法的通用方法,并以顶点覆盖问题为例,深入探讨算法的分析技巧和该问题的计算复杂性。
上一节我们详细研究了顶点覆盖问题。但本课程的核心并非解决特定问题,而是传授解决问题的方法论。因此,让我们从方法论的角度回顾迄今为止所学的内容。
设计近似算法的三步法 📝
在顶点覆盖的例子中,我们应用了一种标准的三步法来设计近似算法。以下是具体步骤:
- 建立整数规划模型:首先,为待解决的问题找到一个合适的整数规划模型。
- 求解线性规划松弛:其次,放松整数约束,求解对应的线性规划问题。
- 对解进行舍入:最后,将线性规划的解通过某种规则(如舍入)转化为整数解。
这是一种在近似算法设计中反复使用的标准技术。
算法分析的三个要点 🔍
设计出算法后,我们需要从以下三个方面对其进行分析:
- 正确性验证:必须验证算法的输出确实满足问题的所有条件。
- 效率验证:算法必须在多项式时间内运行。在本课程中,“高效”即指多项式时间复杂度。
- 近似比分析:这是最重要的部分。必须分析输出解的值,并证明它在一个特定因子内接近最优值。对于顶点覆盖,我们证明的因子是2。
近似比分析的核心技巧:借助LP值 📊
对于这类算法,分析解的质量有一个特定的技巧:以线性规划的最优值作为中间桥梁。
具体方法是:
- 一方面,将算法输出解的值与线性规划的最优值(LP值)联系起来。
- 另一方面,将问题的最优值(OPT)与LP值联系起来。
- 最后,结合这两个关系,即可得出算法输出与OPT之间的关系。
因此,分析解质量的关键信息是:聚焦于线性规划的最优值。
关于顶点覆盖问题的更多讨论 💡
现在,让我们再谈谈顶点覆盖问题本身,特别是关于近似比的质量。
我们得到了一个2-近似算法。我们能否期望得到更好的近似算法呢?
- 能否精确求解? 不能。Karp在1972年证明了顶点覆盖是NP完全问题。
- 能否无限接近最优解? 不能。Dinur和Safra在21世纪初证明,假设P≠NP,不可能得到优于1.36的近似比。这是目前基于P≠NP假设的最佳下界结果。
- 2-近似是否最优? 存在一个条件性结果:如果“唯一游戏猜想”成立,那么2-近似算法就是最优的,不可能在多项式时间内得到严格小于2的近似比(例如1.999)。
综上所述,顶点覆盖问题在最坏情况下是难以近似的。
总结 📚

本节课中,我们一起学习了设计近似算法的通用三步法(建模、松弛、舍入),并掌握了分析算法的三个要点(正确性、效率、近似比)。我们特别强调了通过线性规划最优值来分析近似比的核心技巧。最后,我们以顶点覆盖问题为例,回顾了这些方法的应用,并了解了该问题的计算复杂性边界。
近似算法:1.8.4:半整性



在本节中,我们将学习顶点覆盖问题线性规划松弛的一个关键性质:半整性。我们将证明,总存在一个最优解,其所有变量值均为0、1/2或1,并且可以通过一个多项式时间算法找到它。

首先,我们明确要证明的定理。
定理:对于顶点覆盖问题的线性规划松弛,总存在一个最优解,其每个坐标 x_i 的值要么是0,要么是1/2,要么是1。并且存在一个多项式时间算法来构造这个解。
接下来,我们将详细描述这个算法。
算法步骤
- 求解线性规划:首先,求解顶点覆盖的线性规划松弛,得到一个最优解
x*。这个解满足所有约束,并且使得目标函数值最小。此时,变量值在0到1之间,但不一定都是0、1/2或1。 - 初始化分类:我们将变量分为三类:
- 冻结变量:值已经是0、1/2或1的变量。这些变量在后续步骤中保持不变。
- 大变量:值严格介于0.5和1之间的变量(即
0.5 < x_i < 1)。 - 小变量:值严格介于0和0.5之间的变量(即
0 < x_i < 0.5)。
- 观察性质:根据线性规划的约束(对于每条边
(u, v),有x_u + x_v >= 1),我们可以得出一个重要性质:一个小变量不能与另一个小变量相邻。因为如果两个小变量相邻,它们的和将严格小于1,违反约束。 - 构造扰动解:我们尝试对当前解
x*进行两种微小的扰动,得到两个新解y和z。设ε为一个很小的正数。- 对于解
y:将所有小变量的值增加ε,将所有大变量的值减少ε。 - 对于解
z:将所有小变量的值减少ε,将所有大变量的值增加ε。
- 对于解
- 可行性分析:对于足够小的
ε,解y和z仍然是可行的(即满足所有约束)。原因如下:- 如果一条边连接一个小变量和一个大变量,在
y和z中,一个增ε,一个减ε,总和不变,仍然满足>=1。 - 如果一条边连接两个大变量,在
y中两者都减ε,但原来总和>1,对于足够小的ε,总和仍>=1;在z中两者都加ε,总和变得更大,自然满足约束。 - 如果一条边连接一个小变量和一个值为1的冻结变量,在
y中小变量增ε,总和>1;在z中小变量减ε,但原来总和>1(因为1加一个正数),对于足够小的ε,总和仍>=1。
- 如果一条边连接一个小变量和一个大变量,在
- 最优性分析:由于
x*是最优解,任何可行解的目标函数值都不小于x*的值。因此,y和z的目标函数值都<=x*的值。同时,y和z的平均值恰好等于x*的值(因为增减ε相互抵消)。这意味着y和z的目标函数值必须都等于x*的值。因此,y和z也是最优解。 - 增加ε直到变量“冻结”:现在,我们不再将
ε视为无穷小,而是逐渐增大它。随着ε增大,以下四种事件之一会最先发生:- 某个小变量的值增加到0.5。
- 某个大变量的值减少到0.5。
- 某个小变量的值减少到0。
- 某个大变量的值增加到1。
- 冻结新变量并迭代:当上述任一事件发生时,我们停止增加
ε。此时,我们得到了一个新的最优解(y或z,取决于哪个事件发生),并且这个解中有一个额外的变量被“冻结”了(其值变为0、0.5或1)。我们以这个新解作为起点,重复步骤2-7。 - 算法终止:每次迭代都至少冻结一个变量。由于变量总数有限,经过有限次迭代后,所有变量都将被冻结。最终我们得到一个最优解,其中所有变量的值都是0、1/2或1。


上述过程不仅是一个证明,也直接给出了一个多项式时间的构造算法。因为每次迭代(冻结一个变量)的工作量是多项式时间的,且总迭代次数不超过变量个数。


总结
本节课中,我们一起学习了顶点覆盖线性规划松弛的半整性性质。我们证明了总存在一个坐标仅为0、1/2或1的最优解,并通过一个逐步“冻结”变量的扰动算法,展示了如何在多项式时间内找到这样的解。这个性质是设计基于线性规划的近似算法时非常有用的工具。
009:背包问题定义与初步探索 🎒

在本节课中,我们将学习近似算法中的另一个经典问题——背包问题。我们将从定义问题开始,尝试设计算法,并分析其局限性,最后聚焦于一个特殊的简化情况。
问题定义
背包问题是一个常见的组合优化问题。想象你要去露营,面前摆满了可能想带的物品,每件物品都有其重量(或体积)和价值。但你的背包容量有限,因此需要决定带哪些物品,以在不超过背包容量的前提下,最大化所带物品的总价值。
形式化定义如下:
- 给定一个容量
B(背包能承受的最大重量)。 - 给定
n件物品,其中第i件物品的重量为s_i,价值为v_i。 - 目标:选择一个物品子集,使其总重量不超过
B,同时总价值最大化。
该问题与另外20个问题一起,在Dick Karp关于NP完全性的著名论文中被证明是NP难的。因此,我们通常无法在多项式时间内精确求解,这使得它成为设计近似算法的绝佳候选。
图形化表示与贪心算法尝试
设计近似算法的一个通用思路是寻找问题的良好表示。对于背包问题,每件物品由两个数字(重量 s_i 和价值 v_i)定义,很自然地可以将其表示为一个长宽分别为 v_i 和 s_i 的矩形。
当你选择多件物品时,相当于将这些矩形沿对角线方向拼接(一个矩形的东北角与下一个矩形的西南角相连)。这样,拼接形成的“阶梯”在水平方向的总长度代表总价值,在垂直方向的总高度代表总重量。容量限制 B 意味着这个阶梯的高度不能超过 B,而我们的目标是让阶梯尽可能向右延伸(即最大化总价值)。
这种表示法提示我们,高价值、低重量(即图形上“扁平”)的矩形更为理想。因此,一个直观的想法是按照物品的“价值密度”(即价值与重量的比值 v_i / s_i)降序(或等价地,按 s_i / v_i 升序)考虑物品。
基于此,我们尝试第一个算法:贪心算法。
- 将物品按
s_i / v_i升序排序。 - 依次尝试将物品放入背包,只要放入后不超过容量
B就保留。
这个算法运行速度快(主要是排序的时间),且总能给出一个可行解。然而,它的近似性能可能非常差。
贪心算法为何失败
让我们通过一个例子来说明贪心算法的问题。
假设背包容量 B = 100,有两件物品:
- 物品1:价值
v1 = 1,重量s1 = 1。 - 物品2:价值
v2 = 99,重量s2 = 100。
计算比值 s_i / v_i:物品1为 1,物品2约为 1.01。贪心算法会先选择比值更小的物品1放入背包。放入后,剩余容量为 99,而物品2的重量为 100,无法放入。因此,算法最终解的总价值为 1。
然而,最优解是只放入物品2,总价值为 99。在这个例子中,贪心算法的解与最优解相差了 99 倍,且通过调整参数,这种差距可以任意大。因此,简单的贪心策略不是一个好的近似算法。
探索特殊情形以获取直觉
当通用算法失败时,另一个设计近似算法的通用方法是研究特殊情形以培养直觉。
以下是几个简单的特殊情形:
- 所有物品重量相同:此时只需按价值降序贪心选取,直到放不下为止。这是最优策略。
- 所有物品价值相同:此时只需按重量升序贪心选取,直到放不下为止。这也是最优策略。
- 所有物品的重量等于其价值(即
s_i = v_i):此时所有物品的比值s_i / v_i都等于1,上述贪心算法无法区分它们,选取顺序变得任意。这个情形开始变得有趣,它不再是平凡的。

本节总结
本节课我们一起学习了背包问题的定义,并尝试用图形化方式理解它。我们设计了一个按价值密度排序的贪心算法,但通过反例发现其近似比可能任意差。最后,我们通过分析几个特殊情形来寻找新的算法设计思路,并决定接下来重点研究 “所有物品重量等于价值” 这一非平凡的特殊情形。在下一节中,我们将针对这个特殊情形设计新的算法。
010:贪婪算法讲座

在本节课中,我们将学习针对背包问题的一种特殊情况——物品大小等于其价值——的贪婪算法。我们将分析该算法的性能,并证明它能够提供一个近似比为2的近似解。
概述
我们定义了背包问题,并决定暂时专注于一个特殊情况:每个物品的大小等于其价值。本节将为此特殊情况设计一个算法,并分析其近似性能。
特殊情况与算法设计
这种情况看似简单。我们的目标是最大化装入背包的物品总价值。一个自然的想法是尝试按价值递减的顺序依次选取物品。这个算法是贪婪算法的另一个版本,即按价值递减顺序选取物品。
从图形上看,当大小等于价值时,每个物品是一个正方形,而非矩形。我们的算法是:将正方形按尺寸递减排序,然后贪婪地依次尝试将它们装入背包,只要其总高度不超过背包容量 B。
以下是算法的核心步骤:
- 排序:将所有物品按价值(即大小)从大到小排序。
- 贪婪选择:按排序后的顺序,依次尝试将物品放入背包。如果当前物品放入后,背包内物品总高度不超过
B,则放入;否则,跳过。
# 伪代码示例:贪婪算法(价值递减)
def greedy_knapsack(items, capacity_B):
# 假设每个物品 item 有属性 value,且 size = value
sorted_items = sorted(items, key=lambda x: x.value, reverse=True)
total_value = 0
knapsack = []
for item in sorted_items:
if total_value + item.value <= capacity_B:
knapsack.append(item)
total_value += item.value
return knapsack, total_value
算法分析
现在我们来分析这个贪婪算法的性能。我们将建立算法输出值(Output)与最优解值(Opt)之间的关系。
首先,我们观察到对于任何可行解(包括最优解),其总高度(即总价值)必须小于等于背包容量 B。因此,我们有:
公式: Opt ≤ B
接下来,我们需要将算法输出值与 B 联系起来。考虑算法执行过程:输出解由一组被选中的“蓝色”物品组成。假设存在至少一个被算法拒绝的“红色”物品(即因为放入它会超过容量 B 而被跳过)。
我们可以做以下合理假设:
- 输出解中至少有一个物品(否则,若所有物品都无法装入,则最优解也为0,算法输出即为最优)。
- 至少存在一个被拒绝的物品(否则,若所有物品都被装入,则算法输出即为最优解)。
考虑算法选取物品的顺序。第一个被放入背包的物品是能放入背包的最大物品。因此,这个第一个物品的尺寸大于或等于任何被拒绝物品的尺寸。
设输出解的总价值为 Output,并考虑一个特定的被拒绝物品(红色)。由于该物品被拒绝,意味着如果将它加入当前输出解,总价值将超过 B。即:
公式: Output + value(红色物品) > B
又因为第一个被选中的物品(蓝色)尺寸不小于这个红色物品,所以 Output ≥ value(第一个蓝色物品) ≥ value(红色物品)。
结合以上两个不等式,我们可以推导出:
公式: Output + Output ≥ Output + value(红色物品) > B
因此,我们得到:
公式: Output > B / 2
最后,结合之前得到的 Opt ≤ B,我们得出结论:
公式: Output > B / 2 ≥ Opt / 2 或者说 Opt < 2 * Output
这证明了该贪婪算法是一个2-近似算法。它显然在多项式时间内运行(主要是排序的时间复杂度),并且总能给出一个可行的解。
本节总结与过渡
在本节中,我们一起学习了针对“物品大小等于价值”这一特殊背包问题的贪婪算法。我们详细分析了算法步骤,并通过数学推导证明了该算法具有2的近似比,即其解的价值至少是最优解的一半。

然而,2-近似并不够好。我们能否得到比2-近似更好的结果呢?这将是下一节我们要探讨的内容。在下一节中,我们将尝试开发一个性能远优于2-近似的算法。
011:动态规划求解特殊背包问题 🎒

在本节课中,我们将学习如何利用动态规划技术,为一种极其特殊的背包问题设计一个最优算法。我们将从一个简单的近似算法出发,探索如何通过增加额外假设来获得精确解。
上一节我们介绍了当物品价值等于其大小时的2-近似算法。本节中,我们来看看如何通过引入更强的假设,来设计一个能给出最优解的算法。
问题定义与假设
我们考虑一个特殊中的特殊情况。除了要求每个物品的大小等于其价值外,我们增加一个关键假设:
- 所有物品的价值都是小整数。
- 背包的容量
B也是一个小整数。
这里的“小”是相对于物品数量 n 而言的,具体指价值 v_i 和容量 B 的数值范围有限。在这个假设下,背包问题不再是NP难问题,我们可以通过动态规划精确求解。
动态规划算法设计
动态规划的核心思想是:将大问题分解为相互关联的小问题,并存储中间结果以避免重复计算。对于背包问题,我们需要定义状态,并找到状态之间的递推关系。
定义状态
我们需要记录关于“过去”决策的哪些信息,以便“未来”能做出最优决策?假设我们已经考虑了前 i 个物品,并决定将其中一部分装入背包。
以下是关键信息:
- 我们不需要记住具体选择了哪些物品。
- 我们只需要记住目前已装入物品的总价值。
因此,我们定义一个布尔数组 A:
A[i][v]表示:是否存在一个由前i个物品组成的子集,其总价值恰好等于v。- 其中
i的范围是0到n,v的范围是0到B。
推导递推公式
现在,我们需要找到计算 A[i][v] 的方法。考虑是否将第 i 个物品放入背包,存在两种可能性:
- 不放入第
i个物品:那么,总价值v必须能够由前i-1个物品组成的某个子集达到。即A[i][v]为真,当且仅当A[i-1][v]为真。 - 放入第
i个物品:这要求第i个物品的价值v_i不超过目标价值v。放入后,剩余需要由前i-1个物品凑出的价值为v - v_i。即A[i][v]为真,当且仅当v >= v_i且A[i-1][v - v_i]为真。
综合这两种情况,我们得到递推公式:
A[i][v] = A[i-1][v] OR (v >= v_i AND A[i-1][v - v_i])
算法流程与复杂度
基于上述递推公式,我们可以按顺序填充数组 A。算法步骤如下:
- 初始化:
A[0][0] = True(空集的总价值为0),其他A[0][v] = False。 - 递推计算:对于
i从1到n,对于v从0到B,根据上述公式计算A[i][v]。 - 得出结果:在所有物品都考虑完毕后(即
i = n),最优解就是满足A[n][v] = True的最大v值。
该算法包含两层循环,分别遍历 n 个物品和 B 个可能的价值。因此,其时间复杂度为 O(n * B)。

本节总结
本节课中我们一起学习了如何利用动态规划解决一个特殊的背包问题。我们首先定义了问题的状态 A[i][v],然后推导出关键的递推公式,最后描述了算法的执行流程并分析了其时间复杂度 O(n * B)。这个算法在物品价值和背包容量都是“小整数”的假设下,能够给出最优解。
在下一节中,我们将探讨如何将这个针对特殊情况的精确算法进行扩展和改造,从而为更一般的背包问题设计出有效的近似算法。我们将尝试逐步放松“特殊特殊情况”的限制,让算法能应用于更广泛的场景。
012:通用动态规划讲座

在本节课中,我们将学习如何使用动态规划方法解决一个特定版本的背包问题。我们将从一个非常特殊的情况开始,逐步扩展到物品价值为小整数的更一般情况,并最终推导出相应的算法。
概述
到目前为止,我们已经解决了一个非常特殊、非常特殊情况下的背包问题。这个情况本身似乎并不十分有趣,但我们将运用相同的思路,尝试解决一个稍微更一般的情况。
从特殊到一般
上一节我们介绍了特殊情况的解法,本节中我们来看看这个“不那么特殊”的特殊情况是什么。这种情况是:物品的价值是小整数。每个物品和之前一样,有大小和价值,但大小不再等于价值。大小可以是任意的,但物品的价值是1到大写N范围内的小整数,其中大写N可以是固定的。
动态规划思路
我们将扩展先前从非常特殊情况中得出的思路。我们是如何解决那个非常特殊情况的?我们使用了动态规划。因此,让我们再次从动态规划开始。动态规划的思路是,我们必须确定要定义什么样的“接口”。给定一个部分解,对于前 i 个物品,我们希望记住足够多的部分解信息,以便能够最优地完成这个解。
以下是我们的做法:
- 我们将向这部分添加一些物品。
- 我们将为任意一个起始状态最优地完成填充。
因此,我们需要一个接口来告诉我们在这里要记住什么。要记住什么?之前,对于每一个 i,对于前 i 个物品,我们记录了是否有一个子集能够为每一个可能的价值 V 达到该价值 V。现在,我们再次关注前 i 个物品和特定的价值 v,但为了弄清楚能否在某个解的基础上添加物品,我们需要记住大小。我们必须记住大小。
定义动态规划表
大小可以是任意的,可能很小,也可能很大,可能是长数字,情况复杂。它们的和有很多种,存在指数级的可能性。因此,我们要做的是记住那些“有趣”的大小。在表项 A[i][v] 内部,我们记住大小。让我们更具体一些。
在 A[i][v] 中,我们将记住:对于前 i 个物品中总价值为 v 的任何子集,其可达到的大小。实际上,并不完全是这样。对于前 i 个物品中总价值为 v 的任何子集,可能有许多不同的可达到的大小。我们只需要记住这些值中的最小值。为什么?因为如果对于相同的价值,我们可以用这个大小、那个大小在背包中实现它,那么我们不妨使用可能的最小大小。
因此,在 A[i][v] 中,我们将这个表项定义为:任何总价值等于 v 的前 i 个物品的子集所能达到的最小大小。
推导递推关系
现在我们已经定义了动态规划表,我们需要推导出一个递推关系,以便在算法中填充这些表项。
什么时候会存在前 i 个物品的某个子集,其总大小为 S 同时总价值等于 v?这取决于物品 i。这取决于子集是否包含物品 i。有两种情况。
以下是两种情况:
- 情况1:物品 i 不在子集中。这很简单,我们只需要查看前 i-1 个物品,并且必须能够以总价值 v 达到大小 S。
- 情况2:物品 i 在子集中。那么它使用了背包的 S 大小。因此,子集中的其他物品的总大小必须为 S - s_i。并且它提供了价值 v,所以子集的其余部分必须提供价值 v - v_i。
这个推理现在给出了一个递推公式,可以用代数方式表达如下:
令 A[i][v] 表示任何总价值为 v 的前 i 个物品的子集所能达到的最小大小。
如果 v ≥ v_i,那么 A[i][v] 是两种可能性中的最小值:一种是物品 i 在子集中,另一种是不在。
A[i][v] = min( A[i-1][v], A[i-1][v - v_i] + s_i )
如果 v < v_i,那么物品 i 不能放入子集,那么显然 A[i][v] 就是 A[i-1][v]。
算法与复杂度
这给出了我们的递推关系。我们将按照 i 值递增的顺序使用它来填充表项,这引出了我们针对这种特殊情况的算法。
以下是算法的大致框架(未详细说明初始化和最后步骤,这些很容易自行检查):
for i from 1 to n:
for v from 0 to n*N:
if v >= v_i:
A[i][v] = min(A[i-1][v], A[i-1][v - v_i] + s_i)
else:
A[i][v] = A[i-1][v]
重要的是包含 min 操作的那一行。
运行时间是多少?外层循环是 O(n)。内层循环 v 从 0 扫描到 n * N。因为这是可想象的最大价值。为什么?因为每个物品的价值最多为 N,即使每个物品都放入背包,总价值也将是 n * N。因此,由于这两个循环,运行时间将是 O(n² * N)。当 N 是常数时,这意味着算法是二次的。
核心思想回顾
我们如何设计这个算法?我们必须考虑尝试动态规划方法。核心思想确实出现在动态规划数组的定义中,这个定义以恰到好处的方式被选择,使其能够拥有一个递推公式。

我们看到它在多项式时间内工作,它为我们提供了将物品放入背包的最佳方式,以便在尊重背包容量的情况下最大化价值。
总结
本节课中,我们一起学习了如何在所有物品都具有小价值的情况下精确解决背包问题。在下一部分,我们将以此为基础,最终为背包问题设计一个近似算法。
近似算法:第1章:背包问题的一般情况算法


在本节课中,我们将学习如何解决一般情况的背包问题。我们将利用之前为特殊情况设计的算法,通过一个巧妙的归约,构建出一个适用于一般情况的近似算法。
上一节我们介绍了当物品价值均为小整数时的背包问题精确算法。本节中,我们来看看如何将这个算法推广到处理任意正实数价值的物品。
核心思路是进行归约:我们将修改一般情况的输入,使其价值变为小整数,然后将其作为“黑盒”输入到我们已有的精确算法中。
以下是实现这一归约的具体步骤:
- 预处理:首先,丢弃所有尺寸超过背包容量
B的物品,因为它们无论如何都无法被装入。 - 缩放:将所有剩余物品的价值
v_i乘以一个缩放因子α,使得缩放后的最大价值恰好等于一个我们选择的参数N。即α = N / max(v_i)。 - 取整:将每个缩放后的价值向下取整到最近的整数,得到新的整数值
v'_i = floor(v_i * α)。 - 调用精确算法:将修改后的输入(容量
B,尺寸s_i,新价值v'_i)输入到之前为小整数价值设计的动态规划算法中。 - 输出:算法返回一个物品集合,我们直接输出这个集合作为最终解。
这个算法的运行时间主要取决于动态规划部分。动态规划的时间复杂度为 O(n^2 * N),其中 n 是物品数量。如果我们选择 N = 100n,那么总运行时间就是 O(100 * n^3),这是一个多项式时间(立方时间)算法。

本节课中我们一起学习了解决一般背包问题的近似算法。该算法通过缩放和取整将原始问题归约到我们已解决的特殊情况,从而在多项式时间内得到一个近似解。接下来,我们将分析这个近似解的质量,证明其价值接近最优解。
014:背包问题近似算法分析 🎒


在本节课中,我们将学习如何分析一个针对背包问题的近似算法的性能。我们将证明该算法能在多项式时间内运行,并且其解的值至少能达到最优值的99%。
上一节我们介绍了针对一般背包问题设计的算法,并证明了其多项式时间复杂度。本节中,我们将分析该算法的近似比,这是本章的技术核心。
首先,我们需要一些符号定义。算法输出的物品集合记为 S。我们知道 S 是针对缩放并取整后的输入的最优解,这是通过动态规划得到的。
其次,记 S* 为针对原始输入的最优解集合。由于缩放操作不会改变最优解的结构,S* 同时也是针对缩放但未取整输入的最优解。
现在,我们需要将 S 在缩放取整输入上的值,与 S* 在缩放未取整输入上的值联系起来。
首先,我们通过约简处理了输入,因此需要关注两个值:S 在原始输入上的值,以及 S 在修改后输入上的值。我们来建立这两个值的关系。
S 在原始输入上的值可以表示为:
value(S) = sum_{i in S} v_i
经过缩放(乘以因子 α)和向下取整后,修改后的价值 v'_i 满足:
α * v_i >= v'_i
因此,S 在原始输入上的值至少是其在修改后输入上值的 1/α 倍:
value(S) >= (1/α) * sum_{i in S} v'_i
接下来,在修改后的输入上,比较 S 和 S* 的值。由于 S 是该输入上的最优解,其值不小于 S* 的值:
sum_{i in S} v'_i >= sum_{i in S*} v'_i
现在,我们需要将 S* 在修改后输入上的值,关联回其在原始输入上的值。取整操作是向下取整到最近的整数,因此对于每个物品,有:
v'_i >= α * v_i - 1
对 S* 中的所有物品求和,得到:
sum_{i in S*} v'_i >= α * sum_{i in S*} v_i - |S*|
由于 |S*| 最多为物品总数 n,因此:
sum_{i in S*} v'_i >= α * sum_{i in S*} v_i - n
这里,sum_{i in S*} v_i 正是原始问题的最优值 OPT。
现在,将上述所有不等式串联起来:
value(S) >= (1/α) * sum_{i in S} v'_i >= (1/α) * sum_{i in S*} v'_i >= (1/α) * (α * OPT - n) = OPT - n/α
我们得到了一个加性误差:n/α。
为了得到相对误差界,我们需要处理这个加性项。为此,我们利用预处理步骤中得到的一个 OPT 的下界。
在预处理中,我们丢弃了所有单个尺寸就超过背包容量的物品。因此,剩余的任何单个物品都能放入背包,这意味着 OPT 至少等于所有剩余物品中的最大价值:
OPT >= max_i v_i
将这个下界代入我们的误差表达式。回顾一下,缩放因子 α 被定义为 N / max_i v_i,其中我们设定 N = 100n。
代入 α 的定义和 OPT 的下界:
value(S) >= OPT - n/α = OPT - n * (max_i v_i / N) >= OPT - n * (OPT / N) = OPT * (1 - n/N)
最后,代入 N = 100n:
value(S) >= OPT * (1 - n/(100n)) = OPT * 0.99
至此,我们证明了算法给出的解,其值至少是最优值的99%。


本节课中,我们一起学习了如何逐步分析背包问题近似算法的近似比。我们通过关联原始输入与修改后输入上的解值,利用最优解的性质和设定的参数,最终证明了算法能达到99%的近似保证。在下一节,我们将以此为基础,将其扩展成一个完整的近似方案。
近似算法:第一讲:背包问题的近似方案 🎒

在本节课中,我们将学习如何为背包问题设计一个近似方案。我们将看到,通过调整算法中的参数,可以获得任意接近最优解的近似解,同时保持算法运行时间在多项式范围内。
我们之前已经见过一个背包问题的近似算法,它能给出一个非常接近最优解的方案。
现在,我们希望得到一个近似方案。也就是说,我们希望做得比之前更好。
如何才能做得更好呢?这里有一种改进方法。我们不再将 N 定义为 1000 * n,而是使用一个更大的 N 值。具体来说,令 N = 1000 * n。
其余步骤与之前相同:我们像之前一样进行缩放和舍入,像之前一样应用动态规划算法,并输出对应的物品集合。
这个新算法与之前算法的唯一区别在于,N 现在是 1000 * n,而不是 100 * n。
这个改变带来了什么?它带来的变化是,现在得到的背包问题解不再是 99% 的近似,而是 99.9% 的近似。这个解非常接近最优解,并且运行时间仍然是多项式级别的。
我们可以继续这个过程。也就是说,对于每一个 ε,我们都可以设计一个算法,其运行时间是输入规模的多项式,并且其解的值非常接近最优值。解的误差最多为 ε * OPT。
因此,我们得到的是一个近似方案。背包问题拥有一个近似方案。我们可以得到最优解的 99%、99.9%,甚至 (1 - ε) 倍。ε 越小,我们得到的解就越接近最优解。
那么,为什么不令 ε 趋近于零呢?这样我们就能在多项式时间内找到精确的最优解了。然而,我们无法做到这一点,因为该问题是 NP 难的,所以我们知道不可能做到。同时,随着 ε 趋近于零,运行时间也会增加。当 N = n / ε 时,运行时间与 N 成线性关系。随着 ε 趋近于 0,运行时间将趋近于无穷大。这就是为什么我们无法使用近似方案来精确解决问题。
我们为背包问题设计近似算法的核心方法是什么?
我们展示了如何首先简化输入,将一个复杂的输入规约到一个简单的特例,然后设计一个适用于这种简单输入的算法。
这种方法的关键在于,在简化输入的同时,不损失太多的最优解价值。
通过这种技术,我们解决了背包问题。这为我们研究“舍入”技术提供了契机。这里的舍入是针对输入进行的,与我们之前在研究顶点覆盖问题时对输出进行的舍入不同。
值得注意的是,在所有近似算法中,这可能是最实用的算法之一。背包问题是一个基础问题,而这个算法易于实现,并且在实践中相当快速。
需要观察的是,运行时间不仅是输入规模的多项式,也是 1/ε 的多项式。当然,随着 ε 趋近于零,运行时间会趋近于无穷大,但只是以 1/ε 的多项式速率增长。
因此,这样的近似方案被称为完全多项式时间近似方案。背包问题拥有一个完全多项式时间近似方案。这是本章节你可以记住的核心结论。
设计近似算法的一种技巧是:对输入进行舍入,以简化输入,将其规约到一个特例。

下一讲,我们将看到使用更复杂舍入技术的其他近似方案。
近似算法:第1章:装箱问题与Next Fit算法

在本节课中,我们将学习组合优化中最著名的问题之一——装箱问题。我们将介绍一个极其简单的算法,即Next Fit算法,并分析其性能。
装箱问题定义
装箱问题的目标是:给定一组物品,将它们装入尽可能少的箱子中。我们研究该问题最基础的版本。
- 输入包含
n个物品。 - 每个物品
i有一个参数s_i描述其大小。 - 我们约定每个物品的大小
s_i不超过 1。 - 目标是使用最少数量的、容量为 1 的箱子来装下所有物品。
Next Fit算法
接下来,我们介绍一个非常朴素的算法,它可能是你首先想到的算法。这个算法被称为Next Fit算法。
以下是该算法的描述:
我们一次处理一个箱子。在任何时候,我们都有一个“开放”的箱子,正试图将物品放入其中。我们按任意顺序逐个处理物品。对于当前物品,我们尝试将其放入当前开放的箱子中。
- 如果它放得下,我们就把它放进去。
- 如果它放不下,我们就关闭当前箱子,打开一个新箱子,并将该物品放入新箱子。
这就是算法的全部。它非常简单。
让我们看一个例子。假设我们有六个物品,大小分别为:0.7, 0.6, 0.4, 0.3, 0.45, 0.5。我们按此顺序处理:
- 物品1(0.7)放入箱子1。
- 物品2(0.6)尝试放入箱子1,但放不下(0.7+0.6>1)。因此,关闭箱子1,打开箱子2,将物品2放入箱子2。
- 物品3(0.4)放入箱子2(0.6+0.4=1)。
- 物品4(0.3)尝试放入箱子2,但放不下(1+0.3>1)。因此,关闭箱子2,打开箱子3,将物品4放入箱子3。
- 物品5(0.45)放入箱子3(0.3+0.45=0.75)。
- 物品6(0.5)尝试放入箱子3,但放不下(0.75+0.5>1)。因此,关闭箱子3,打开箱子4,将物品6放入箱子4。
最终,我们使用了4个箱子来装下这6个物品。
显然,这个算法运行时间很短。但它做得好吗?它确实装下了所有物品,但会不会浪费了一些箱子?它有多糟糕?
Next Fit算法分析
为了回答上述问题,我们需要分析Next Fit算法的性能。在近似算法中,我们希望将Next Fit算法使用的箱子数量与最优箱子数量进行比较。
让我们通过一个例子来建立一些直觉。为了便于处理整数,假设箱子容量为100。我们有一系列大小在1到99之间的物品。使用Next Fit算法后,假设我们使用了31个箱子。
观察结果,我们试图找出Next Fit算法展现出的某种结构。让我们关注箱子7。箱子7包含三个物品:25, 14, 25。下一个到达的物品是61,但25+14+25+61 > 100,所以我们关闭箱子7,打开箱子8,并将物品61放入箱子8。
我们观察到:箱子7的内容物(25+14+25)加上箱子8的第一个物品(61)的总和大于100。
一般规律:
对于任意两个连续的箱子(例如第 (2i-1) 个和第 2i 个箱子),这两个箱子中所有物品的大小总和大于箱子的容量(这里是100)。原因在于算法规则:我们不会关闭一个箱子,除非下一个物品放不进去。因此,当第 2i 个箱子被打开时,意味着第 (2i-1) 个箱子的内容加上第 2i 个箱子的第一个物品已经超过了容量。
推导关系式:
假设Next Fit使用了 K 个箱子。我们可以将箱子两两配对(除了最后一个箱子可能落单)。对于每一对连续的箱子,其内容物总大小大于1(或容量值)。因此,所有物品的总大小至少是 (K-1)/2。
与最优解的关系:
另一方面,考虑最优解 OPT。即使最优解完美地打包,每个箱子最多也只能装满容量1。因此,所有物品的总大小最多是 OPT * 1。
结合两个关系:
我们有两个不等式:
- 总大小 ≥
(K-1)/2 - 总大小 ≤
OPT
结合它们,得到:OPT ≥ (K-1)/2。整理后得到:K ≤ 2 * OPT + 1。
结论与讨论
我们证明了Next Fit算法使用的箱子数 K 最多是最优解 OPT 的两倍再加一。如果没有这个“加一”,我们称之为2-近似算法。由于这个额外的箱子,我们称其为渐近2-近似算法,即当 OPT 很大时,性能比趋近于2。
- 这个界限是紧的吗? 是的,可以构造例子使得最优解需要约
N/2个箱子,而Next Fit需要约N个箱子。 - 对于小的
OPT呢? 当OPT很小时,区分需要2个箱子和需要3个箱子的实例本身是NP难的,所以这种渐近分析是合理的。
本节总结
在本节中,我们一起学习了:
- 装箱问题的定义:将大小不超过1的物品装入最少数量容量为1的箱子。
- Next Fit算法:一个极其简单的在线算法,只维护一个当前打开的箱子。
- 算法分析:我们证明了Next Fit是一个渐近2-近似算法,即
NF ≤ 2 * OPT + 1。
我们学到的重要启示是:即使是非常简单的算法,有时也能提供相当好的性能保证。在算法设计中,“保持简单”是一个重要原则,因为简单的算法通常更容易分析。

为了获得更好的算法,我们接下来将尝试使用线性规划。
近似算法:第1章:线性规划松弛

在本节中,我们将学习如何为装箱问题设计一个线性规划松弛模型,并探讨其局限性。
上一节我们介绍了“下次适应”算法及其渐近近似比。本节中,我们来看看能否设计出更好的算法。为此,我们将尝试为装箱问题构建一个线性规划松弛模型。
我们的最终目标是设计一个渐近近似方案,即一个算法,对于任意给定的 ε,能输出一个装箱方案,其所需箱数不超过 (1 + ε) * OPT + 低阶项。为了实现这个目标,我们首先采用组合优化问题的标准方法:将其建模为整数规划,然后尝试其线性规划松弛。
首先,我们需要为装箱问题找到一个整数规划模型。回忆一下,在装箱问题中,我们有 n 个物品,目标是用最少的单位容量箱子装下它们。
我们将为一个稍有不同的决策版本问题建立整数规划:给定 n 个物品和 K 个单位容量箱子,目标是判断是否存在一种将这 n 个物品装入这 K 个箱子的方案。显然,如果能解决这个问题,就能解决原问题,两者是等价的。
如何为这个问题建立整数规划呢?我们需要使用一些 0-1 变量。一个装箱方案需要决定每个物品放入哪个箱子。因此,我们为每个物品 i 和每个箱子 j 定义一个变量 X_ij。
X_ij 的定义如下:
X_ij = 1当且仅当物品 i 被放入箱子 j。X_ij = 0当且仅当物品 i 未被放入箱子 j。
例如,考虑屏幕上的装箱方案。物品 A 放入箱子 1,所以 X_A1 = 1。物品 B 和 F 放入箱子 2,所以 X_B2 = 1 且 X_F2 = 1。其他情况以此类推。所有未使用的 X_ij 都等于 0。
这样,物品的装箱方案与变量 X_ij 的 0-1 赋值之间就建立了一一对应关系。现在,我们需要为这个整数规划添加约束条件。
以下是约束条件:
-
每个物品必须放入一个箱子:对于每个物品 i,它必须被放入 K 个箱子中的某一个。这意味着对于每个 i,所有箱子 j 对应的
X_ij之和必须等于 1。- 公式:对于每个物品 i,
∑_{j=1}^{K} X_ij = 1。
- 公式:对于每个物品 i,
-
箱子容量限制:对于每个箱子 j,放入其中的所有物品的大小之和不能超过箱子的容量(假设为 1)。对于物品 i,如果
X_ij = 1,则贡献其大小s_i;如果X_ij = 0,则贡献 0。因此,对于每个箱子 j,所有物品 i 的X_ij * s_i之和必须 ≤ 1。- 公式:对于每个箱子 j,
∑_{i=1}^{n} X_ij * s_i ≤ 1。
- 公式:对于每个箱子 j,
-
变量为整数:每个
X_ij必须是 0 或 1。- 公式:对于所有 i, j,
X_ij ∈ {0, 1}。
- 公式:对于所有 i, j,
综上所述,我们的整数规划模型如下:
- 变量:
X_ij,对于所有物品 i 和箱子 j (1 ≤ i ≤ n, 1 ≤ j ≤ K)。 - 约束:
- 对于每个物品 i:
∑_{j=1}^{K} X_ij = 1。 - 对于每个箱子 j:
∑_{i=1}^{n} X_ij * s_i ≤ 1。 - 对于所有 i, j:
X_ij ∈ {0, 1}。
- 对于每个物品 i:
这个整数规划是可行的(有解),当且仅当存在一种将物品装入 K 个箱子的方案。
现在,为了得到线性规划松弛,我们只需将最后一个约束条件 X_ij ∈ {0, 1} 替换为 X_ij 是介于 0 和 1 之间的实数。
因此,线性规划松弛模型为:
- 变量:
X_ij,对于所有物品 i 和箱子 j。 - 约束:
- 对于每个物品 i:
∑_{j=1}^{K} X_ij = 1。 - 对于每个箱子 j:
∑_{i=1}^{n} X_ij * s_i ≤ 1。 - 对于所有 i, j:
0 ≤ X_ij ≤ 1。
- 对于每个物品 i:
我们想利用这个线性规划松弛来设计装箱算法。首先需要评估这个松弛模型的质量,即它是否足够接近原始的整数规划。我们损失了多少精度?
这里有一个坏消息。考虑以下反例:
- 有 5 个物品。
- 每个物品的大小均为 0.6。
- 箱子容量为 1。
显然,至少需要 5 个箱子才能装下这些物品,所以最优解 OPT = 5。
然而,对于线性规划松弛模型,如果我们设定箱子数量 K = 3,可以构造一个可行的分数解:
- 令所有
X_ij(对于 i=1..5, j=1..3)都等于1/3。
验证约束:
- 对于每个物品 i:
X_i1 + X_i2 + X_i3 = 1/3 + 1/3 + 1/3 = 1。满足。 - 对于每个箱子 j(例如箱子 1):
∑_{i=1}^{5} X_i1 * s_i = 5 * (1/3) * 0.6 = 1。满足容量限制(≤1)。
因此,根据线性规划松弛,这 5 个大小为 0.6 的物品似乎可以“分数地”装入 3 个箱子。但在现实中,需要 5 个箱子。这意味着线性规划松弛的最优值(此处为 3)至少比整数规划的最优值(5)小了 5/3 倍。
这个例子表明,我们为获得更好装箱方案而设计的第一个线性规划松弛模型失败了。它的整数间隙(Integrality Gap)至少为 5/3,这意味着任何基于此松弛模型的算法,其近似比可能不会优于 5/3。
因此,我们必须寻找更好的思路,探索装箱问题更深层的结构或性质。这将是我们在下一部分要着手进行的工作。

本节课中,我们一起学习了如何为装箱问题构建整数规划和线性规划松弛模型。我们定义了决策变量 X_ij 来表示物品 i 是否放入箱子 j,并建立了相应的约束条件。然而,通过一个简单的反例,我们发现这个基本的线性规划松弛模型的整数间隙较大(至少 5/3),无法直接用于设计高质量的近似算法。这促使我们需要在后续内容中寻找更强大的建模技巧。
018:特殊案例分析

在本节课中,我们将学习如何通过分析装箱问题的特殊案例来获得算法设计的直觉。我们将首先回顾之前尝试线性规划松弛法的失败,然后转向一种新的元工具:通过解决特殊案例来发展直觉。我们将从所有物品尺寸都较小的情况开始分析。
特殊案例:小物品
上一节我们介绍了线性规划松弛法在解决一般装箱问题时的局限性。本节中,我们来看看一个特殊的案例:当所有物品的尺寸都小于箱子容量的三分之一时,情况会如何。
小物品情况下的“下次适应”算法
以下是“下次适应”算法在这种特殊输入下的行为分析。我们通过一个例子来观察:
假设物品尺寸序列为:0.2, 0.3, 0.25, 0.33, 0.24, 0.29, 0.33, 0.2...
执行“下次适应”算法后,我们可能会使用三个箱子:
- 第一个箱子装载了尺寸为0.2、0.3和0.25的物品,总装载量为 0.75。
- 第二个箱子装载了尺寸为0.33、0.24和0.29的物品,总装载量为 0.86。
- 第三个箱子目前装载了尺寸为0.33的物品,总装载量为 0.53。
此时,即使我们还没有看到下一个物品,我们也可以预测它能放入第三个箱子。因为它的尺寸至多是箱子容量的三分之一(≤ 1/3),而 0.53 + (≤ 1/3) < 1。因此,下一个物品将能放入第三个箱子。
关键结构观察
这里发生了一些之前没有的情况。我们可以观察到:
如果一个箱子的装载量小于三分之二,那么我们保证能放入下一个物品。
这意味着,在“下次适应”算法中,我们只会在一个箱子的装载量达到至少三分之二时才会关闭它(最后一个箱子除外)。因此,除了最后一个箱子,所有被使用的箱子都至少被填充到了三分之二。
性能分析推导
从这个观察中,我们可以推导出算法的性能上界。
-
算法使用的箱子数与物品总尺寸的关系:
由于每个箱子(最后一个可能除外)都至少装载了三分之二,所有物品的总尺寸至少是(2/3) * (使用的箱子数 - 1)。
用公式表示为:
总尺寸 ≥ (2/3) * (NF - 1)
其中NF代表“下次适应”算法使用的箱子数。 -
最优解的下界:
我们知道,最优解OPT至少需要能容纳所有物品总尺寸的箱子数。由于箱子容量为1,所以:
OPT ≥ 总尺寸 -
结合两者得出近似比:
将上述两个不等式结合:
(2/3) * (NF - 1) ≤ 总尺寸 ≤ OPT
整理后得到:
NF ≤ 1 + (3/2) * OPT
定理总结
我们刚刚证明了一个定理:
当所有物品尺寸都小于箱子容量的三分之一时,“下次适应”算法使用的箱子数至多为 1 + 1.5 * OPT。
这个结果(1.5倍近似)比之前针对一般情况分析的2倍近似要好得多。
分析的启示与扩展
这个分析过程传递了一个方法:从一个特殊例子(小物品)出发,得到一个结构性的观察(箱子至少填充三分之二),然后利用这个观察,结合关于物品总尺寸的上下界,推导出算法的性能保证。
这种方法可以进一步扩展:
- 如果物品尺寸小于四分之一呢?
- 如果小于五分之一呢?
- 更一般地,如果物品尺寸小于
ε(一个很小的正数)呢?
那么,“下次适应”算法将几乎是最优的(误差主要来自最后一个箱子)。这是一个很好的练习。
过渡到下一个案例
至此,我们已经看到,当所有物品都很小时,可以对装箱算法进行更好的分析。接下来,我们将看看另一个特殊案例,可以说是相反的情况:当物品都很大时。在分别对“全小”和“全大”两种极端情况建立直觉之后,我们将能够结合这些见解,最终分析一个能很好处理所有可能输入的算法。

本节课总结
在本节课中,我们一起学习了如何通过分析装箱问题的特殊案例来设计近似算法。我们重点研究了当所有物品尺寸都小于箱子容量三分之一的情况,并证明了在此条件下,“下次适应”算法的近似比可以提升至1.5倍。这个过程展示了从具体例子中提取结构性观察,并将其转化为严格性能证明的分析方法。
近似算法:第1章:大尺寸物品装箱问题 🧳


在本节课中,我们将学习如何处理物品尺寸较大的装箱问题。具体来说,我们将探讨当所有物品的尺寸都大于某个下限值,且物品尺寸的种类有限时,如何设计精确或近似最优的装箱算法。
上一节我们介绍了如何处理小尺寸物品的装箱问题。本节中,我们来看看相反的情况:当所有物品尺寸都较大时,问题依然相当困难。为了找到解决方案,我们先从一个特殊的子问题入手。
以下是解决该问题的核心思路:当物品尺寸较大且种类有限时,每个箱子可能的物品组合方式(我们称之为“配置”)是有限的。我们可以通过枚举这些配置,并利用整数规划或穷举搜索来找到最优的装箱方案。
首先,我们通过一个例子来建立直观理解。
例子分析
考虑一个箱子容量为12的情况。物品尺寸有两种:3和4。假设我们有10个尺寸为3的物品和10个尺寸为4的物品。
如何装箱?尺寸为3的物品,4个恰好装满一个箱子(4×3=12)。尺寸为4的物品,3个恰好装满一个箱子(3×4=12)。因此,我们可以先用2个箱子装尺寸为3的物品(用掉8个),再用3个箱子装尺寸为4的物品(用掉9个)。剩余物品为:2个尺寸为3,1个尺寸为4。它们可以装入第6个箱子(3+3+4=10<12)。总箱子数为6。经过思考,可以确定这是最优解。
从这个例子中,我们得到了什么启示?当物品尺寸较大时,一个箱子能容纳的物品数量有限。因此,所有可能的箱子配置也是有限的。
一般化建模
现在,我们将这个思路推广到一般情况。设物品尺寸集合为S,其中每种尺寸s的物品数量为n_s。每个箱子可以有不同的物品组合,我们称之为配置C。对于每个配置C,定义a_{s,C}为该配置中尺寸为s的物品槽位数量。
我们的目标是选择每种配置的使用数量x_C(整数),使得所有物品都被装入箱子,并且使用的箱子总数最小。
以下是该问题的整数规划模型:
目标函数:最小化总箱子数
[
\min \sum_{C} x_C
]
约束条件:对于每种物品尺寸s,所有配置提供的该尺寸槽位总数必须至少等于该尺寸的物品数量
[
\sum_{C} a_{s,C} \cdot x_C \geq n_s \quad \forall s
]
变量:( x_C \in \mathbb{Z}_{\geq 0} )
求解方法
由于物品尺寸较大(例如,每个尺寸至少占箱子容量的α倍),每个箱子最多能装⌊1/α⌋个物品。同时,物品尺寸的种类数K是一个常数。因此,可能的配置总数最多为K^{⌊1/α⌋},这也是一个常数。
基于此,我们有两种求解策略:
-
近似求解:求解上述整数规划的线性松弛(允许x_C为分数),然后将每个变量向上取整。这会引入最多每个变量+1的误差,总误差不超过配置总数,即一个常数。因此,我们得到一个箱子数不超过OPT + O(1)的近似解。
-
精确求解:由于每种配置的使用数量最多为物品总数n,我们可以穷举所有可能的配置数量组合(最多(n+1)^{(配置总数)} 种可能性)。对于每种组合,检查是否满足所有物品的约束,并选择箱子数最少的方案。由于配置总数是常数,该算法的时间复杂度是n的多项式级别,虽然可能较大,但在理论上是可行的。

本节课中,我们一起学习了如何处理物品尺寸较大且种类有限的装箱问题。我们通过分析例子引入了“配置”的概念,并建立了整数规划模型。我们了解到,由于配置数量有限,该问题可以在常数误差内近似求解,甚至可以通过穷举搜索精确求解。

接下来,我们将尝试放宽“物品尺寸种类有限”这个假设,探索更一般的算法。
020:大型物件多尺寸装箱问题 🧳


在本节课中,我们将学习如何处理物品尺寸较大且尺寸种类繁多时的装箱问题。我们将介绍一种基于自适应舍入的核心技术,它能将复杂问题转化为我们已知如何高效求解的简单情况。
上一节我们解决了物品尺寸较大但种类较少的特殊情况。本节中,我们来看看当物品尺寸仍然较大,但尺寸种类繁多时的情况。这是本章最重要的部分。
核心思路与解决背包问题时类似,即使用舍入技术。我们有许多不同的物品尺寸,但希望将其减少为少数几种尺寸。我们将对尺寸进行向上舍入。这样,如果舍入后的物品能装入一个箱子,那么原始物品也一定能装入,因为它们也满足箱子的容量限制。
以下是舍入方法的一个初步尝试:
- 假设箱子容量为
100。 - 最小物品尺寸至少为
10。 - 将每个物品尺寸向上舍入到最接近的整数。
这样,我们最多只有 90 种可能的物品尺寸,从而可以在多项式时间内求解舍入后的问题,并且其解也是原问题的可行解。这个想法源自上一章的背包算法。
但这种舍入方法的效果如何呢?让我们通过一个例子来分析。
假设箱子容量为 100。我们有 50 个尺寸为 33.333... 的物品和 50 个尺寸为 66.666... 的物品。最优解是将这两种物品配对装箱,恰好装满 50 个箱子,即 OPT = 50。
现在应用整数向上舍入:
33.333...舍入为3466.666...舍入为67
此时,34 + 67 = 101,超过了容量,无法配对。因此,舍入后问题的最优解变为 75 个箱子。虽然每个物品的尺寸只增加了一点点,但整体解的质量却大幅下降。这说明这种简单的舍入方法失败了。
我们需要一种新的舍入方法,即自适应舍入。这是本章乃至本周课程中最重要的概念。
以下是自适应舍入的步骤:
- 排序:首先将所有物品按尺寸从小到大排序。
- 分组:将排序后的物品分成若干组。每组包含固定数量的物品(例如,每组包含
n * ε²个物品,其中n是物品总数,ε是精度参数)。 - 舍入:将同一组内所有物品的尺寸,都向上舍入到该组中最大的物品尺寸。

通过这种方式,舍入后不同尺寸的种类数就等于组的数量。只要组的数量是一个常数,我们就能将问题归约到上一节已解决的“少量尺寸”情况。
现在,我们正式描述针对大型物品的近似方案算法。假设所有物品尺寸都至少是箱子容量 B 的 ε 倍。
算法步骤如下:
- 将物品按尺寸从小到大排序。
- 将物品分组,使得每组恰好包含
n * ε²个物品(最后一组可能不足)。 - 在每一组内,将所有物品尺寸向上舍入到该组的最大尺寸。
- 使用上一节的方法,求解这个舍入后(尺寸种类数为常数)的装箱问题。
- 输出对应的装箱方案。
由于所有物品尺寸都大于 B * ε,且舍入后尺寸种类数为常数,因此该算法可以在多项式时间内运行。
我们接下来将证明,该算法输出结果所使用的箱子数量,最多为 OPT * (1 + O(ε))。这证明了它是一个渐近近似方案。


本节课中,我们一起学习了如何处理物品尺寸较大且种类繁多的装箱问题。我们认识到简单舍入的局限性,并引入了关键的自适应舍入技术。通过先排序、再分组、最后组内统一向上舍入的方法,我们将问题有效简化,并设计出了一个多项式时间的渐近近似方案。在下一节中,我们将对这个算法进行严格的分析,证明其近似比。
021:大型项目分析


在本节课中,我们将学习一个针对特定装箱问题的近似算法,并分析其性能。我们将证明,当所有物品尺寸都大于某个阈值时,该算法能找到接近最优解的方案。
上一节我们介绍了当所有物品尺寸都大于 ε * B(其中 B 是箱子容量)时,所采用的算法框架。本节中,我们来详细分析这个算法的性能,并证明其输出值接近最优值。
算法回顾
首先,我们简要回顾算法步骤:
- 将物品按尺寸分组,每组包含
n * ε²个物品。 - 将每组内所有物品的尺寸向上取整(round up)为该组中的最大尺寸。
- 对取整后的问题(记为
U)求最优解。 - 输出对应于原始物品的装箱方案。
算法的输出值,即我们使用最优算法求解取整后问题 U 得到的值,记为 OPT(U)。我们的目标是证明 OPT(U) 与原始问题的最优值 OPT(I) 非常接近。
核心分析思路
分析的关键在于构造另一个取整方案,并与算法中使用的向上取整方案进行比较。
以下是分析中最重要的部分。考虑输入物品 I。我们已通过向上取整得到问题 U。现在,我们定义一种向下取整方案 D:
- 对于每一组物品,将其尺寸向下取整(round down)为前一组的最大尺寸。
- 第一组物品则向下取整为尺寸
0。
因此,D 看起来像这样:每组有 n * ε² 个相同尺寸的物品,第一组尺寸为 0。
这之所以有帮助,是因为装箱问题是单调的:增大物品尺寸只会使最优值 OPT 增加或不变;减小物品尺寸只会使 OPT 减少或不变。
于是,我们得到以下关系:
- 由于
D是将I的尺寸减小得到的,因此OPT(D) ≤ OPT(I)。 - 由于
U是将I的尺寸增大得到的,因此OPT(I) ≤ OPT(U)。
这样,我们就把 OPT(I) “夹”在了 OPT(D) 和 OPT(U) 之间:
OPT(D) ≤ OPT(I) ≤ OPT(U)
比较 U 与 D
为什么这很好?现在让我们忽略原始输入 I,只观察两个取整后的问题 U 和 D。
观察 U 和 D,它们几乎相同:
- 从第二组开始,
U和D都包含相同数量(n * ε²)的、相同尺寸的物品。 - 唯一的区别在于:
D包含第一组尺寸为0的物品,而U没有。这无关紧要,因为尺寸为0的物品不影响OPT值。U包含第一组尺寸较大的物品(尺寸在εB到B之间),而D没有。这组物品的数量最多为n * ε²。
基于此,我们可以得出:任何针对 D 的装箱方案,都可以通过为 U 中那最多 n * ε² 个“特殊”大物品每个单独分配一个新箱子,从而转换成一个针对 U 的装箱方案。
因此,U 和 D 的最优值之差最多为 n * ε²:
OPT(U) - OPT(D) ≤ n * ε²
推导近似保证
结合前面的不等式,我们可以进行推导:
- 我们有
OPT(D) ≤ OPT(I) ≤ OPT(U)。 - 我们有
OPT(U) ≤ OPT(D) + n * ε²。
由此可得:
OPT(U) ≤ OPT(I) + n * ε²
这意味着,我们算法求得的取整后问题的最优值 OPT(U),最多只比原始问题的最优值 OPT(I) 大一个加性误差 n * ε²。
将加性误差转换为相对误差
分析还剩最后一步:我们需要将加性误差 n * ε² 与 OPT(I) 联系起来,转化为相对误差。
由于我们假设所有物品尺寸至少为 εB,且箱子容量为 B,因此每个箱子最多只能装下 1/ε 个物品。更准确地说,每个箱子最多装 floor(1/ε) 个物品,但为简化分析,一个直接推论是:装下所有 n 个物品至少需要 εn 个箱子。
因此,原始问题的最优值满足:
OPT(I) ≥ εn
那么,加性误差项可以表示为:
n * ε² = ε * (εn) ≤ ε * OPT(I)
最终结论
将以上所有步骤结合起来,我们可以得出最终的结论:
当所有物品尺寸都至少为 εB 时,我们的算法能在多项式时间内,给出一个装箱方案,其使用的箱子数量最多为 (1 + ε) * OPT(I)。

本节课中,我们一起学习了如何分析针对“大物品”装箱问题的近似算法。我们通过构造向上和向下两种取整方案,将原问题最优值“夹”在中间,并比较两个取整后问题的差异,最终证明了算法具有 (1 + ε) 的近似比。在下一节中,我们将完成这个算法系列的开发,得到一个适用于一般情况(包含任意小物品)的完整近似算法。
022:通用装箱算法


在本节课中,我们将学习如何设计一个适用于任意物品大小的通用装箱算法。我们将通过将问题归约到之前已解决的“大物品”情况,并巧妙地处理小物品,最终得到一个渐近近似方案。
上一节我们解决了所有物品尺寸都大于 εB(即“大物品”)时的装箱问题。本节中,我们来看看如何处理物品尺寸任意的通用情况。
我们的策略是将通用情况归约到之前已解决的大物品情况。以下是具体做法。
首先,我们需要区分物品的大小。以下是算法的第一步:
- 分离小物品:将所有尺寸小于
εB的物品定义为“小物品”,并将其暂时搁置。剩余物品则全部是“大物品”。
现在,我们面对的是一个仅包含大物品的输入。这正是我们上一节已解决的问题。因此,算法的第二步是:
- 打包大物品:对剩余的大物品应用上一节的算法。该算法通过尺寸取整,将问题转化为仅有少数几种尺寸的大物品装箱问题,并高效求解。
这个过程就像俄罗斯套娃:我们先解决一个最特殊的子问题(尺寸取整后的大物品),然后用它作为黑盒来解决更一般的子问题(所有大物品),最后再用这个结果来解决最通用的问题。
解决了大物品的打包问题后,接下来需要处理之前搁置的小物品。以下是处理小物品的方法:
- 贪心填入小物品:查看上一步得到的大物品装箱方案。我们尝试将小物品贪心地填入这些已有箱子中剩余的空间里。如果某个小物品无法放入任何现有箱子,则为其开启一个新箱子。
这个思路借鉴了我们在学习“所有物品都很小”的情况时获得的直觉。
以上就是完整的算法描述。现在,让我们分析这个算法的近似性能。分析将分为两种情况。
情况一:在填入小物品时,没有开启任何新箱子。
在这种情况下,算法的最终输出值(所用箱子数)就等于打包大物品所用的箱子数。根据上一节的结论,这个值最多为 (1 + ε) * OPT(L),其中 OPT(L) 是仅打包大物品 L 所需的最优箱子数。由于 L 是整个输入 I 的子集,显然 OPT(L) ≤ OPT(I)。因此,在此情况下,算法输出值 ≤ (1 + ε) * OPT。
情况二:在填入小物品时,开启了一些新箱子。
此时,除了最后一个被开启的箱子,其他所有箱子都被填充到了很高的程度。具体来说,因为我们是贪心填入,只有当一个物品无法放入任何现有箱子时才会开新箱,所以每个现有箱子的剩余空间都小于 εB。这意味着每个现有箱子的填充量至少为 (1 - ε)B。
由此我们可以推导出,所有物品的总体积至少是 (箱子总数 - 1) * (1 - ε)B。另一方面,最优解 OPT 必须能装下所有物品,因此总体积 ≤ OPT * B。将两者结合,经过代数变换(注意到 1/(1 - ε) = 1 + O(ε)),我们可以得出算法输出值 ≤ OPT * (1 + O(ε)) + 1。
综合以上两种情况,我们得出结论:该算法能在多项式时间内,给出一个装箱方案,其使用的箱子数最多为 OPT * (1 + O(ε)) + 1。

本节课中,我们一起学习了如何为通用装箱问题设计一个渐近近似方案。我们通过将问题归约到已解决的大物品情况,并利用贪心策略处理小物品,最终证明了算法的近似性能。这标志着我们对装箱问题近似算法的探索取得了重要进展。
023:装箱问题课程结论


在本节课中,我们将总结在装箱问题研究中学习到的核心技术与算法思想,并回顾该领域的重要发展历程。
课程概述
我们通过研究装箱问题,学习了两项重要的算法设计技术:自适应舍入与俄罗斯套娃技术。此外,我们也了解了该问题的复杂性背景与研究脉络。
核心技术
上一节我们介绍了处理不同规模物品的算法,本节我们来总结其中用到的核心思想。
以下是我们在装箱问题工作中学习到的两项关键技术:
-
自适应输入舍入
这是一种新颖的技术,并非像背包问题中那样舍入到最接近的整数或分数,而是一种依赖于输入其他部分的舍入方式。这项技术对于简化输入非常有用,常见于输入为一组数值的许多问题中。 -
俄罗斯套娃技术
这是一种逐层简化的技术。最内层的“套娃”解决了物品体积大且尺寸种类少的特殊情况。其外一层是处理所有物品体积都大(但尺寸种类可以很多)情况的算法。最外层的“套娃”则处理对物品体积和种类没有任何限制的一般情况。如果想使用更专业的术语,这也可以称为连续归约技术。这项技术在算法设计中通常非常有用。
问题变体与研究背景
装箱问题只是组合优化与运筹学中许多实例的一个简单案例。它存在多种变体:例如当输入不是数字而是正方形或矩形的二维变体;当每个输入物品由三个数字(而非一个)描述的三维变体;以及物品以在线方式随时间到达,必须在看到全部物品之前决定其放置位置并关闭容器的在线变体。过去人们对许多版本的装箱问题进行了研究。
重要贡献者
接下来,我们回顾一下在装箱问题研究中做出严格证明贡献的研究者们。
以下是该领域的几位先驱:
- Stephen Cook:以其1972年关于NP完全问题的论文而闻名。他证明了装箱问题在一般情况下是困难的(NP完全)。
- David S. Johnson 与 Micha Garey:他们是首批为装箱问题设计并证明近似算法性能界的研究者。这实际上是David Johnson博士论文的一部分,并且他职业生涯中从未停止对装箱问题的兴趣。
在问题被证明是NP完全且出现了常数因子近似算法之后,研究在20世纪80年代转向了平均情况分析。早期工作由 Boris Pittel 和 Joseph Kruskal 等人完成,随后许多数学家和计算机科学家都参与了装箱问题平均情况分析及特定算法的平均性能研究,其中 David S. Johnson 是突出代表,Lisa McGough 也在此方向有所贡献。
渐近近似方案
本周我们还学到了另一点:如果我们无法找到一个近似方案(因为区分两个箱子和三个箱子是NP完全的),那么我们可以改变定义,专注于渐近情况。
以下是该方向的关键进展:
- W. Fernandez de la Vega 与 George S. Lueker 率先在此方向取得突破。他们发明了自适应舍入法,并用以设计了我们在课程中看到并分析的算法。
- 此后不久,Nicolas Karmarkar 与 Richard M. Karp 采用了相同的思路,但使用了更好、更复杂的分析,并巧妙地运用了线性规划。他们成功设计出一个算法,其所需箱子数最多为 OPT + O(log² OPT)。当OPT趋向无穷大时,log² OPT远小于OPT,因此这几乎是最优的,是对装箱问题更精细的审视方式。
- 随后该领域沉寂了约30年,直到2015年,Rebecca Hoberg 与 Thomas Rothvoss 将 log² OPT 的余项改进到了 log OPT,这是一个更优秀的渐近近似方案。

我们才刚刚开始学习近似算法,但已经触及了当前研究的前沿。如果我们能学习更多关于线性规划、差异理论以及巧妙舍入的知识,就能阅读这些论文并理解装箱问题的当前研究了。
总结
本节课中,我们一起学习了解决装箱问题的两项核心技术——自适应舍入与俄罗斯套娃(连续归约)技术,回顾了该问题的各种变体,并梳理了从NP完全性证明、常数因子近似、平均情况分析到渐近近似方案的研究历程。现在,你可能已经对这个问题、对装箱、对处理数字感到足够了,在下一章中,我们将重新开始研究图论问题。
024:集合覆盖问题


在本节课中,我们将学习集合覆盖问题。这是一个经典的组合优化问题,也是顶点覆盖问题的推广。我们将探索一种新的近似算法设计技术:使用线性规划的解,并通过随机舍入进行处理。这将是本课程中第一个随机化算法。
输入与输出
集合覆盖问题的输入包含两部分:
- 一个由元素构成的“全集”,例如图中的1到6号元素。
- 一系列该全集的子集,每个子集都有一个成本。例如,蓝色子集包含了橙色和绿色元素。
这对应着现实中的许多场景。例如,你需要购买一系列烹饪食材,不同的商店(子集)以不同的价格(成本)提供不同的食材组合(元素)。你的目标是选择一些子集(商店),使得所有需要的元素(食材)都被至少一个选中的子集覆盖,同时最小化所选子集的总成本。
输出是一个选中的子集集合。例如,图中三个被轻微着色的子集,其成本分别为4、2和2,总成本为8,并且可以验证每个元素都被至少一个选中的子集覆盖。
与顶点覆盖的关系
集合覆盖问题听起来是否似曾相识?是的,它与我们之前学过的顶点覆盖问题密切相关。实际上,顶点覆盖是集合覆盖的一个特例。
在顶点覆盖问题中,我们需要用最少的顶点覆盖所有的边。我们可以将其重新表述为一个集合覆盖问题:
- 元素:图中的每条边。
- 子集:每个顶点对应一个子集,该子集包含所有与该顶点相连的边。
- 成本:如果目标是使用最少的顶点,则每个子集的成本为1。
因此,集合覆盖问题比顶点覆盖问题更一般化,也至少同样困难。既然我们曾用线性规划松弛和舍入的方法近似解决顶点覆盖,这启发我们对集合覆盖问题采用类似的方法。
整数规划与线性规划松弛
首先,我们为集合覆盖问题建立一个整数规划模型。与顶点覆盖类似,我们为每个输入的子集 S 引入一个变量 x_S。
- 变量:
x_S ∈ {0, 1}。x_S = 1表示子集S被选中加入覆盖,x_S = 0则表示未被选中。 - 约束:对于全集中的每个元素
e,必须被至少一个选中的子集覆盖。即,所有包含元素e的子集S,其对应的变量x_S之和至少为1。公式表示为:∑_{S: e ∈ S} x_S ≥ 1。 - 目标:最小化总成本。设每个子集
S的成本为c_S,则目标函数为:Minimize ∑_S c_S * x_S。
这个整数规划精确地描述了集合覆盖问题。接下来,我们将其松弛为线性规划。
- 线性规划松弛:将整数约束
x_S ∈ {0, 1}放松为x_S ∈ [0, 1],允许变量取0到1之间的任意实数值。其他约束和目标函数保持不变。
从顶点覆盖舍入法的失败尝试
在顶点覆盖问题中,我们采用的舍入策略很简单:对于线性规划的解 x_u,如果 x_u ≥ 0.5,则将其舍入为1(选中该顶点),否则舍入为0。
我们能否将这一策略直接套用到集合覆盖上呢?即,对于每个子集 S,如果线性规划解 x_S ≥ 0.5,则选中它。遗憾的是,这个策略会失败,它可能无法产生一个有效的集合覆盖。
考虑一个例子:假设有三个子集,每个在线性规划解中的值 x_S = 1/3。某个元素恰好只属于这三个子集。那么,该元素被覆盖的“分数”总和为 1/3 + 1/3 + 1/3 = 1,满足约束。但按照 ≥ 0.5 的阈值舍入,这三个 x_S 都会被舍入为0,导致该元素完全未被覆盖。
如果我们降低阈值,比如设为 1/3,那么在这个例子中所有子集都会被选中。但我们可以构造另一个例子,让所有 x_S = 1/4,此时 1/3 的阈值又会导致所有子集被舍弃。对于任何一个固定的阈值,我们总能构造出一个反例,使得舍入后的解无法覆盖所有元素。
因此,我们需要一种更智能的、非固定阈值的舍入方法。
引入随机化舍入
既然固定阈值的方法行不通,我们将采用随机化舍入技术。这种方法的核心思想是:根据线性规划解 x_S 的值,以一定的概率随机决定是否选中子集 S。具体来说,我们以概率 x_S 独立地将每个子集 S 加入覆盖。
这个想法非常直观:x_S 可以解释为“子集 S 应该被选中的倾向性”。通过随机化,我们期望在多次实验中,每个元素被覆盖的概率与其在线性规划中被满足的程度相关。

在下一节中,我们将详细分析这种随机化舍入算法的性能,并证明它能以高概率得到一个总成本可控且覆盖所有元素的解。
本节课中,我们一起学习了集合覆盖问题的定义,理解了它如何推广顶点覆盖问题。我们建立了该问题的整数规划模型及其线性规划松弛,并指出了直接沿用顶点覆盖的确定性舍入法会失败。最后,我们引入了随机化舍入作为解决方案,这将是我们在近似算法工具箱中的又一个强大工具。
近似算法课程:第1章:集合覆盖问题与随机舍入算法


在本节课中,我们将学习集合覆盖问题,并探讨一种基于线性规划松弛和随机舍入思想的近似算法。我们将从问题定义开始,回顾之前尝试的失败,然后详细介绍随机舍入算法的步骤,并分析其效率、正确性和近似比。
我们定义了集合覆盖问题。我们认识到它是顶点覆盖问题的一个推广。
我们曾尝试将用于顶点覆盖的算法进行扩展,但未能成功。
现在,我们需要再次尝试,以得到一个求解集合覆盖的算法。
以下是我们之前使用的线性规划松弛模型。这个模型我们将保留。核心问题是:如何对线性规划的解进行舍入?
现在,我们将介绍一种使用随机舍入的算法。
考虑一个取值在0到1之间的变量,例如0.9。当我们想将其舍入为整数时,0.9很可能应该被舍入为1。另一方面,如果它的值是0.1,那么很可能应该被舍入为0。但正如我们所见,我们不能简单地设定一个固定阈值,规定高于阈值的都变为1,低于阈值的都变为0。
因此,我们需要更巧妙的方法。这里的思路是使用随机化,即利用概率。
具体如何操作?假设一个变量的值为0.8。我们不会总是将其舍入为1,而是以一定的概率进行。我们抛掷一些“硬币”。然后,我们以80%的概率(即0.8的概率)将该变量舍入为1。这就是我们如何利用分数值,将其转化为整数值的方法。
新的舍入算法非常简单。对于每一个集合S,我们查看线性规划解中对应的变量 ( x_S )。以概率 ( x_S ),我们将集合S放入集合覆盖中。以概率 ( 1 - x_S ),我们不将S放入覆盖中。当我们对每个集合S都执行此操作后,就做出了一系列决策,然后输出最终得到的集合族。
这是一个非常简单的舍入算法。对于这类算法,我们通常需要回答三个问题。
以下是需要分析的三个核心问题:
- 效率如何? 算法是否高效?
- 是否正确? 算法的输出是否是一个有效的集合覆盖?
- 质量如何? 输出覆盖的成本是多少?
第一个问题很容易回答。算法是否高效?是的。为什么?因为所需时间主要是求解线性规划的时间,我们知道这是多项式时间的。此外,对于每个变量,只需进行一次随机选择(在0到1之间生成一个随机数并做一次决策)。因此,总体上是多项式时间,前提是我们能够获取随机数,这在实践中是成立的。
第二个问题是关于输出的正确性。输出可能不是一个覆盖。因为我们对每个集合都做了随机选择,有可能我们决定不将任何集合放入覆盖中,导致输出为空,元素未被覆盖。所以,它可能是一个覆盖,也可能不是。
第三个问题是关于输出的质量。这也不确定。我们可能选择了一些昂贵的集合,也可能没有。
因此,对于这个算法,我们需要分析两件事:输出结果是一个覆盖的概率,以及输出集合族的成本。

总结
本节课中,我们一起学习了集合覆盖问题,并介绍了一种基于线性规划松弛的随机舍入算法。我们明确了算法的三个关键分析维度:效率、正确性和近似质量。算法的效率很高,但其正确性(是否能产生一个覆盖)和成本并非确定性的,需要通过概率分析来评估其期望性能。在接下来的课程中,我们将深入进行这种概率分析。
近似算法:第1章:随机舍入的成本分析


在本节中,我们将分析上一节介绍的随机舍入过程所产生的解的成本。我们将看到,该解在期望值上的成本与线性规划松弛的最优值直接相关。
现在,我们有了一个随机舍入程序,可以将线性规划松弛的解转化为整数解。接下来,让我们分析这个解的成本,并将其与线性规划的最优值联系起来。
输出的集合族质量如何?这取决于具体情况。首先,我们写出输出解的成本表达式。
输出解的成本是输出集合族中每个集合S的成本之和。这是一个随机变量,因为集合S是否在输出中取决于随机选择。因此,这个值有时大,有时小。对于随机算法,我们通常关心这个值的平均表现或典型表现。
为此,我们将输出解的成本重写为:对所有集合S,计算其成本 C_S 乘以一个指示函数。该指示函数在集合S被选入输出覆盖时取值为1,否则为0。
我们可以将输出解的成本表示为:
Cost = Σ_S (C_S * I_S),其中 I_S 是指示函数。
我们关心这个随机变量的期望值。这里我们使用期望的线性性质,这是算法分析中一个非常有用的工具。对于任意随机变量A和B,有 E[A + B] = E[A] + E[B]。期望与求和运算可以交换顺序。
因此,输出解的期望成本为:
E[Cost] = E[ Σ_S (C_S * I_S) ] = Σ_S E[ C_S * I_S ]。
接下来,我们关注 E[ C_S * I_S ]。根据期望的性质,对于任意常数λ和随机变量X,有 E[λX] = λ * E[X]。这里 C_S 是常数,因此可以提到期望外面:
E[ C_S * I_S ] = C_S * E[ I_S ]。
现在,我们需要计算 E[ I_S ],即指示函数的期望值。指示函数的期望值恰好等于该事件发生的概率。这是一个基本的概率论事实。
因此,E[ I_S ] 等于集合S被选入输出集合族的概率。根据我们的舍入规则,我们恰好以概率 x_S 将集合S包含在输出中。
综合以上推理,我们得出结论:输出集合族的期望成本等于:
E[Cost] = Σ_S (C_S * x_S)。
观察这个表达式。你认出来了吗?这个量恰好是线性规划松弛的目标函数值。我们知道,由于这是一个松弛,该值至多是原始问题最优值 OPT。
因此,我们的舍入过程产生的集合族,其期望成本至多为未知的最优值 OPT。这正是我们选择这种特定舍入方式的原因——我们决定恰好以概率 x_S 将变量舍入为1,就是为了得到这个目标函数关系。

现在我们知道,我们的舍入程序产生的集合族,其期望成本至多为 OPT。接下来,我们需要考察这个集合族能提供多少覆盖。
近似算法:第一讲:集合覆盖问题分析


在本节课中,我们将学习如何分析一个用于解决集合覆盖问题的随机算法。我们将看到,该算法能以不超过最优解(OPT)的期望成本输出一个集合族,但我们需要验证这个输出是否确实覆盖了所有元素。
上一节我们介绍了算法的成本分析,本节中我们来看看算法的正确性分析,即它是否总能输出一个有效的集合覆盖。
算法正确性分析
算法的输出是否构成一个集合覆盖?答案取决于我们的随机选择,可能“是”,也可能“不是”。因此,我们需要更仔细地分析这个性质。
具体来说,我们来分析被选中并放入输出结果的集合所覆盖的元素数量。这个数量可以表示为每个元素的指示函数之和:
覆盖的元素数量 = Σ_e I(e被覆盖)
其中,指示函数 I(e被覆盖) 在元素 e 被覆盖时为1,否则为0。这是一个随机变量,我们将分析它的期望值。
计算这个和的期望值。当我们看到和的期望时,可以交换求和与期望的顺序。指示函数的期望值等于该事件发生的概率。因此,我们得到:
E[覆盖的元素数量] = Σ_e Pr(e被覆盖)
现在,为了分析这个表达式,我们聚焦于一个特定的元素 e。元素 e 被覆盖的概率是多少?这是本章技术性较强的部分。
计算元素被覆盖的概率
元素 e 被覆盖,当且仅当输出集合族中存在一个包含 e 的集合 S。因此:
Pr(e被覆盖) = Pr(∃ S ∈ 输出集合族,使得 e ∈ S)
计算“存在”某事件的概率,常用技巧是使用其对立事件:
Pr(e被覆盖) = 1 - Pr(所有包含e的集合S都未被选入输出集合族)
现在我们需要分析这个概率。以下是关键步骤:
- 利用独立性:算法为每个集合
S独立地以概率x_S将其放入输出。因此,所有包含e的集合都未被选中的概率,等于每个此类集合未被选中的概率的乘积。 - 计算单个概率:集合
S未被选中的概率是1 - x_S。
综合以上两点,我们得到:
Pr(e被覆盖) = 1 - Π_{S: e∈S} (1 - x_S)
简化概率表达式
接下来,我们对乘积项 Π (1 - x_S) 进行简化处理。
首先,利用对数的性质,将乘积转化为求和:
Π_{S: e∈S} (1 - x_S) = exp( Σ_{S: e∈S} ln(1 - x_S) )
其次,利用不等式 ln(1 - x) ≤ -x(当 x ∈ [0,1) 时成立),我们可以得到:
Π_{S: e∈S} (1 - x_S) ≤ exp( - Σ_{S: e∈S} x_S )
现在,观察指数中的求和项 Σ_{S: e∈S} x_S。我们在线性规划(LP)的约束条件中见过它:对于每个元素 e,所有包含 e 的集合的 x_S 值之和至少为1。即:
Σ_{S: e∈S} x_S ≥ 1
将这个不等式代入,我们得到:
Π_{S: e∈S} (1 - x_S) ≤ exp(-1) = 1/e
因此,元素 e 被覆盖的概率至少为:
Pr(e被覆盖) ≥ 1 - 1/e
结论与当前状态
将这个下界代入期望覆盖元素数量的公式中,我们得到:
E[覆盖的元素数量] ≥ n * (1 - 1/e)
其中 n 是元素总数。1 - 1/e 约等于 63.2%。
至此,我们证明了:通过随机舍入,我们得到了一个集合族,其期望成本不超过 OPT,并且平均至少覆盖了 63.2% 的元素。
然而,这还不是一个完整的集合覆盖。我们尚未保证覆盖所有元素,但已经非常接近了。接下来,只需要对算法进行一些修改,就能将其转变为一个完全正确的集合覆盖算法。

本节课中我们一起学习了如何分析随机舍入算法在集合覆盖问题上的覆盖能力。我们证明了算法能以低成本覆盖大部分元素,为最终构建完整解奠定了基础。
近似算法:第1章:集合覆盖问题的迭代随机舍入算法


在本节课中,我们将学习如何利用迭代随机舍入技术,为集合覆盖问题构建一个近似算法。我们将从一个成本最优但覆盖不完全的初始方案出发,通过迭代改进,最终得到一个高概率正确且成本有界的近似解。
上一节我们介绍了集合覆盖问题的线性规划松弛及其随机舍入方法,该方法在成本上表现优异,但在覆盖完整性上存在不足。本节中,我们来看看如何通过迭代来改进覆盖效果。
初始的随机舍入算法在成本方面表现很好,其期望成本至多为最优解(OPT)。但在覆盖方面表现不佳,它平均只能覆盖大约63%的元素。因此,我们需要改进算法以获得一个完整的集合覆盖。
解决方案的核心思想很简单:迭代。我们重复选择集合的过程,直到获得一个实际的集合覆盖。
以下是针对集合覆盖问题的随机舍入算法。设 N 表示集合覆盖中元素的总数。
我们将重复以下操作 log N + 3 次:
- 对于每个集合 S,我们以概率 x_S 将其加入我们的覆盖中(如果它尚未被加入)。
选择 log N + 3 次是因为 e^3 约等于20,这个数字在后续分析中会用到。
让我们分析这个迭代过程意味着什么。
成本分析
在期望值上,总成本至多是 (log N + 3) * OPT。这非常好,如果算法能产生一个集合覆盖,那么它就给出了一个 log N 倍的近似解。
正确性分析
算法输出一个完整集合覆盖的概率是多少?这等于1减去输出“不是覆盖”的概率。
输出“不是覆盖”的概率,即存在至少一个元素未被覆盖的概率。根据概率的并集界限(Union Bound),这个概率至多是所有元素“未被覆盖”的概率之和。
那么,单个元素 E 未被覆盖的概率是多少?
首先,看一次循环迭代。根据之前的分析,在单次迭代中,元素 E 未被覆盖的概率至多是 1/e(约0.367)。
当我们把所有的迭代放在一起考虑时,各次迭代之间是独立的。因此,元素 E 在所有迭代中均未被覆盖的概率,就是每次迭代中它未被覆盖的概率的乘积。
由于每次迭代的概率至多为 1/e,并且有 log N + 3 次迭代,所以这个乘积至多是 (1/e)^(log N + 3)。
这个值等于 1/(e^3 * N),大约为 1/(20N)。
综合以上分析,所有元素未被覆盖的概率总和至多为 N * (1/(e^3 * N)),即小于 0.05。
因此,算法输出一个正确集合覆盖的概率至少为 95%。

综上所述,通过进行迭代随机舍入,我们得到了一组集合,这组集合有至少 95% 的概率构成一个完整的集合覆盖,并且其期望成本至多为 (log N + 3) * OPT。

我们几乎已经成功了。我们得到了一个本质上是集合覆盖的随机算法,它在 95% 的情况下都能输出一个集合覆盖。在下一部分,我们将看到如何将这个算法转化为一个总是返回集合覆盖的算法。
029:停止时间算法 🛑⏱️


在本节课中,我们将学习如何改进集合覆盖问题的算法,使其总能输出一个有效的覆盖,并分析其近似成本。
概述
上一节我们介绍并分析了一个用于解决集合覆盖问题的迭代随机舍入算法。该算法能以高概率输出一个覆盖,且平均成本约为 O(log n) * OPT。然而,它有两个主要弱点:1)不能保证总是输出一个有效的集合覆盖;2)成本不能严格保证在 log n + 3 * OPT 以内。本节中,我们将针对第一个弱点,设计一个总能输出有效集合覆盖的改进算法。
改进算法:从“固定次数”到“直到覆盖”
原算法的核心是重复一个随机选择过程固定的次数(例如 log n + 3 次)。为了确保输出总是有效的集合覆盖,一个直观的改进是:将“重复固定次数”的条件,改为“重复直到获得一个集合覆盖”。
以下是改进后的算法步骤:
- 初始化一个空的覆盖集合
C = ∅。 - 重复以下过程,直到
C成为一个集合覆盖(即覆盖所有元素):- 根据线性规划松弛解给出的分数变量 x_S,按概率选择集合 S。具体来说,选择集合 S 的概率为 x_S / Σ x_S'。
- 如果 S 尚未在覆盖集合
C中,则将其加入C。
这个算法可以称为 “采样迭代算法”。它总是会停止,并输出一个有效的集合覆盖。
算法变体:串行化实现
上述描述是并行考虑所有集合。为了便于分析,我们可以考虑一个等价的、更清晰的串行化版本:
算法:停止时间算法
初始化 C = ∅
当 C 不是集合覆盖时:
根据概率分布 P(S) = x_S / (Σ x_S') 随机选择一个集合 S
如果 S 不在 C 中,则将 S 加入 C
返回 C
这个算法本质上与原迭代随机舍入算法相同,但将停止条件从固定轮次改为了达成覆盖目标。这样,算法的正确性得到了保证。
过渡到成本分析
现在,我们得到了一个总能输出正确集合覆盖的算法。接下来,我们需要分析这个输出覆盖的期望成本是多少。这将是下一节的重点内容。
总结


本节课中,我们一起学习了如何通过修改停止条件来改进集合覆盖算法,使其总能输出有效的解。我们将原算法的“固定迭代次数”改为了“迭代直到覆盖”,从而得到了停止时间算法。在下一节,我们将对这个新算法的近似比(即期望成本)进行详细分析。
030:停时分析教程


在本节课中,我们将学习如何分析“采样与迭代”算法,这是一种用于解决集合覆盖问题的近似算法。我们将重点关注一个关键的分析技巧——停时分析,并最终证明该算法的期望成本不超过最优解的 (1 + log n) 倍。
算法回顾
上一节我们介绍了用于集合覆盖问题的“采样与迭代”算法。本节中我们来看看如何分析它。
该算法重复执行以下步骤:以与 X_S 成比例的概率随机选取一个集合 S,将其加入覆盖集,直到所选集合构成一个完整的集合覆盖为止。这个过程何时停止是随机的,我们称这个停止时刻为 T。
成本表达式
我们首先需要分析输出结果的总成本。总成本是每一步所选集合成本的总和。
设 C_t 为第 t 次迭代所选集合的成本。那么输出成本为:
总成本 = Σ_{t=1}^{T} C_t
其中 T 是算法停止的总迭代次数,它是一个随机变量(停时)。
我们的目标是分析这个随机总成本的期望值 E[Σ_{t=1}^{T} C_t]。
分析中的挑战与技巧
直接交换期望与求和符号会遇到问题,因为求和上限 T 本身是随机的。我们不能简单地写成 Σ_{t=1}^{∞} E[C_t]。
此时,我们需要借助概率论中的一个技巧——沃尔德方程(Wald‘s Equation)。该方程指出,对于一个停时 T,如果随机变量 X_t 的期望值 E[X_t] 一致有上界 μ,且 E[T] 有限,则有:
E[Σ_{t=1}^{T} X_t] ≤ μ * E[T]
这允许我们在一定条件下“交换”期望与求和。
在我们的场景中,X_t 就是 C_t。因此,下一步我们需要找出 C_t 期望值的上界。
单步成本的期望值
第 t 步所选集合成本的期望值为:
E[C_t] = Σ_{S} Pr[S 在第 t 步被选中] * C_S
根据算法,集合 S 在第 t 步被选中的概率为 X_S / (Σ_{所有集合} X_S)。代入上式得:
E[C_t] = (Σ_{S} X_S * C_S) / (Σ_{S} X_S)
注意,这个值与具体的迭代步数 t 无关。我们记这个值为 μ。
于是,应用沃尔德方程,我们得到输出总成本的期望值满足:
E[总成本] ≤ μ * E[T]
至此,问题转化为分析期望迭代次数 E[T]。
分析期望迭代次数
为了分析 E[T],我们引入新的记号。设 N_t 为经过 t 次迭代后,尚未被覆盖的元素数量。
我们知道:
N_0 = n(初始所有n个元素都未被覆盖)。N_T = 0(算法停止时所有元素已被覆盖)。- 在停止前一步(
T-1),至少有一个元素未被覆盖,即N_{T-1} ≥ 1。
关键在于分析 N_t 在每一步的期望减少量。具体来说,我们关心在已知 N_t 的条件下,N_t - N_{t+1} 的期望值,即下一步能覆盖的新元素数量的期望值。
考虑一个尚未被覆盖的特定元素 e。它在下一步(第 t+1 步)被新选中的集合覆盖的概率是多少?这个概率等于所有包含 e 的集合 S 在第 t+1 步被选中的概率之和,即:
Pr[e 在 t+1 步被覆盖] = Σ_{S: e ∈ S} (X_S / (Σ_{所有集合} X_S))
根据线性规划松弛的约束条件,对于每个元素 e,有 Σ_{S: e ∈ S} X_S ≥ 1。因此:
Pr[e 在 t+1 步被覆盖] ≥ 1 / (Σ_{所有集合} X_S)
由于有 N_t 个元素未被覆盖,且每个元素被覆盖的事件可能重叠,但期望具有可加性。因此,在给定 N_t 的条件下,下一步覆盖的新元素数量的期望值满足:
E[N_t - N_{t+1} | N_t] ≥ N_t / (Σ_{所有集合} X_S)
我们记 f(z) = (Σ_{所有集合} X_S) / z,那么期望减少量的下界就是 N_t / (Σ X_S) = 1/f(N_t)。
应用停时定理
对于这种期望减少量有下界的随机递减过程,有一个定理(类似于“漂移分析”)可以给出停时期望的上界。应用该定理,我们得到期望迭代次数的上界为:
E[T] ≤ 1 + ∫_{z=1}^{n} f(z) dz
= 1 + (Σ_{所有集合} X_S) * ∫_{z=1}^{n} (1/z) dz
= 1 + (Σ_{所有集合} X_S) * log n
最终结论
现在,我们将所有结果结合起来。输出总成本的期望值满足:
E[总成本] ≤ μ * E[T]
≤ [ (Σ_{S} C_S * X_S) / (Σ_{S} X_S) ] * [ 1 + (Σ_{S} X_S) * log n ]
= (Σ_{S} C_S * X_S) * (1/(Σ X_S) + log n)
注意,Σ_{S} C_S * X_S 正是原始线性规划的目标函数值,它不超过整数最优解的值 OPT。而 1/(Σ X_S) 是一个很小的数。因此,最终我们得到:
E[总成本] ≤ (1 + log n) * OPT
证明完成:我们证明了“采样与迭代”算法输出的集合覆盖,其期望成本最多是最优解的 (1 + log n) 倍。
总结


本节课中我们一起学习了如何分析“采样与迭代”这一随机近似算法。我们遇到了求和上限随机带来的分析挑战,并引入了沃尔德方程和停时分析技巧来克服它。通过分析单步成本期望和未覆盖元素数量的期望减少量,我们最终证明了该算法具有 (1 + log n) 的近似比。这几乎是解决集合覆盖问题最优的近似算法之一。在后续内容中,我们将讨论如何将这个“期望”保证加强为“高概率”保证,并探讨该问题近似比的极限。
031:集合覆盖问题总结与展望 🎯


在本节课中,我们将总结集合覆盖问题的核心内容,并展望更优的算法及其理论极限。我们将介绍贪心算法,探讨其优势,并回顾该领域的重要贡献者。
关于集合覆盖的其他说明
关于集合覆盖问题,还有许多其他方面可以讨论。我们以几点说明作为结尾。
我们已证明的结果是,“采样与迭代”算法输出的集合覆盖,其平均成本至多是 log n + 1 乘以最优解的成本 OPT。
能否做得更好? 🚀
我们能否做得比这更好?能否设计出一个算法,其成本总是至多 log n + 1 乘以 OPT?能否设计出更快的算法?这两个问题的答案都是肯定的。
实际上,一个答案可以同时解决这两个问题。我们在算法第一部分使用的线性规划需要多项式时间,但通常比纯组合算法慢。
引入贪心算法
因此,让我们尝试用组合算法替代线性规划。实现方法是采用贪心策略。
具体操作如下:我们进行一个循环,不断选择集合,直到形成一个集合覆盖。这与“采样与迭代”算法类似。
在每一步中,我们重复选择“最佳”集合。我们选择的集合会覆盖一定数量的尚未被覆盖的元素,并且其成本为 C_S。我们选择“最佳”集合,即最大化“新覆盖元素数量”与“集合成本”之比的集合。
贪心算法的性质
当你这样做时,就形成了一个自然的贪心算法。关于这个算法,已知的是:它当然会输出一个集合覆盖,并且其成本总是至多 log n + 1 乘以 OPT。
因此,它与我们研究过的“采样与迭代”算法一样好。
贪心算法的优势
以下是贪心算法的优势:
- 首先,贪心算法是组合算法,不需要求解线性规划。
- 其次,其结果的质量在最坏情况下是有保证的,而不仅仅是平均情况。
但两者的性能界限是相同的,这并非巧合。这两种算法有一些相似之处。实际上,你可以论证,如果采用随机化算法并进行适当的去随机化处理,就可能生成贪心算法。因此,这是生成良好近似算法的另一种技术。
能否超越对数因子? 🧐
我们能否做得比 log n 更好?答案是否定的。
如果 P ≠ NP,那么不可能在多项式时间内得到一个成本总是优于 log n 乘以 OPT 的集合覆盖。因此,这个算法是最优的。如果 P ≠ NP,这就是我们所能期望的最好结果。它是集合覆盖问题的最佳近似算法。
重要贡献者
许多人都研究过这个问题并证明了这些优秀的结果。以下仅列举几位:
- 在20世纪70年代,László Lovász(匈牙利人)设计并分析了贪心算法。
- 在20世纪70年代,David Johnson(美国人)设计并分析了贪心算法。
- 等等,他们都做了同样的事情?实际上,这些都是独立的研究工作。我猜想,当时正是设计第一批近似算法和该领域初步发展的成熟时期。
- 关于最优性,即无法获得更好近似比的 NP 难度结果,是由以色列的 Uriel Feige 证明的。
- 最后,我在讲解“采样与迭代”算法最后一部分时的表述,主要灵感来源于 Neal Young 使用沃尔德不等式的讲义。
本章总结 📚
那么,我们在本章中学到了什么?
- 我们学习了一个著名问题:集合覆盖。
- 我们学习了针对这个基本问题的基本算法和性质。
- 这是我们遇到的第一个重要概念的例子:算法设计中的随机化。
- 对于近似算法,我们使用了基本技术:随机舍入。这项技术在其他场景中也非常有用,包括我们将在下一章看到的内容。
- 在分析工具方面,我们看到的最基本、最重要的工具(如果非要记住一个的话)就是期望的线性性质。这对于分析任何随机化算法都至关重要。
- 最后,就我们进行分析的方式而言,这种缓慢而稳健的方式——按部就班地列出方程,审视每一步——有点像爬山。你必须缓慢而小心地一步接一步,左脚,右脚,左脚,右脚。这样你就能到达山顶,而不会在路上感到疲惫。

下章预告
在下一章中,完成集合覆盖问题的学习后,我们将探讨线性规划的另一个应用,并研究针对多路割问题的一个有趣的随机舍入方法。
032:多路割问题

概述
在本节课中,我们将学习一个新的问题——多路割问题。我们将定义这个问题,分析其计算复杂度,并介绍一个简单的近似算法。随后,我们将探讨如何通过更精细的分析来改进这个算法的近似比,并引入线性规划松弛和随机舍入的概念,为设计更好的算法做准备。
多路割问题的定义 😊
多路割问题的输入和输出定义如下。
输入是一个图,图中的边带有权重。此外,图中有一些特殊的顶点,称为终端。我们有k个终端,k是一个固定的数,可能是2、3,或者像下图中的6个。

目标是通过切割边来将这些终端彼此分离。因此,输出是一个边的子集F,使得从图中移除这些边后,所有终端彼此都不再连通。我们的目标是最小化所切割边的总权重。
这就是多路割问题。
一个特例:最小割问题 😊
有一个我们熟悉特例:当K=2时。两个终端A和B。用最小权重的边集将A与B断开。这就是一个割,一个将A与B分离的割。这就是著名的最小割问题。我们知道,最小割问题可以在多项式时间内精确求解。
问题的复杂度 😊
下一个情况,k=3。三个终端A、B、C必须通过一个最小权重的边集彼此断开。这个问题已经是NP难的了。这并不奇怪,因为在组合优化中,k=2的情况通常简单,而k=3或更多的情况通常是困难且NP难精确求解的。
一个简单的三路割算法 😊
以下是一个针对k=3的简单算法。
我们取三个终端A、B、C。
- 计算将顶点A与B和C分离的最小割。这是一个将顶点划分为两个子集的割,使得跨越割的边具有最小权重,并且它将顶点A与顶点B和C断开。
- 解决相同的问题来隔离顶点B:找到将顶点B与顶点A和C分离的最小割。
- 类似地,计算将顶点C与其他两个终端分离的最小割。
现在,我们取这三个割,选择其中成本最低的两个,并输出它们的并集。
在下图案例中,红色和紫色的割是三个中最便宜的,因此这就是你的解。

算法分析
上一节我们介绍了三路割的简单算法,本节中我们来看看这个算法是否有效。
算法能在多项式时间内运行吗?
我们需要找到一个子程序,用于在多项式时间内将顶点A与顶点B和C分离。这可以通过归约到最小割问题轻松完成。方法如下:
创建一个新顶点S和另一个新顶点T。用无限权重的边将S连接到A。用无限权重的边将顶点B和C连接到T。在这个新图中,求解从S到T的最小割。这个最小割永远不会使用无限权重的边,因为那样成本太高,所以它只会使用图内部的边,从而给出一个A与S在同一侧、B和C与T在同一侧的解决方案,因此它将分离A与BC。实际上,从S到T的割与从A到BC的割存在精确的一一对应关系。所以我们可以用多项式时间解决这个问题。
输出正确吗?它是一个三路割吗?能分离三个终端吗?
我们取了红色和紫色的解决方案。红色割将顶点A与B和C都分开了。所以现在A到B和A到C都没有连接。B和C之间可能仍然存在连接,但由于紫色割将C与其他两个顶点分开了,C与B是断开的。因此,该解决方案是一个三路割,是问题的正确解。
它的效果如何?输出的成本是多少?
我们说过,在分离A与BC、B与AC、C与AB的三个割中,输出的成本是这三个项中最便宜的两个。因此,其成本至多是这三个最小割之和的三分之二。
近似比分析 😊
我们已经上界了算法输出的成本。要继续分析,我们需要下界最优解OPT的成本。
考虑最优解OPT。OPT大于等于从A到BC的最小割。因为最优解分离了所有三个终端,所以它确实将终端A与其他两个终端分离,因此它是从A到BC的一个可行割,所以OPT的值至少是从A到BC的最小割。类似地,它至少是从B到AC的最小割,也至少是从C到AB的最小割。
因此,既然它至少是这三项中的每一项,那么它就至少是它们的平均值,即至少是这三个最小割之和的三分之一。
算法输出至多是三个最小割之和的三分之二,而OPT至少是三个最小割之和的三分之一,因此该算法是一个2-近似算法。
我们证明了我们的解决方案是一个2-近似。你能做得更好吗?通过更仔细的分析,你可以做得更好。
扩展到一般的K 😊
上一节我们分析了三路割的情况,现在让我们将算法扩展到一般的K个终端。
我们有K个终端,A1, A2, ..., Ak。
算法的自然扩展是输出所有k个最小割中最小的k-1个。其中,第i个割分离终端Ai与其他所有终端。同样,这可以在多项式时间内完成,因为要计算将终端Ai与其他终端分离的最小割,我们可以用无限边将源点S连接到Ai,用无限边将汇点T连接到所有其他终端,然后求解从S到T的最小割。
所以这是一个多项式时间算法,它给你一个K路割(多路割),我们需要做的是分析它的值。
类似地,我们输出k个割中最小的k-1个。输出的成本可以通过如果我们从k个割中随机选择k-1个割的平均成本来上界。因此,输出的成本至多是 (k-1)/k 乘以所有最小割之和,即每个终端Ai与其他终端分离的最小割值之和。
对OPT成本的下界分析 😊
现在,让我们对最优解OPT的成本进行下界分析。
我断言,对于每个终端Ai,可以找到一些割将其与其他所有终端分离。考虑属于最优解OPT的一条边E。为什么E是OPT的一部分?因为如果我们把E留在图中,那么两个终端就会连通,这就是为什么OPT必须移除边E。所以,一旦我们移除它,它就会分离某个终端Ai和某个终端Aj。让我们把这条边E放入边集F_i和边集F_j中。我们这样分配OPT的每条边:如果这条边连接一个在终端Ai簇中的顶点和一个在终端J簇中的顶点,这就是F_i和F_j的定义。
换句话说,F_i定义为OPT中那些将终端Ai与某个不同于i的终端Aj分离的边的集合。OPT中的每条边E属于两个集合F_i和F_j。所以,如果我们对所有F_i的成本求和,每条边出现两次,我们恰好得到两倍的OPT。
因此,这为我们提供了一种表达OPT值的方法:2 * OPT = Σ_i cost(F_i)。
接下来,我们如何分析F_i的成本?F_i由OPT中所有将终端Ai与其他某个终端分离的边组成。所以F_i将Ai与其他终端分离。因此,F_i的成本至少是将Ai与其他终端分离的最小成本,即F_i的成本大于等于将Ai与其他终端分离的最小割值。
现在进行求和。我们得到:2 * OPT = Σ_i cost(F_i) ≥ Σ_i (MinCut separating Ai from others)。
因此,2 * OPT ≥ Σ_i (MinCut separating Ai from others)。
结合上下界得出近似比 😊
现在我们要做的就是将这个结果与我们之前得到的算法输出成本的上界结合起来。
算法输出的成本至多是 (k-1)/k 乘以我们证明过的所有最小割之和。现在,这个最小割之和至多是 2 * OPT,所以这给出了什么?它表明该算法是一个 2 * (1 - 1/k) 近似算法。
该算法的近似比为 2 * (1 - 1/k)。
- K = 2:这得到
2 * (1 - 1/2) = 1,即1倍OPT。该算法是最优的。确实,它只是计算一个最小割。 - K = 3:
1 - 1/k是1 - 1/3 = 2/3,2 * 2/3 = 4/3。这是一个4/3近似。
所以实际上,我之前概述的分析并不紧。通过进行这种更仔细的分析,我们能够证明该算法对于k=3是4/3近似,对于一般的k是 2*(1-1/k) 近似。

总结与展望
在本节课中,我们一起学习了多路割问题的定义及其计算复杂度。我们介绍并分析了一个简单的基于最小割的算法,证明了它对于k个终端的近似比为 2*(1-1/k)。特别地,对于三路割问题,该算法是一个4/3-近似算法。
然而,我们还可以做得更好。接下来,我们将尝试设计一个更好的算法,依赖于一个有趣的线性规划松弛和随机舍入技术。这将是我们在后续课程中探索的方向。
033:基于线性规划松弛的多路割问题建模 🧩


在本节课中,我们将学习如何为多路割问题设计一个更复杂、性能更好的算法。这个算法的核心思想是使用一个有趣的线性规划松弛方法。
整数规划建模
上一节我们介绍了一个简单的多路割算法。本节中,我们来看看如何为多路割问题设计一个整数规划模型。
我们需要定义一些变量。考虑到多路割的本质是将图的顶点划分为与各个终端对应的簇。
以下是定义变量的步骤:
- 对于每个图顶点
U和每个终端i,我们定义一个变量x_{U,i}。这个变量的含义是:如果顶点U属于终端i的簇,则其值为 1。 - 对于每条边
E和每个终端i,我们定义一个变量z_{E,i}。这个变量的含义是:当且仅当边E从簇i中穿出时,其值为 1。
接下来,我们需要为整数规划添加约束条件。
以下是主要的约束条件:
- 所有变量
x_{U,i}和z_{E,i}必须是 0 或 1。 - 对于终端
a_i,它必须位于自己的簇中,因此x_{a_i, i} = 1。 - 每个顶点必须恰好属于一个簇。因此,对于任意顶点
U,所有x_{U,i}的和必须等于 1。 - 我们需要表达“边穿过簇”这一条件。对于边
E = (U, V)和终端i,当且仅当x_{U,i}不等于x_{V,i}时,z_{E,i}应为 1。这可以表示为:z_{E,i} >= |x_{U,i} - x_{V,i}|。由于这是“大于等于”绝对值的不等式,我们可以将其线性化为两个条件:z_{E,i} >= x_{U,i} - x_{V,i}和z_{E,i} >= x_{V,i} - x_{U,i}。
最后,我们定义目标函数。目标是计算割的权重,即所有穿过不同簇的边的权重之和。对于一条边 E,如果它连接两个不同的簇,则恰好有两个 z_{E,i} 值为 1(对应两个簇)。因此,目标函数是最小化 (1/2) * Σ_{E} Σ_{i} (c_E * z_{E,i}),其中 c_E 是边 E 的权重。
综上所述,我们得到了多路割问题的整数规划模型。
线性规划松弛与几何解释
现在,我们进行线性规划松弛。方法很简单:将变量 x_{U,i} 和 z_{E,i} 的 0/1 整数约束,松弛为在 [0, 1] 区间内的实数约束。其他所有约束和目标函数保持不变。
这个线性规划松弛的有趣之处在于,我们可以对其进行几何解释。我们将其视为一个几何对象来研究。
对于每个顶点 U,我们可以将其变量 x_{U,i} 视为一个 K 维向量(K 是终端数量)。由于所有 x_{U,i} 非负且和为 1,这个向量的 L1 范数等于 1。
让我们以 K=3 为例进行可视化。三个终端 a_1, a_2, a_3 对应的向量分别是 (1,0,0), (0,1,0), (0,0,1)。所有其他顶点的向量必须满足 x1 + x2 + x3 = 1 且各分量非负。这在三维空间中定义了一个三角形,三个终端位于三角形的三个角上。
目标函数可以重写为:最小化 (1/2) * Σ_{(U,V)} c_{UV} * ||x_U - x_V||_1,即所有边对应的向量之间的 L1 距离的加权和。
进一步分析这个三角形,我们可以发现一个关键的几何事实:两个点 x 和 y 之间的 L1 距离,等于它们在该三角形三条边上的投影之间的距离之和的一半。
因此,当 K=3 时,线性规划松弛所做的几何解释是:将图的顶点嵌入到这个三角形中(三个终端固定在三个角上),以最小化所有图的边在三角形三条边上的投影的加权长度。
对于 K>3 的情况,原理类似,只是将三角形推广到更高维的单形(如四面体),但 K=3 的情况更易于可视化。
总结

本节课中,我们一起学习了如何为多路割问题构建一个基于线性规划松弛的算法框架。我们首先设计了一个整数规划模型,然后将其松弛为线性规划。最关键的一步是,我们为这个松弛问题(特别是 K=3 时)找到了一个清晰的几何解释:将图的顶点嵌入到一个三角形中并优化投影长度。这为我们下一节将要讨论的舍入策略奠定了坚实的基础。
034:随机化舍入


在本节课中,我们将学习如何对线性规划松弛的解进行“舍入”,从而得到一个可行的、高质量的近似解。我们将聚焦于一个几何视角,并引入一种称为“随机化舍入”的巧妙技术。
上一节我们介绍了多路割问题的线性规划松弛及其几何解释。本节中我们来看看如何将松弛解“舍入”为一个实际的划分方案。
几何舍入问题
考虑 K=3 的情况。我们有一个三角形,三个终端分别位于三个角上。每个图顶点都嵌入到这个三角形中。我们的目标是将三角形划分为三个区域,分别对应三个终端。
以下是划分规则:
- 位于第一个区域(例如,靠近角 A1 的区域)的顶点(黑色)将被划分到终端 A1。
- 位于第二个区域(例如,靠近角 A2 的区域)的顶点(绿色)将被划分到终端 A2。
- 位于第三个区域(例如,靠近角 A3 的区域)的顶点(红色)将被划分到终端 A3。
这样划分后,就得到了顶点的一个三分划。割的成本是所有端点颜色不同的边的权重之和。例如,如果边 (U, V) 的端点 U(黑色)和 V(绿色)被分到不同终端,我们就需要支付这条边的成本 C_UV。
因此,一个好的舍入方案,就是找到一种将三角形划分为三个区域的方法,使得对应的割成本尽可能小。
从嵌入到舍入
回忆一下,线性规划松弛可以看作是将顶点放置在三角形中,以最小化边在三角形各边上的投影长度之和。这里,定义两个顶点 U 和 V 之间的距离为它们嵌入坐标的 L1 距离的一半,即 d(U, V) = 0.5 * ||x_U - x_V||_1。这样定义后,终端之间的距离恰好为 1。
因此,求解线性规划松弛,就是找到一种顶点嵌入方式,使得所有边 (U, V) 的权重 C_UV 乘以距离 d(U, V) 的总和最小。
现在我们需要思考如何舍入。一个直观的想法是:如果一个顶点离某个终端(三角形的角)很近,那么它就应该被划分给那个终端。
随机化舍入算法
我们如何实现这个直观想法呢?我们使用“球”的概念。考虑以终端 A1 为中心、半径为 R 的球(在 L1 距离下)。这个球是三角形中的一个区域,由一条平行于对边的直线界定。如果 R 较小,这个球就靠近角 A1;如果 R 接近 1,这个球几乎覆盖整个三角形。
以下是核心思路:我们将离终端足够近(即在以该终端为中心、半径为 R 的球内)的顶点分配给该终端。但这里有两个问题:
- 我们应该以什么顺序考虑各个终端?
- 半径 R 应该取什么值?
当我们不知道答案时,一个经典的策略是:引入随机性。
以下是完整的随机化舍入算法(针对一般 K 值):
- 求解线性规划松弛:将顶点嵌入到 K-单纯形(例如,K=3 是三角形,K=4 是四面体)中,终端位于角上。目标是最小化
∑ C_UV * d(U, V)。 - 随机排序:随机生成终端的一个排列
σ(1), σ(2), ..., σ(K)。 - 随机半径:随机均匀地选取一个半径
R ∈ [0, 1]。 - 顺序分配:
- 对于
i从 1 到K-1:- 将所有尚未分配的、且位于以终端
σ(i)为中心、半径为R的球内的顶点,分配给终端σ(i)。
- 将所有尚未分配的、且位于以终端
- 将所有剩余的顶点分配给最后一个终端
σ(K)。
- 对于
算法分析(思路)
为了理解这个算法的效果,我们需要分析一条边 (U, V) 被割(即其两个端点被分到不同终端)的概率。
考虑我们首先处理终端 A3 的情况(在随机排序中它可能出现在任何位置)。随机半径 R 会与顶点 U 和 V 到 A3 的距离 d_U 和 d_V 进行比较。有三种情况:
- 情况一:
R < min(d_U, d_V)。球很小,U 和 V 都不在球内,它们都不会在这一轮被分配给 A3。 - 情况二:
min(d_U, d_V) ≤ R < max(d_U, d_V)。球包含了离 A3 较近的那个顶点(比如 V),但不包含另一个(U)。因此 V 被分配给 A3,而 U 不会被分配给 A3(可能被分配给其他终端),导致边(U, V)被割。 - 情况三:
R ≥ max(d_U, d_V)。球很大,U 和 V 都在球内,它们会被同时分配给 A3,因此边(U, V)不会被割。

可以计算,情况二发生的概率正好是 |d_U - d_V|,而在线性规划的几何解释中,这个差值正好等于顶点 U 和 V 在某个坐标上的差值。对所有终端和所有可能的排序进行概率分析后,可以证明:边 (U, V) 被割的期望成本,不超过线性规划松弛中该边贡献的成本的某个常数倍(对于 K=3,这个常数是 2)。
通过期望值的线性性质,整个割的期望总成本也就不超过线性规划松弛最优值的常数倍。由于线性规划松弛的最优值是原问题最优解的下界,这就证明了我们的随机化算法是一个常数因子的近似算法。
本节课中我们一起学习了如何利用几何视角和随机化技术,将多路割问题的线性规划松弛解舍入为一个可行的近似解。我们介绍了随机化舍入算法的完整步骤,并概述了其分析的核心思想——通过计算边被割的期望概率,将其与线性规划松弛中的贡献联系起来,从而证明算法的近似比。这种方法将组合优化问题与概率论、几何直观优美地结合在了一起。
035:多路割算法分析 🧮


在本节课中,我们将学习如何分析一个用于解决多路割问题的算法。我们将通过线性规划和随机舍入的方法,推导出算法的近似比,并理解其背后的几何解释。
算法分析概述
上一节我们设计了一个多路割算法,本节中我们来看看如何分析它的性能。首先,算法在多项式时间内运行,并且输出一个有效的多路割。核心问题是:输出的质量如何?我们通过计算期望值来评估。
期望值计算
由于算法是随机的,我们关注输出的期望值。利用期望的线性性质,我们可以交换求和与期望的顺序。输出的期望值是图中每条边 C_UV 乘以该边被割的概率之和。
以下是计算步骤:
- 关注单条边:我们只需分析图中任意一条边
UV被割的概率。 - 舍入规则:在
k=3的情况下,算法随机排列三个终端点顺序,并依次以它们为中心画球。 - 割边条件:边
UV被割,当且仅当在某个终端点I被处理时,以其为中心的球恰好包含U和V中的一个。
概率分析
现在我们来分析边 UV 被割的概率。这分为两种情况:
- 情况一:终端点
I不是离U或V最近的点(记为L)。此时,I必须在随机排序中先于L(概率为1/2),并且以其为中心的球半径R必须落在d(I, U)和d(I, V)之间(概率为|d(I,U) - d(I,V)|)。这两个事件独立。 - 情况二:终端点
I就是最近点L。此时,L不能是排序中的最后一个点(概率为1 - 1/k),并且其球半径R必须落在d(L, U)和d(L, V)之间(概率为|d(L,U) - d(L,V)|)。这两个事件也独立。
综合这两种情况,边 UV 被割的概率 P(UV cut) 可以表示为:
P(UV cut) = (1 - 1/k) * |U_L - V_L| + Σ_{I ≠ L} (1/2) * |U_I - V_I|
其中 U_I 和 V_I 是顶点在嵌入到 L1 度量空间后对应于终端 I 的坐标。
与线性规划目标的关联
我们对上述表达式进行整理,将其与线性规划松弛的目标函数关联起来。
- 合并求和:将第二项中的
1/2因子提取出来,可以将求和扩展到所有终端点(包括L),得到(1/2) * Σ_I |U_I - V_I|。 - 处理第一项:剩余的第一项变为
(1/2 - 1/k) * |U_L - V_L|。 - 关键上界:注意到在单纯形嵌入中,所有坐标和为1。可以证明,对于任意两个点
U和V,有|U_L - V_L| ≤ (1/2) * Σ_I |U_I - V_I|。这是L1距离的一半。
将上界代入,我们得到:
P(UV cut) ≤ (1/2 - 1/k) * (1/2) * Σ_I |U_I - V_I| + (1/2) * Σ_I |U_I - V_I| = (3/2 - 1/k) * (1/2) * Σ_I |U_I - V_I|
近似比推导
现在,整个割的期望成本 E[cost] 是每条边成本乘以割边概率的总和:
E[cost] = Σ_{edge UV} C_UV * P(UV cut) ≤ (3/2 - 1/k) * (1/2) * Σ_{edge UV} C_UV * Σ_I |U_I - V_I|
表达式 Σ_{edge UV} C_UV * Σ_I |U_I - V_I| 正是我们线性规划松弛的目标函数值,记作 OPT_LP。根据定义,OPT_LP ≤ OPT,其中 OPT 是最优整数解的值。
因此,我们最终证明了:
E[cost] ≤ (3/2 - 1/k) * OPT
总结

本节课中我们一起学习了多路割算法的分析。我们证明了,通过线性规划松弛和基于几何解释的随机舍入,可以为多路割问题获得一个 (3/2 - 1/k) 的近似比。这个分析展示了如何将概率计算与线性规划目标巧妙地联系起来。接下来,我们将探索如何改进这个结果。
036:多路割问题结论


在本节课中,我们将总结关于多路割问题的近似算法设计,回顾已学的核心技术与问题,并展望后续课程内容。
算法设计与分析回顾
上一节我们介绍并分析了一个针对多路割问题的近似算法。
我们设计并分析了一个针对多路割问题的近似算法。
关于问题的进一步探讨
关于这个问题,我们还能说些什么?
首先,我们使用了一个巧妙的几何嵌入方法来解释线性规划松弛。随后,我们以此为灵感设计了舍入方案。
我们能否通过更好的随机舍入方法,为多路割问题获得更优的近似比?是否可能得到优于 3/2 - 1/k 的近似比?
答案是肯定的。例如,对于 k=3 的情况,通过使用线性规划松弛并找到更好的舍入方法,可以获得 12/11 的近似比。
因此,我们不仅仅使用这些“球”进行舍入,还可以将寻找良好随机舍入的问题表述为一个线性规划问题。寻找正确的随机舍入方式可以表达为线性规划。我们通过一个线性程序来对线性规划松弛解进行舍入,这非常巧妙。
对于更大的 k,同样可以使用此方法来尝试获得更好的近似比。超越 3/2 近似是可能的。然而,该问题是 APX-hard 的。如果 P ≠ NP,则不可能得到 1 + ε 的近似,即不存在该问题的近似方案。
多路割问题的背景故事
让我们简要了解一下多路割问题背后的故事。第一篇处理近似算法的论文为我们提供了一些关于应用背景的介绍。
以下是他们的说法:该问题出现在并行计算系统中通信成本最小化、将程序模块分配给处理器、在网络节点间划分文件、在多计算机环境中将用户分配给计算机,以及将电路元件划分到不同芯片上的子电路等场景中。最后一个问题在某种程度上更具几何特性,因此可能稍微容易一些,因为它是一个更特殊的特例。
这些问题的变体出现在许多需要将某物分配给另一物,且存在底层图结构的场景中。
那么,谁最先从近似算法的角度研究这个问题呢?
以下是设计本章开头所见基础算法的先驱者:Elias Dahlhaus, David Johnson, Mihalis Yannakakis, Christos Papadimitriou 和 Paul Seymour。他们证明了问题的 NP-hardness,并给出了使用最小割的近似算法。其中一些名字您可能之前见过,这些人在近似算法领域非常知名。
谁提出了几何表示法?Kalev, Karger 和 Rabani。他们提出了几何嵌入以及如何使用它。这非常巧妙,我认为这是本章最具新意的想法。他们正是通过这个方法得到了 3/2 - 1/k 的近似比。
第三,谁提出了通过使用线性规划寻找正确的舍入方式,以获得更好的随机舍入,从而改进近似比的方法?David R. Karger, Philip Klein, Cliff Stein, Neal Young。他们得到了 12/11 的近似比。
以上就是为多路割问题设计近似算法的主要步骤。
课程技术总结
在过去的五周里,我们学习了近似算法设计中的一系列技术。
以下是我们在课程中学习到的主要技术:
- 适应性舍入输入:以可能具有适应性的方式对输入进行舍入。
- 线性规划松弛:我们深入研究的工具。
- 随机舍入:有时是复杂的随机舍入版本。
- 概率分析技术:特别是在集合覆盖的背景下,用于证明我们结果的优良性。
- 线性规划松弛的几何解释:将抽象的松弛解赋予直观的几何意义。
这已经是一套很好的工具集,开始构成了近似算法领域的扎实知识。
已研究的问题
在过去的几周里,我们研究了哪些问题?
以下是五个组合优化的基础性问题:
- 顶点覆盖
- 背包问题
- 装箱问题
- 集合覆盖
- 多路割
它们都是 NP-hard 问题。对于所有这些问题,我们都设计了良好的近似算法,并且每一个问题都为我们提供了学习新技术的机会,无论是用于设计近似算法还是用于分析解的质量。
后续展望
那么,我们还缺少什么呢?仍然有一些重要的技术尚未涉及。
如果您想探索它们,您需要学习后续课程——近似算法(二)。在那门课程中,我们将研究:
- 线性规划对偶性:使用线性规划的一个基础、核心工具。
- 原始对偶算法:利用线性规划对偶性来设计高效的近似算法。
- 半定规划:一种非常强大的技术,用于解决以前看似无法处理的问题。

因此,如果您想学习这些技术,请参加近似算法(二)课程。希望在那里见到您。
本节课总结:本节课我们一起回顾了多路割近似算法的设计与改进思路,总结了本课程第一部分所涵盖的核心技术(如线性规划松弛、随机舍入、几何解释等)和五个经典 NP-hard 问题(顶点覆盖、背包、装箱、集合覆盖、多路割),并展望了将在第二部分课程中学习的线性规划对偶性、原始对偶算法和半定规划等高级主题。

浙公网安备 33010602011771号