我们提供了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
答案 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
的总和设置为要最小化的变量就可以了。