集合覆盖问题的贪心和界限
集合覆盖问题 Set Cover :给定一个全集 \(U\),以及若干个子集 \(S_1, S_2, \dots, S_m\),希望选出尽可能少的子集,使它们的并集覆盖整个 \(U\)。如果每个集合还有费用 \(c_i\),目标就从“集合数量最少”变成“总费用最小”。无权版本可以看作每个集合费用都是 \(1\) 的特殊情况。
困难性质:重叠把选择变成了搜索
Set Cover 是 NP-hard 的。一个直观的看法是,它足够表达 Vertex Cover。给定图 \(G=(V,E)\),把每条边看作全集里的一个元素,也就是 \(U=E\)。对每个顶点 \(v\),构造一个集合 \(S_v\),其中包含所有与 \(v\) 相连的边。现在,选若干个集合覆盖所有边,就等价于选若干个顶点,使每条边至少有一个端点被选中。这个归约说明 Set Cover 至少不会比 Vertex Cover 更容易。
除非问题规模很小,或者集合结构有特殊性质,否则不能指望一个精确算法在一般情况下跑得很好。暴力搜索要检查 \(2^m\) 种集合组合;即使加上剪枝,只要输入稍微恶劣一点,搜索空间仍然会膨胀。
所以 Set Cover 只能有近似算法。它的精确解很难,但它又有一个简单、稳定、可实现的贪心算法,而且这个算法能给出漂亮的调和数上界。
贪心算法:先买最便宜的新增覆盖
无权 Set Cover 的贪心策略很直接:每一步选择能覆盖最多未覆盖元素的集合。已经覆盖过的元素不再计入收益,因为重复覆盖不会让目标更接近完成。
如果是带权版本,则需要稍微更加精巧的构思:每一步选择单位新增覆盖成本最低的集合,也就是最小化 \(c_i / |S_i \setminus C|\),其中 \(C\) 是当前已经覆盖的元素集合。也就是说,让选择此集合能新覆盖的元素数量相比其价格最小。而无权版本就是 \(c_i=1\) 时的特例。
伪代码可以写得很短:
C = ∅
answer = []
while C != U:
choose S_i with S_i \ C ≠ ∅ minimizing c_i / |S_i \ C|
answer.append(S_i)
C = C ∪ S_i
如果某一轮已经不存在能覆盖新元素的集合,而 \(C \neq U\),说明输入本身不可行。
这个问题的集合费用通常假设非负;零费用集合可以优先加入,因为它们不会增加目标值。
真正写程序时,可以护元素到集合的倒排索引,并用堆保存当前看起来最好的集合。由于覆盖状态不断变化,堆里的收益会过期,所以通常采用 lazy update:弹出堆顶后重新计算它的真实收益,如果仍然最优再使用,否则把更新后的值放回堆里。
调和上界:每个元素被收了一次“服务费”
这个算法显然不是最优,不过他离最优解大概有多近呢?
它其实有一个经典上界。设全集大小为 \(n\),则贪心算法的费用不超过最优解费用的 \(H_n\) 倍,其中 \(H_n = 1 + 1/2 + \cdots + 1/n\),大约是 \(\log n + O(1)\)。也称作 \(H(n)\)- 近似。
这个调和数不是凭空冒出来的。可以用一种“收费”的方式理解它。每当贪心算法选择一个集合 \(S\),它的费用 \(c(S)\) 会平均分摊到这一步新覆盖的元素上。假如这一步新覆盖了 \(k\) 个元素,那么每个新元素被收取 \(c(S)/k\) 的价格。算法最终的总费用,正好等于所有元素被收取的价格之和。
现在拿最优解里的某个集合 \(T\) 来观察。按这些元素被覆盖的时间排序。第一个被覆盖时,\(T\) 中还至少有 \(|T|-1\) 个未覆盖元素;第二个被覆盖时,还至少有 \(|T|-2\) 个未覆盖元素;依此类推。
在任意时刻,集合 \(T\) 本身都是一个可选集合。贪心会选择单位新增覆盖成本最低的集合,既然之前没有选他,那么当 \(T\) 中还剩 \(r\) 个元素未被覆盖时,贪心给某个新元素分摊的价格不会超过 \(c(T)/r\)。于是,\(T\) 中这些元素被收取的总价格至多为:
对最优解中的所有集合做同样分析,就得到贪心费用不超过 \(H_d\) 倍最优费用。由于 \(d \leq n\),也就有 \(H_n\) 上界。
这个证明简介深入。贪心不保证最优,而是保证不会太离谱。重叠越复杂,局部选择越可能绕路;调和数衡量的正是这种绕路能坏到什么程度。
例子和上界紧性
令 \(U=\{a,b,c,d,e,f\}\),给出三个集合:
G = {a,b,c,d}
A = {a,b,e}
B = {c,d,f}
无权情况下,贪心第一步会选 \(G\),因为它覆盖 \(4\) 个元素,比 \(A\) 和 \(B\) 都大。选完 \(G\) 后,只剩 \(e\) 和 \(f\) 没覆盖,而 \(A\) 只能补上 \(e\),\(B\) 只能补上 \(f\),所以贪心最终要选 \(G,A,B\) 三个集合。可是最优解只需要 \(A\) 和 \(B\) 两个集合。
更接近调和上界的构造,在带权 Set Cover 中反而很清楚。令全集为 \(U={e_1,e_2,\dots,e_n}\)。构造一个大集合 \(T=U\),费用为 \(1\)。同时,对每个元素 \(e_r\) 构造一个单元素集合 \({e_r}\),费用设为 \(1/r-\varepsilon\),其中 \(\varepsilon\) 是一个很小的正数。
最优解显然可以直接选择 \(T\),总费用是 \(1\)。但贪心会怎样?一开始还有 \(n\) 个元素未覆盖,集合 \(T\) 的单位新增覆盖成本是 \(1/n\),而单元素集合 \({e_n}\) 的成本是 \(1/n-\varepsilon\),更便宜,所以贪心会先选 \({e_n}\)。接着还剩 \(n-1\) 个元素,\(T\) 的单位成本变成 \(1/(n-1)\),而 \({e_{n-1}}\) 的成本是 \(1/(n-1)-\varepsilon\),贪心又会选单元素集合。
这个过程会一直持续下去。贪心最终选择所有单元素集合,总费用接近:
也就是 \(H_n\)。而最优解费用仍然是 \(1\)。当 \(\varepsilon\) 足够小时,贪心和最优解的比值就可以任意接近 \(H_n\)。
这个构造揭示了贪心算法的弱点:贪心每一步都被一个“刚好便宜一点”的局部选择吸引,结果一路错过了那个一次性覆盖全部元素的大集合。调和数不是凭空出现的,它正对应了这个过程中逐步变贵的边际覆盖成本。
已有 1998 年研究的经典不可近似性结果:Set Cover 的最优近似比就是 \(\ln n\),不可能保证近似比小于 \((1-o(1))\ln n\),再往前推进任何一点点,都会让问题变得和 P=NP 一样困难。
所以,调和数不仅是贪心算法的分析上界,更是整个问题在多项式时间内的理论极限。

浙公网安备 33010602011771号