你对这类集合中的项数有上限吗?如果你这样做,并且它最多是40,那么Wikipedia page on Knapsack中描述的"meet-in-the-middle" algorithm可以非常简单,并且比暴力计算的复杂度要低得多。在
注意:使用比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