不排序的前K个子集总和 [英] Top K subset sum without sorting

查看:22
本文介绍了不排序的前K个子集总和的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给定大小为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个元素,这是最小的子集,然后通过关联一个或多个元素来找到获得下一个最小子集的方法,但同样有太多可供替换的选择。

推荐答案

我会这样解决这个问题:

  1. 对数组进行排序,让s为前k元素之和。
  2. 使用backtracking search生成等于s的SUM的所有子集。
  3. 使用branch-and-bound algorithm查找最小的s2 > s,使得存在一个总和等于s2的子集。
  4. 如果有这样的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较小时,这两种算法都不需要迭代到长列表的末尾。

这篇关于不排序的前K个子集总和的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆