4

アプリケーションで必要な次の数学の問題があり、近似ではなく最適な解を見つける効率的な方法があるかどうか疑問に思っています。

  1. 正と負の値のリストがあります。
  2. これらの値の合計は範囲 (x, y) 内にあります。
  3. 残りの値の合計が範囲内に収まるように、除外できる値の最大数を知りたいです。

例:

Values: -10, -5, -2, 7, 9, 15
Sum: 14
Range: (10, 18)

Eliminate -2 => SUM = 16
Eliminate -5 => SUM = 21
Eliminate 7 => SUM = 14
Eliminate -10 => SUM = 24
Eliminate 9 => SUM = 15

15 を削除すると、SUM = 0 になり、範囲外になります。5 つの値が削除されました。

一方、15 を削除することから始めて、次に -10、-5、-2 を削除すると、4 つの値しか削除できません。

考えられるすべての組み合わせを単純に試すアルゴリズムを書いたことがありますが、値が 25 以上になると、そのパフォーマンスは急速に低下します。100 ~ 200 の値の場合、10 分の 1 秒で結果が必要です。

現在、絶対値に基づいて小さい値から大きい値に値を並べ替え、合計が範囲内になくなるまで値を 1 つずつ削除します。明らかに、常に最適なソリューションが得られるとは限りません。

これがこの種の質問に適切な場所ではなく、別のフォーラムを参照できる場合は、それも役に立ちます。

4

3 に答える 3

3

私はこれを逆に実行したくなりますが、許可されているかどうかはわかりません (私のコメントを参照してください)。

したがって、値を 1 つずつ削除する代わりに、合計が範囲内にある最小のサブリストを見つけましょう!

問題があります-サブセット合計の問題はnp-completeであるため、このアプローチもそうです。(範囲が 0 である状況を想像してみてください。それは同じ問題です。)

この問題を O(2 N/2 ) で解く既知のアルゴリズムがあります。いくつかの Python コードのモックアップを作成しますが、それまでの間、ウィキペディアのページが役立つはずです。範囲内で最小のリストを見つけたいので、明らかに少し変更する必要があります。

基本的に、リストをそれぞれ長さ N/2 の 2 つの任意のサブリストに分割します (リストには N 個の要素があります)。次に、各リストにすべてのサブセットを生成し、それらの合計を計算します。(ここでは、サブセットとその合計を辞書に保存するので、どの数値が残っているかがわかります。最小のものだけを見つけたいので、より小さいものと同じ合計を持つすべてのサブセットも削除します。)これらのリストを並べ替え、範囲内に収まる合計がすべて見つかるまで、順方向と逆方向に実行します。最後に、どれが最も少ない要素を含んでいるかを調べれば、準備完了です!

最終的なリストが範囲内にある限り、ルールに違反する排除を行うことが許可されている場合は、この質問を確認してください

編集:ここにいくつかのPythonがあります。それは:

  • テストされていない

  • Python、特に高速ではない

  • 明らかに最適ではない

  • リファクタリングが急務

しかし、一般的な考え方としては、取得できる最速のアルゴリズムだと思います。もっと速いコンセプトに興味があります!

>>> from itertools import combinations, chain
>>> 
>>> available = [-10, -5, -2, 7, 9, 15]
>>> target = (10, 18)
>>> 
>>> 
>>> 
>>> def powerset(iterable): # from https://stackoverflow.com/questions/374626/how-can-i-find-all-the-subsets-of-a-set-with-exactly-n-elements
...     xs = list(iterable)
...     # note we return an iterator rather than a list
...     return chain.from_iterable(combinations(xs, n) for n in range(len(xs)+1))
... 
>>> 
>>> def getMinList(available, target):
...     middleIndex = len(available)/2
...     l1 = available[:middleIndex]
...     l2 = available[middleIndex:]
...     dict1 = {}
...     dict2 = {}
...     for subset in powerset(l1): # reverse so only the smallest subsets are used.
...         total = sum(subset)
...         if total not in dict1:
...             dict1[total] = subset
...     for subset in powerset(l2):
...         total = sum(subset)
...         if total not in dict2:
...             dict2[total] = subset
...     sortedDict1 = sorted(dict1.iteritems())
...     sortedDict2 = sorted(dict2.iteritems())
...     resultList = ()
...     minValues = middleIndex * 2
...     for k1, v1 in sortedDict1:
...         for k2, v2 in reversed(sortedDict2):
...             sumOfSubsets = k1 + k2
...             if sumOfSubsets <= target[1] and sumOfSubsets >= target[0]:
...                 newTuple = v1 + v2
...                 lenNewTuple = len(newTuple)
...                 if (lenNewTuple) < minValues:
...                     resultList = ((sumOfSubsets, newTuple))
...                     minValues = lenNewTuple
...     return resultList
... 
>>> getMinList(available, target)
(15, (15,))
>>> 
>>> target = (10, 10)
>>> 
>>> getMinList(available, target)
(10, (-5, 15))
>>> 
>>> target = (19, 22)
>>> 
>>> getMinList(available, target)
(22, (7, 15))
于 2013-08-08T17:52:00.103 に答える
1

動的プログラミング (メモ化によって実装) を使用すると、以下を使用できます。

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        return self.memo[args]        

def maxsubset(values, min_sum, max_sum):
    target_range = range(min_sum, max_sum+1)

    @Memoize
    def maxsubsetsize(target_sum, current_value_index=len(values)-1):
        if current_value_index < 0:
            if target_sum == 0:
                return 0
            else:
                return float("-inf")

        withit = maxsubsetsize(target_sum - values[current_value_index], current_value_index-1) + 1
        without = maxsubsetsize(target_sum, current_value_index-1)
        return max(withit, without)

    result_sum = max(target_range, key=maxsubsetsize)
    setsize = maxsubsetsize(result_sum)

    result = []
    for i in reversed([x-1 for x in xrange(len(values))]):
        s = maxsubsetsize(result_sum, i)
        if s < setsize:
            result.append(values[i+1])
            setsize -= 1
            result_sum -= values[i+1]

    return result

使用法:

>>> values = [-10, -5, -2, 7, 9, 15]
>>> min_sum = 10
>>> max_sum = 18

>>> xs = maxsubset(values, min_sum-sum(values), max_sum-sum(values))
>>> print xs
[9, 7, -2, -5, -10]
>>> print "sum:", sum(xs)
-1

特定の合計に到達可能かどうかを確認する追加のチェックを追加できます。使用可能なすべての負の値は合計の下限を示し、使用可能なすべての正の値は上限を示します。

于 2013-08-09T10:38:26.803 に答える