[饭后算法系列] 最大不重叠的集合覆盖问题
1. 问题
给定一组集合S1, S2, ..., Sn,其满足:对任意一个集合Si,存在Sj (j!=i) 使得Si与Sj相交
要求: 从S1, S2, ..., Sn选择k个互不相交的集合,使得这k个集合的并集包含的元素最多
2. 分析
集合覆盖属于NP完全问题, 多项式内不能解, 时间复杂度最好为O(2^n)
因此, 我们可以做一些多项式时间的预处理:
1. 做预处理, 得到B(Si, Sj) = true/false, 表示两个集合是否相交
2. 做预处理, 得到|Si| = Ni, 表示集合的元素数
暴力解法如下:
1. 取出{S1, S2, ..., Sn}的所有子集, 共O(2^n)种情况
2. 计算该子集内部是否有两个元素有交集, 这是一个双重循环, 时间复杂度O(n^2)
因此暴力法的时间复杂度为O(2^n * n^2)
3. 动态规划
对于{S1, ..., Sn}的一个子集{Si1, Si2, ..., Sik}, 要计算它的覆盖结果R(Si1, Si2, ..., Sik):
1. 如果{Si1, Si2, ... Sik}是两两不相交的, 则R(Si1, Si2, ..., Sik) = R(Si1, Si2, ..., Si(k-1)) + |Sik|
2. 如果{Si1, Si2, ... Sik}内部有交集, 则R(Si1, Si2, ..., Sik) = 0
第2步有两个可能:
2.1 {Si1, Si2, ... Si(k-1)}内部已经有交集了, 即R(Si1, Si2, ..., Si(k-1)) = 0
2.2 R(Si1, Si2, ..., Si(k-1)) != 0, 但Sik和{Si1, Si2, ... Si(k-1)}中的某个S有交集
算法的主体:
1 result = {S1, ..., Sn}的一个子集, max = 该子集并集的元素数 2 初始状态result = 空集, max = 0 3 for s = S1 to Sn: # 循环单个元素 4 R(s) = |s| 5 if R(s) > max: 6 max = R(s), result = s 7 8 # 循环所有子集 9 for size = 2 to n: 10 for A 为 {S1, ..., Sn}长度为size的子集: 11 last(A) = A的最后一个元素 12 pre(A) = A - last(A) 即A去除最后一个元素 13 R(A) = R(pre(A)) + |last(A)| if R(pre(A)) > 0 and last(A)和pre(A)中的元素都不相交 (*) 14 R(A) = 0 if 上述条件不满足 15 if R(A) > max: 16 max = R(A), result = A
算法的第9, 10行一共迭代了{S1, S2, ..., Sn}的所有子集, 共O(2^n)种情况
算法的第13行(打*号)中, 需要通过一次循环进行相交的判定, 需要O(n)时间
因此动态规划法的总体时间复杂度为 O(2^n * n)
关键字: 算法, 集合覆盖, 动态规划