问题和实现引自资料:https://en.wikipedia.org/wiki/Subset_sum_problem
子集和问题(英语:Subset sum problem),又称子集合加总问题,是计算复杂度理论和密码学中一个很重要的问题。问题可以描述为:给一个整数集合,问是否存在某个非空子集,使得子集内中的数字和为0。例:给定集合{−7, −3, −2, 5, 8},答案是YES,因为子集{−3, −2, 5}的数字和是0。这个问题是NP完全问题,且或许是最容易描述的NP完全问题。
一个等价的问题是:给一个整数集合和另一个整数s,问是否存在某个非空子集,使得子集中的数字和为s。子集合加总问题可以想成是背包问题的一个特例。
initialize a list S to contain one element 0.
for each i from 1 to N do
let T be a list consisting of xi + y, for all y in S
let U be the union of T and S
sort U
make S empty
let y be the smallest element of U
add y to S
for each element z of U in increasing order do
//trim the list by eliminating numbers close to one another
//and throw out elements greater than s
if y + cs/N < z ≤ s, set y = z and add z to S
if S contains a number between (1 − c)s and s, output yes, otherwise no
在wiki提供的解法上进行略微改造,会列出所有的子集结果
import time
if __name__ == '__main__':
x_list = [1150, 495, 995, 995, 995, 995, 100, 750, 3305, 75, 510, 3265, 2145, 1935, 140, 140, 15, 1330, 2800, 1250, 350, 850, 110]
target = 8270
error = 0.009
start_time = time.time()
S = [(0, [])]
for x in x_list:
T = [(x + y, y_list + [x]) for y, y_list in S]
U = T + S
U.sort(key=lambda a: a[0])
y, y_list = U[0]
S = [(y, y_list)]
for z, z_list in U:
if y + error * target / len(x_list) < z <= target:
y = z
S.append((z, z_list))
else:
while abs(S[-1][0] - target) < error:
print(S.pop())
print(time.time() - start_time)