最小子集覆盖所有集合

时间:2019-05-02 19:57:11

标签: python set

我们提供了5种不同的套装:

s1 = {a,b,c}
s2 = {c,d}
s3 = {a,g,h}
s4 = {d,e,f}
s5 = {g,k,l}

目标是找到最少数量的物品,以使每个集合至少代表一次。在这种情况下,我们可以轻松地看到想法解决方案是{a,d,g}。有没有办法通过编程方式做到这一点?

编辑:这是我到目前为止的内容(r是集合列表)

for i in r:
    i.sort()

r.sort(reverse=True)

arr = []

arr.append(r[0][0])

def isInArr(k):
    for j in k:
        if j in arr:
            return False
    return True

for i in r[1:]:
    if isInArr(i):
        arr.append(i[0])

编辑2:

此代码结合了Connor的答案,并暴力破解了(据我所知)最佳解决方案。

def isnotarr(k,rs):
    for j in k:
        if j in rs:
            return True
    return False

def most_frequent(List):
   return max(set(List), key = List.count)

##s1 = set(["a", "b", "c"])
##s2 = set(["c", "d"])
##s3 = set(["a", "g", "h"])
##s4 = set(["d", "e", "f"])
##s5 = set(["g", "k", "l"])
set_list = [set(i) for i in r]

return_set = []
while len(set_list) > 0:
   elements = []
   for s in set_list:
       for el in s:
           elements.append(el)
   element = most_frequent(elements)
   return_set.append(element)
   new_set_list = []
   for s in set_list:
       if element not in s:
           new_set_list.append(s)
   set_list = new_set_list

print "================initial set found============\n"
print(return_set)
print "================initial set found============\n"



def isvalidcomb(cm):
    for el in r:
        if isnotarr(el,cm):
            pass
        else:
            return False
    return True

def bfopt(n):
    combs = itertools.combinations(return_set,n)
    for i in combs:
        if isvalidcomb(i):
            return i
    return None

for i in range(len(return_set),0,-1):
    print "===========computing sets for maxlen %d============\n"%(i)
    tmp = bfopt(i)
    if tmp is not None:
        print tmp

5 个答案:

答案 0 :(得分:2)

这就是我的方法。

select getSetting('a_setting_name');

答案 1 :(得分:2)

首先:每个集合可以由2的幂表示:si = 2^(i-1)

每个字母都可以视为重量为1的具有一定值的物品。

一个字母的值可以作为它所代表的集合的总和来评估。

例如: a 表示 s1 s3 ,因此 value [a] = 2 ^(1-1)+ 2 ^(3-1)= 3

现在,您的目标是找到权重最小的iten量,以使其总和为(1 + 2 + 4 + 8 + 16)=31。这基本上是knapsack problem,已知的动态编程问题。每个项目都是一个字母,您的背包最大尺寸为5。您需要在此大小范围内获得31的值。

关于每个字母的值,您可以进行预处理。

答案 2 :(得分:1)

这正是 set cover problem ,这是经典的离散优化问题。它是NP难的,但是有很多好的算法,包括精确算法和近似算法。

答案 3 :(得分:0)

您可以使用itertools.combinations从所有不同的项目中选择数量不断增加的项目,并检查所选择的项目集是否在列表中的每个集合中至少有一个项目:

from itertools import count, combinations
r = [{'a', 'b', 'c'},
     {'c', 'd'},
     {'a', 'g', 'h'},
     {'d', 'e', 'f'},
     {'g', 'k', 'l'}]
all_items = set.union(*r)
print(next(set(picks) for size in count(1)
    for picks in combinations(all_items, size)
    if all(any(i in s for i in picks) for s in r)))

这将输出:(您的样本输入具有多个最佳解决方案,并且仅输出其中一个。)

{'c', 'd', 'g'}

如果需要所有最佳解决方案,则可以在上方的生成器表达式上使用itertools.groupby作为关键功能,使用len

from itertools import groupby
list(next(groupby((set(picks) for size in count(1)
    for picks in combinations(all_items, size)
    if all(any(i in s for i in picks) for s in r)), len))[1])

这将返回:

[{'f', 'c', 'g'},
 {'e', 'c', 'g'},
 {'a', 'd', 'g'},
 {'b', 'd', 'g'},
 {'c', 'd', 'g'},
 {'a', 'd', 'l'},
 {'a', 'd', 'k'}]

答案 4 :(得分:0)

正如我刚学到的,还有另一种解决方法。本质上,每个元素都是一个bool变量,每个集合都是OR约束的集合。每个集合都必须返回true,并将元素的最小数量设置为true。事实证明,使用z3之类的线性求解器可以轻松解决该问题。只需将每组true的总和设置为要最小化的变量就可以了。