给出一个大小为N的数组,以元素之和的升序打印所有大小为K (0<K<=N)
的子集
Array:
[6,8,3,9], N=4, K=3
Sorted Subsets:
[3, 6, 8] (sum=17)
[3, 6, 9] (sum=18)
[3, 8, 9] (sum=20)
[6, 8, 9] (sum=23)
我不需要整个排序列表,而需要前T个条目(T很小)。列出所有子集(nCk)并对它们进行排序对于大N来说将是非常昂贵的。有没有一种方法可以在不实际枚举所有子集的情况下获得前T个子集?我当时正在考虑选择最小的K个元素(这是最小的子集),然后找到一种方法来替换一个或多个元素来获取下一个最小的子集,但是替换的选择仍然太多。
答案 0 :(得分:5)
我会这样解决这个问题:
s
为前k
个元素的总和。s
的和的子集。s2 > s
,以使子集的总和等于s2
。s2
,请设置s = s2
并转到步骤2。否则,请停止。这是Python中的一种实现:它按总和的顺序懒惰地生成每个子集,因此您可以只提取它产生的第一个T子集。
def subsets_in_sum_order(lst, k):
"""
Returns a generator yielding the k-element subsets
of lst, in increasing order of their sum.
"""
lst = sorted(lst)
s = sum(lst[:k])
max_s = sum(lst[-k:])
while s is not None:
yield from subsets_of_sum(lst, k, s)
s = smallest_sum_in_range(lst, k, s+1, max_s)
def subsets_of_sum(lst, k, s, t=(), i=0):
"""
Returns a generator yielding tuples t + tt, where tt
is a k-element subset of lst[i:] whose sum is s. The
subsets are yielded in lexicographic order. The list
lst must be sorted.
"""
if k < 0:
raise ValueError()
elif k == 0:
if s == 0:
yield t
else:
for j in range(i, len(lst) - k + 1):
if sum(lst[j:j+k]) > s: break
v = lst[j]
s2 = s - v
t2 = t + (v,)
yield from subsets_of_sum(lst, k-1, s2, t2, j+1)
def smallest_sum_in_range(lst, k, min_s, max_s, i=0):
"""
Returns the smallest s such that min_s <= s <= max_s,
and there is a k-element subset of lst[i:] with sum s.
The list lst must be sorted.
Returns None if there is no such s.
"""
result = None
if k < 0:
raise ValueError()
elif k == 0:
if min_s <= 0:
result = 0
elif min_s <= max_s and sum(lst[-k:]) >= min_s:
for j in range(i, len(lst) - k + 1):
v = lst[j]
if k * v > max_s: break
s = smallest_sum_in_range(lst, k-1, min_s-v, max_s-v, j+1)
if s is not None:
s += v
result = s
max_s = s - 1
return result
示例:
>>> subsets = subsets_in_sum_order([1, 2, 3, 4, 5], 3)
>>> for subset in subsets:
... print(subset, sum(subset))
...
(1, 2, 3) 6
(1, 2, 4) 7
(1, 2, 5) 8
(1, 3, 4) 8
(1, 3, 5) 9
(2, 3, 4) 9
(1, 4, 5) 10
(2, 3, 5) 10
(2, 4, 5) 11
(3, 4, 5) 12
@ user3386109观察到,如果列表长度比您要生成的子集的数量大得多,则实际上并不需要整个列表,因为列表中较大的元素不会出现在列表中。前T个子集。前T个子集只能使用列表中的前T + k-1个元素,因此我们可以使用heapq.nsmallest
来稍微提高效率:
import heapq
from itertools import islice
def smallest_subsets(lst, k, num_subsets):
lst = heapq.nsmallest(num_subsets + k - 1, lst)
subsets = subsets_in_sum_order(lst, k)
return islice(subsets, num_subsets)
这使您不必对整个长度为N的列表进行排序。但是,回溯搜索和分支定界算法并不能从中受益很多,因为它们都已经使用了总和的边界来尽早消除分支。当T很小时,两者都不需要迭代到长列表的末尾。
答案 1 :(得分:2)
其中一种方法涉及动态编程。
首先,假设我们是否有一个像这样的数据结构:
for each count of elements to use
for each possible sum
for each starting index
count of ways to get there (with or without that starting index)
编写代码来填充它并不难。对于[6,8,3,9]
,您将得到类似这样的信息:
counts_by_count_by_sum_by_index = [
{ # empty sets
0: [1, 1, 1, 1]
},
{ # 1 element sets
3: [1, 1, 1, 0],
6: [1, 0, 0, 0],
8: [1, 1, 0, 0],
9: [1, 1, 1, 1],
},
{ # 2 element sets
9: [1, 0, 0, 0],
11: [1, 1, 0, 0],
12: [1, 1, 1, 0],
14: [1, 0, 0, 0],
15: [1, 0, 0, 0],
17: [1, 1, 0, 0],
},
{ # 3 element sets
17: [1, 0, 0, 0],
18: [1, 0, 0, 0],
20: [1, 1, 0, 0],
23: [1, 0, 0, 0],
},
{ # 4 element sets
26: [1, 0, 0, 0]
}
]
如果元素更多,则此数据结构可能会很大,但会以伪多项式的方式扩展。具体是O((size of elements) * (size of set) ^ 3)
。
使用这种数据结构,可以很容易地按和编写搜索,然后以字典顺序(通过使用的索引)的顺序递归查找解决方案。
如果需要,也可以找到百万分之一的解决方案,而不必生成先前的解决方案。