[英]Multiple subset sum calculation
我有 2 个集合,集合 A 包含一组随机数,集合 B 的元素是集合 A 子集的总和。
例如,
A = [8, 9, 15, 15, 33, 36, 39, 45, 46, 60, 68, 73, 80, 92, 96]
B = [183, 36, 231, 128, 137]
我想用这样的数据找到哪个数字是哪个子集的总和。
S = [[45, 46, 92], [36], [8, 15, 39, 73, 96], [60, 68], [9, 15, 33, 80]]
我能够用 python 编写非常愚蠢的蛮力代码。
class SolvedException(BaseException):
pass
def solve(sums, nums, answer):
num = nums[-1]
for i in range(0, len(sums)):
sumi = sums[i]
if sumi == 0:
continue
elif sumi - num < 0:
continue
answer[i].append(num)
sums[i] = sumi - num
if len(nums) != 1:
solve(sums, nums[:-1], answer)
elif sumi - num == 0:
raise SolvedException(answer)
sums[i] = sumi
answer[i].pop()
try:
solve(B, A, [list() for i in range(0, len(B))])
except SolvedException as e:
print e.args[0]
这段代码适用于小数据,但计算我的数据(有 71 个数字和 10 个总和)需要十亿年的时间。
我可以使用一些更好的算法或优化。
对不起,我的英语不好,代码效率低下。
编辑:对不起,我意识到我没有准确描述问题。
由于A
中的每个元素都用于生成 B 的元素,因此sum(A) == sum(B)
此外,集合S
必须是集合A
分区。
这被称为子集和问题,它是一个众所周知的 NP 完全问题。 所以基本上没有有效的解决方案。 参见例如https://en.wikipedia.org/wiki/Subset_sum_problem
但是,如果您的数字 N 不是太大,则有一个伪多项式算法,使用动态规划:您从左到右阅读列表 A,并保留可行且小于 N 的总和列表。如果您知道数字对于给定的 A 是可行的,你可以很容易地得到那些对于 A + [a] 可行的。 因此,动态规划。 对于您在那里给出的大小问题,它通常足够快。
这是一个 Python 快速解决方案:
def subsetsum(A, N):
res = {0 : []}
for i in A:
newres = dict(res)
for v, l in res.items():
if v+i < N:
newres[v+i] = l+[i]
elif v+i == N:
return l+[i]
res = newres
return None
然后
>>> A = [8, 9, 15, 15, 33, 36, 39, 45, 46, 60, 68, 73, 80, 92, 96]
>>> subsetsum(A, 183)
[15, 15, 33, 36, 39, 45]
OP编辑后:
现在我正确理解了你的问题,我仍然认为你的问题可以有效地解决,只要你有一个有效的子集和求解器:我会在 B 上使用分而治之的解决方案:
但是,下面的 (71, 10) 问题对于我建议的动态编程解决方案来说是遥不可及的。
顺便说一句,这是您的问题的快速解决方案,不使用分治法,但包含我的动态求解器的正确改编以获得所有解决方案:
class NotFound(BaseException):
pass
from collections import defaultdict
def subset_all_sums(A, N):
res = defaultdict(set, {0 : {()}})
for nn, i in enumerate(A):
# perform a deep copy of res
newres = defaultdict(set)
for v, l in res.items():
newres[v] |= set(l)
for v, l in res.items():
if v+i <= N:
for s in l:
newres[v+i].add(s+(i,))
res = newres
return res[N]
def list_difference(l1, l2):
## Similar to merge.
res = []
i1 = 0; i2 = 0
while i1 < len(l1) and i2 < len(l2):
if l1[i1] == l2[i2]:
i1 += 1
i2 += 1
elif l1[i1] < l2[i2]:
res.append(l1[i1])
i1 += 1
else:
raise NotFound
while i1 < len(l1):
res.append(l1[i1])
i1 += 1
return res
def solve(A, B):
assert sum(A) == sum(B)
if not B:
return [[]]
res = []
ss = subset_all_sums(A, B[0])
for s in ss:
rem = list_difference(A, s)
for sol in solve(rem, B[1:]):
res.append([s]+sol)
return res
然后:
>>> solve(A, B)
[[(15, 33, 39, 96), (36,), (8, 15, 60, 68, 80), (9, 46, 73), (45, 92)],
[(15, 33, 39, 96), (36,), (8, 9, 15, 46, 73, 80), (60, 68), (45, 92)],
[(8, 15, 15, 33, 39, 73), (36,), (9, 46, 80, 96), (60, 68), (45, 92)],
[(15, 15, 73, 80), (36,), (8, 9, 33, 39, 46, 96), (60, 68), (45, 92)],
[(15, 15, 73, 80), (36,), (9, 39, 45, 46, 92), (60, 68), (8, 33, 96)],
[(8, 33, 46, 96), (36,), (9, 15, 15, 39, 73, 80), (60, 68), (45, 92)],
[(8, 33, 46, 96), (36,), (15, 15, 60, 68, 73), (9, 39, 80), (45, 92)],
[(9, 15, 33, 46, 80), (36,), (8, 15, 39, 73, 96), (60, 68), (45, 92)],
[(45, 46, 92), (36,), (8, 15, 39, 73, 96), (60, 68), (9, 15, 33, 80)],
[(45, 46, 92), (36,), (8, 15, 39, 73, 96), (15, 33, 80), (9, 60, 68)],
[(45, 46, 92), (36,), (15, 15, 60, 68, 73), (9, 39, 80), (8, 33, 96)],
[(45, 46, 92), (36,), (9, 15, 15, 39, 73, 80), (60, 68), (8, 33, 96)],
[(9, 46, 60, 68), (36,), (8, 15, 39, 73, 96), (15, 33, 80), (45, 92)]]
>>> %timeit solve(A, B)
100 loops, best of 3: 10.5 ms per loop
因此,对于这种规模的问题,速度相当快,尽管这里没有优化。
一个完整的解决方案,它计算所有的方式来做一个总和。 我使用整数作为速度和内存使用的特征集: 19='0b10011'
代表[A[0],A[1],A[4]]=[8,9,33]
。
A = [8, 9, 15, 15, 33, 36, 39, 45, 46, 60, 68, 73, 80, 92, 96]
B =[183, 36, 231, 128, 137]
def subsetsum(A,N):
res=[[0]]+[[] for i in range(N)]
for i,a in enumerate(A):
k=1<<i
stop=[len(l) for l in res]
for shift,l in enumerate(res[:N+1-a]):
n=a+shift
ln=res[n]
for s in l[:stop[shift]]: ln.append(s+k)
return res
res = subsetsum(A,max(B))
solB = [res[b] for b in B]
exactsol = ~-(1<<len(A))
def decode(answer):
return [[A[i] for i,b in enumerate(bin(sol)[::-1]) if b=='1'] for sol in answer]
def solve(i,currentsol,answer):
if currentsol==exactsol : print(decode(answer))
if i==len(B): return
for sol in solB[i]:
if not currentsol&sol:
answer.append(sol)
solve(i+1,currentsol+sol,answer)
answer.pop()
为了 :
solve(0,0,[])
[[9, 46, 60, 68], [36], [8, 15, 39, 73, 96], [15, 33, 80], [45, 92]]
[[9, 46, 60, 68], [36], [8, 15, 39, 73, 96], [15, 33, 80], [45, 92]]
[[8, 15, 15, 33, 39, 73], [36], [9, 46, 80, 96], [60, 68], [45, 92]]
[[9, 15, 33, 46, 80], [36], [8, 15, 39, 73, 96], [60, 68], [45, 92]]
[[9, 15, 33, 46, 80], [36], [8, 15, 39, 73, 96], [60, 68], [45, 92]]
[[15, 15, 73, 80], [36], [9, 39, 45, 46, 92], [60, 68], [8, 33, 96]]
[[15, 15, 73, 80], [36], [8, 9, 33, 39, 46, 96], [60, 68], [45, 92]]
[[45, 46, 92], [36], [15, 15, 60, 68, 73], [9, 39, 80], [8, 33, 96]]
[[45, 46, 92], [36], [9, 15, 15, 39, 73, 80], [60, 68], [8, 33, 96]]
[[45, 46, 92], [36], [8, 15, 39, 73, 96], [60, 68], [9, 15, 33, 80]]
[[45, 46, 92], [36], [8, 15, 39, 73, 96], [15, 33, 80], [9, 60, 68]]
[[45, 46, 92], [36], [8, 15, 39, 73, 96], [60, 68], [9, 15, 33, 80]]
[[45, 46, 92], [36], [8, 15, 39, 73, 96], [15, 33, 80], [9, 60, 68]]
[[15, 33, 39, 96], [36], [8, 15, 60, 68, 80], [9, 46, 73], [45, 92]]
[[15, 33, 39, 96], [36], [8, 9, 15, 46, 73, 80], [60, 68], [45, 92]]
[[15, 33, 39, 96], [36], [8, 15, 60, 68, 80], [9, 46, 73], [45, 92]]
[[15, 33, 39, 96], [36], [8, 9, 15, 46, 73, 80], [60, 68], [45, 92]]
[[8, 33, 46, 96], [36], [15, 15, 60, 68, 73], [9, 39, 80], [45, 92]]
[[8, 33, 46, 96], [36], [9, 15, 15, 39, 73, 80], [60, 68], [45, 92]]
请注意,当两个15
不在同一个子集中时,解决方案加倍。
它解决了唯一的解决方案问题:
A=[1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011,
1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023,
1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035,
1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047,
1048, 1049]
B=[5010, 5035, 5060, 5085, 5110, 5135, 5160, 5185, 5210, 5235]
在一秒钟内。 不幸的是,对于 (71,10) 问题,它还不够优化。
另一个,纯粹的动态编程精神::
@functools.lru_cache(max(B))
def solutions(n):
if n==0 : return set({frozenset()}) #{{}}
if n<0 : return set()
sols=set()
for i,a in enumerate(A):
for s in solutions(n-a):
if i not in s : sols.add(s|{i})
return sols
def decode(answer): return([[A[i] for i in sol] for sol in answer])
def solve(B=B,currentsol=set(),answer=[]):
if len(currentsol)==len(A) : sols.append(decode(answer))
if B:
for sol in solutions(B[0]):
if set.isdisjoint(currentsol,sol):
solve(B[1:],currentsol|sol,answer+[sol])
sols=[];solve()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.