集合中加权元素的组合,其中加权和等于固定整数(python中)
我想在一个集合中找到所有可能的加权元素组合,其中它们的权重之和正好等于给定的权重W 假设我想从集合集合中加权元素的组合,其中加权和等于固定整数(python中),python,combinations,knapsack-problem,Python,Combinations,Knapsack Problem,我想在一个集合中找到所有可能的加权元素组合,其中它们的权重之和正好等于给定的权重W 假设我想从集合{A',B',C',D',E'}中选择k个元素,其中权重={A':2',B':1',C':3',D':2',E':1}和W=4 那么这将产生: ('A'、'B'、'E') (‘A’、‘D’) (‘B’,‘C’) (‘B’、‘D’、‘E’) ('C','E') 我意识到蛮力方法是找到给定集合的所有置换(使用itertools.permutations)并用加权和W拼接出前k个元素。但我处理的是每个集合
{A',B',C',D',E'}
中选择k个元素,其中权重={A':2',B':1',C':3',D':2',E':1}
和W=4
那么这将产生:
('A'、'B'、'E')
(‘A’、‘D’)
(‘B’,‘C’)
(‘B’、‘D’、‘E’)
('C','E')
我意识到蛮力方法是找到给定集合的所有置换(使用itertools.permutations
)并用加权和W拼接出前k个元素。但我处理的是每个集合至少20个元素,这在计算上很昂贵
我认为使用背包的一种变体会有所帮助,其中只考虑重量(而不是价值),并且重量之和必须等于到W(而不是更低)
我想用python实现这一点,但任何cs理论提示都会有所帮助。优雅的奖励积分 在所有n中循环!排列太贵了。而是生成所有2^n子集
屈服
[('A', 'D'), ('C', 'B'), ('C', 'E'), ('A', 'B', 'E'), ('B', 'E', 'D')]
通过将列表理解的返回部分更改为tuple(sorted(x))
,或者将list
调用powerset
替换为一个tosorted
,可以将其转换为排序元组有效的方法是使用前k项创建具有相同权重的元素集
从k=0的空集开始,然后使用k-1中的组合为k创建组合。除非可以使用负权重,否则可以修剪总权重大于W的组合
下面是使用您的示例的结果:
comb[k,w]是使用前k个元素具有总权重w的元素集。大括号用于集合。
S+e是通过将元素e添加到S的每个成员而创建的集合集
comb[0,0]={}
comb[1,0]={comb[0,0]}
comb[1,2]={comb[0,0]+'A'}
comb[2,0]={comb[1,0]}
comb[2,1]={comb[1,0]+'B'}
comb[2,2]={comb[1,2]}
comb[2,3]={comb[1,2]'B'}
comb[3,0]={comb[2,0]}
comb[3,1]={comb[2,1]}
comb[3,2]={comb[2,2]}
comb[3,3]={comb[2,3],comb[2,0]+'C'}
comb[3,4]={comb[2,3]+'C'}
comb[4,0]={comb[3,0]}
comb[4,1]={comb[3,1]}
comb[4,2]={comb[3,2],comb[3,0]+'D'}
comb[4,3]={comb[3,3],comb[3,1]+'D'}
comb[4,4]={comb[3,4],comb[3,2]+'D'}
comb[5,0]={comb[4,0]}
comb[5,1]={comb[4,1],comb[4,0]+'E'}
comb[5,2]={comb[4,2],comb[4,1]+'E'}
comb[5,3]={comb[4,3],comb[4,2]+'E'}
comb[5,4]={comb[4,4],comb[4,3]+'E'}
答案是comb[5,4],它简化为:
{
{{'B'}+'C'},
{{'A'}+'D'},
{
{{'A'}+'B'},
{'C'},
{'B'}+'D'
}+'E'
}
给出所有的组合。这类集合中的项目数有上限吗?如果您这样做了,并且它最多约为40,那么中所述的计算可能非常简单,并且比蛮力计算的复杂度要低得多 注意:使用比Python dict更节省内存的数据结构,这也适用于更大的集合。一个高效的实现应该能够轻松地处理大小为60的集合 下面是一个示例实现:
from collections import defaultdict
from itertools import chain, combinations, product
# taken from the docs of the itertools module
def powerset(iterable):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in xrange(len(s) + 1))
def gen_sums(weights):
"""Given a set of weights, generate a sum --> subsets mapping.
For each posible sum, this will create a list of subsets of weights
with that sum.
>>> gen_sums({'A':1, 'B':1})
{0: [()], 1: [('A',), ('B',)], 2: [('A', 'B')]}
"""
sums = defaultdict(list)
for weight_items in powerset(weights.items()):
if not weight_items:
sums[0].append(())
else:
keys, weights = zip(*weight_items)
sums[sum(weights)].append(keys)
return dict(sums)
def meet_in_the_middle(weights, target_sum):
"""Find subsets of the given weights with the desired sum.
This uses a simplified meet-in-the-middle algorithm.
>>> weights = {'A':2, 'B':1, 'C':3, 'D':2, 'E':1}
>>> list(meet_in_the_middle(weights, 4))
[('B', 'E', 'D'), ('A', 'D'), ('A', 'B', 'E'), ('C', 'B'), ('C', 'E')]
"""
# split weights into two groups
weights_list = weights.items()
weights_set1 = dict(weights_list[:len(weights)//2])
weights_set2 = dict(weights_list[len(weights_set1):])
# generate sum --> subsets mapping for each group of weights,
# and sort the groups in descending order
set1_sums = sorted(gen_sums(set1).items())
set2_sums = sorted(gen_sums(set2).items(), reverse=True)
# run over the first sorted list, meanwhile going through the
# second list and looking for exact matches
set2_sums = iter(set2_sums)
try:
set2_sum, subsets2 = set2_sums.next()
for set1_sum, subsets1 in set1_sums:
set2_target_sum = target_sum - set1_sum
while set2_sum > set2_target_sum:
set2_sum, subsets2 = set2_sums.next()
if set2_sum == set2_target_sum:
for subset1, subset2 in product(subsets1, subsets2):
yield subset1 + subset2
except StopIteration: # done iterating over set2_sums
pass
itertools.permutation
返回一个迭代器,而不是所有置换的列表。因此,您可以逐步遍历结果并在第k个匹配处停止。@TimPietzcker-仍然无法提高计算复杂度,除非迭代器返回按某个特定值排序的结果(在本例中为权重之和),我猜它不会。您的评论很好@fylow,但别忘了检查答案左侧的复选标记,用一些冷酷、刻薄的分数来量化你的感激之情!明白了@PaulMcGuire,我对堆栈溢出不是很熟悉。如果集合的大小至少为20,那么上面的操作将需要相当长的时间。对于一组N个权重,复杂度为O(2^N)。对于30码左右的套装,这永远不会结束。。。我发布了一个简单的解决方案,它使用空间/时间权衡来处理更大的权重集。我在示例中犯了一个错误,导致它错过了('B','C')——现在已修复。
from collections import defaultdict
from itertools import chain, combinations, product
# taken from the docs of the itertools module
def powerset(iterable):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in xrange(len(s) + 1))
def gen_sums(weights):
"""Given a set of weights, generate a sum --> subsets mapping.
For each posible sum, this will create a list of subsets of weights
with that sum.
>>> gen_sums({'A':1, 'B':1})
{0: [()], 1: [('A',), ('B',)], 2: [('A', 'B')]}
"""
sums = defaultdict(list)
for weight_items in powerset(weights.items()):
if not weight_items:
sums[0].append(())
else:
keys, weights = zip(*weight_items)
sums[sum(weights)].append(keys)
return dict(sums)
def meet_in_the_middle(weights, target_sum):
"""Find subsets of the given weights with the desired sum.
This uses a simplified meet-in-the-middle algorithm.
>>> weights = {'A':2, 'B':1, 'C':3, 'D':2, 'E':1}
>>> list(meet_in_the_middle(weights, 4))
[('B', 'E', 'D'), ('A', 'D'), ('A', 'B', 'E'), ('C', 'B'), ('C', 'E')]
"""
# split weights into two groups
weights_list = weights.items()
weights_set1 = dict(weights_list[:len(weights)//2])
weights_set2 = dict(weights_list[len(weights_set1):])
# generate sum --> subsets mapping for each group of weights,
# and sort the groups in descending order
set1_sums = sorted(gen_sums(set1).items())
set2_sums = sorted(gen_sums(set2).items(), reverse=True)
# run over the first sorted list, meanwhile going through the
# second list and looking for exact matches
set2_sums = iter(set2_sums)
try:
set2_sum, subsets2 = set2_sums.next()
for set1_sum, subsets1 in set1_sums:
set2_target_sum = target_sum - set1_sum
while set2_sum > set2_target_sum:
set2_sum, subsets2 = set2_sums.next()
if set2_sum == set2_target_sum:
for subset1, subset2 in product(subsets1, subsets2):
yield subset1 + subset2
except StopIteration: # done iterating over set2_sums
pass