以各种组合添加值

时间:2009-01-05 23:58:56

标签: algorithm optimization

不知道如何最好地解释它,除了使用示例......

想象一下,客户有10张未结清的发票,有一天他们会为您提供支票,但不要告诉您它的发票。

返回可以产生所需总数的所有可能值组合的最佳方法是什么?


我目前的想法是一种强力方法,它涉及使用自我调用函数,该函数运行所有可能性(see current version)。

例如,有3个数字,有15种方法可以将它们加在一起:

  1. A
  2. A + B
  3. A + B + C
  4. A + C
  5. A + C + B
  6. B + A
  7. B + A + C
  8. B + C
  9. B + C + A
  10. C
  11. C + A
  12. C + A + B
  13. C + B
  14. C + B + A
  15. 如果删除重复项,请提供7种独特的方法将它们添加到一起:

    1. A
    2. A + B
    3. A + B + C
    4. A + C
    5. B + C
    6. C
    7. 然而,这种情况在您:

      之后会崩溃
      • 15个数字(32,767种可能性/约2秒计算)
      • 16个数字(65,535种可能性/约6秒计算)
      • 17个数字(131,071种可能性/约9秒计算)
      • 18个数字(262,143种可能性/约20秒计算)

      在哪里,我希望这个功能能够处理至少100个数字。

      那么,关于如何改进它的任何想法? (用任何语言)

8 个答案:

答案 0 :(得分:2)

这是subset sum problem的一个非常常见的变体,而且确实很难。链接页面上关于伪多项式时间动态规划解决方案的部分就是您所追求的。

答案 1 :(得分:2)

这是严格的可能性数量,不考虑重叠。我不确定你想要什么。

考虑任何单个值可以同时出现的状态 - 可以包含或排除。这是两个不同的状态,因此所有n个项目的不同状态的数量将是2 ^ n。然而,有一个国家不想要;该状态是指所有数字都不包括在内。

因此,对于任何n个数,组合的数量等于2 ^ n-1。

def setNumbers(n): return 2**n-1

print(setNumbers(15))

这些发现与组合和排列密切相关。


但是,相反,我认为你可能在告诉我们是否给出了一组值,它们的任何组合总和为k。对于这个比尔,蜥蜴指出了你正确的方向。

接下来,请记住我还没有阅读整篇维基百科的文章,我在Python中提出了这个算法:

def combs(arr):
    r = set()

    for i in range(len(arr)):
        v = arr[i]
        new = set()

        new.add(v)
        for a in r: new.add(a+v)
        r |= new

    return r


def subsetSum(arr, val):
    middle = len(arr)//2

    seta = combs(arr[:middle])
    setb = combs(arr[middle:])

    for a in seta:
        if (val-a) in setb:
            return True

    return False

print(subsetSum([2, 3, 5, 8, 9], 8))

基本上算法的工作原理如下:

  1. 将列表拆分为两个大约一半长度的列表。 [O(N)]
  2. 查找子集和的集合。 [O(2 n / 2 n)]
  3. 循环通过第一组最多2个 floor(n / 2) -1值,看第二组中的另一个值是否总计为k。 [O(2 n / 2 n)]
  4. 所以我认为总的来说它在O(2 n / 2 n)中运行 - 仍然相当缓慢但更好。

答案 2 :(得分:1)

听起来像bin packing problem。那些是NP完全的,即几乎不可能为大型问题集找到完美的解决方案。但是你可以使用启发式算法,这可能适用于您的问题,即使它不是严格意义上的垃圾箱问题。

答案 3 :(得分:1)

这是类似问题的变体。

但你可以通过创建一个n位的计数器来解决这个问题。其中n是数字的数量。然后你从000到111(n 1)计数,每个数字1相当于一个可用的数字:

001 = A
010 = B
011 = A+B
100 = C
101 = A+C
110 = B+C
111 = A+B+C

(但这不是问题,嗯,我把它作为目标)。

答案 4 :(得分:1)

这不是严格意义上的装箱问题。这是价值的组合可能产生另一个价值。

这更像是变革问题,它有一堆文件详细说明如何解决它。谷歌在这里指出了我:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.57.3243

答案 5 :(得分:0)

我不知道它在实践中的效果是多少,因为这个过于简单的案例有许多例外,但这是一个想法:

在完美的世界中,发票将支付到某一点。人们将支付A,或A + B或A + B + C,但不支付A + C - 如果他们已收到发票C,那么他们已经收到发票B.在完美的世界中,问题不在于找到一个组合,而是在一条线上找到一个点。

您可以按照发布日期的顺序遍历未完成的发票,而只需将每个发票金额添加到与目标数字进行比较的运行总计中,而不是强制执行发票总额的每个组合。

回到现实世界中,这是一个非常快速的检查,你可以在进入沉重的数字运算或追逐它们之前做。任何点击都是奖励:)

答案 6 :(得分:0)

这是Subset Sums问题的精确整数解的优化的面向对象版本(Horowitz,Sahni 1974)。在我的笔记本电脑上(这没什么特别的)这个vb.net类每秒解决1900个子集(20个项目):

Option Explicit On

Public Class SubsetSum
    'Class to solve exact integer Subset Sum problems'
    ''
    ' 06-sep-09 RBarryYoung Created.'
    Dim Power2() As Integer = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32764}
    Public ForceMatch As Boolean
    Public watch As New Stopwatch
    Public w0 As Integer, w1 As Integer, w1a As Integer, w2 As Integer, w3 As Integer, w4 As Integer


    Public Function SolveMany(ByVal ItemCount As Integer, ByVal Range As Integer, ByVal Iterations As Integer) As Integer
        ' Solve many subset sum problems in sequence.'
        ''
        ' 06-sep-09 RBarryYoung Created.'
        Dim TotalFound As Integer
        Dim Items() As Integer
        ReDim Items(ItemCount - 1)

        'First create our list of selectable items:'
        Randomize()
        For item As Integer = 0 To Items.GetUpperBound(0)
            Items(item) = Rnd() * Range
        Next

        For iteration As Integer = 1 To Iterations
            Dim TargetSum As Integer
            If ForceMatch Then
                'Use a random value but make sure that it can be matched:'

                ' First, make a random bitmask to use:'
                Dim bits As Integer = Rnd() * (2 ^ (Items.GetUpperBound(0) + 1) - 1)

                ' Now enumerate the bits and match them to the Items:'
                Dim sum As Integer = 0
                For b As Integer = 0 To Items.GetUpperBound(0)
                    'build the sum from the corresponding items:'
                    If b < 16 Then
                        If Power2(b) = (bits And Power2(b)) Then
                            sum = sum + Items(b)
                        End If
                    Else
                        If Power2(b - 15) * Power2(15) = (bits And (Power2(b - 15) * Power2(15))) Then
                            sum = sum + Items(b)
                        End If
                    End If
                Next
                TargetSum = sum

            Else
                'Use a completely random Target Sum (low chance of matching): (Range / 2^ItemCount)'
                TargetSum = ((Rnd() * Range / 4) + Range * (3.0 / 8.0)) * ItemCount
            End If

            'Now see if there is a match'
            If SolveOne(TargetSum, ItemCount, Range, Items) Then TotalFound += 1
        Next

        Return TotalFound
    End Function

    Public Function SolveOne(ByVal TargetSum As Integer, ByVal ItemCount As Integer _
                            , ByVal Range As Integer, ByRef Items() As Integer) As Boolean
        ' Solve a single Subset Sum problem:  determine if the TargetSum can be made from'
        'the integer items.'

        'first split the items into two half-lists: [O(n)]'
        Dim H1() As Integer, H2() As Integer
        Dim hu1 As Integer, hu2 As Integer
        If ItemCount Mod 2 = 0 Then
            'even is easy:'
            hu1 = (ItemCount / 2) - 1 : hu2 = (ItemCount / 2) - 1
            ReDim H1((ItemCount / 2) - 1), H2((ItemCount / 2) - 1)
        Else
            'odd is a little harder, give the first half the extra item:'
            hu1 = ((ItemCount + 1) / 2) - 1 : hu2 = ((ItemCount - 1) / 2) - 1
            ReDim H1(hu1), H2(hu2)
        End If

        For i As Integer = 0 To ItemCount - 1 Step 2
            H1(i / 2) = Items(i)
            'make sure that H2 doesnt run over on the last item of an odd-numbered list:'
            If (i + 1) <= ItemCount - 1 Then
                H2(i / 2) = Items(i + 1)
            End If
        Next

        'Now generate all of the sums for each half-list:   [O( 2^(n/2) * n )]  **(this is the slowest step)'
        Dim S1() As Integer, S2() As Integer
        Dim sum1 As Integer, sum2 As Integer
        Dim su1 As Integer = 2 ^ (hu1 + 1) - 1, su2 As Integer = 2 ^ (hu2 + 1) - 1
        ReDim S1(su1), S2(su2)

        For i As Integer = 0 To su1
            ' Use the binary bitmask of our enumerator(i) to select items to use in our candidate sums:'
            sum1 = 0 : sum2 = 0
            For b As Integer = 0 To hu1
                If 0 < (i And Power2(b)) Then
                    sum1 += H1(b)
                    If i <= su2 Then sum2 += H2(b)
                End If
            Next
            S1(i) = sum1
            If i <= su2 Then S2(i) = sum2
        Next

        'Sort both lists:   [O( 2^(n/2) * n )]  **(this is the 2nd slowest step)'
        Array.Sort(S1)
        Array.Sort(S2)

        ' Start the first half-sums from lowest to highest,'
        'and the second half sums from highest to lowest.'
        Dim i1 As Integer = 0, i2 As Integer = su2

        ' Now do a merge-match on the lists (but reversing S2) and looking to '
        'match their sum to the target sum:     [O( 2^(n/2) )]'
        Dim sum As Integer
        Do While i1 <= su1 And i2 >= 0
            sum = S1(i1) + S2(i2)
            If sum < TargetSum Then
                'if the Sum is too low, then we need to increase the ascending side (S1):'
                i1 += 1
            ElseIf sum > TargetSum Then
                'if the Sum is too high, then we need to decrease the descending side (S2):'
                i2 -= 1
            Else
                'Sums match:'
                Return True
            End If
        Loop

        'if we got here, then there are no matches to the TargetSum'
        Return False
    End Function

End Class

以下是与之相关的表单代码:

Public Class frmSubsetSum

    Dim ssm As New SubsetSum

    Private Sub btnGo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGo.Click
        Dim Total As Integer
        Dim datStart As Date, datEnd As Date
        Dim Iterations As Integer, Range As Integer, NumberCount As Integer

        Iterations = CInt(txtIterations.Text)
        Range = CInt(txtRange.Text)
        NumberCount = CInt(txtNumberCount.Text)

        ssm.ForceMatch = chkForceMatch.Checked

        datStart = Now

        Total = ssm.SolveMany(NumberCount, Range, Iterations)

        datEnd = Now()

        lblStart.Text = datStart.TimeOfDay.ToString
        lblEnd.Text = datEnd.TimeOfDay.ToString
        lblRate.Text = Format(Iterations / (datEnd - datStart).TotalMilliseconds * 1000, "####0.0")

        ListBox1.Items.Insert(0, "Found " & Total.ToString & " Matches out of " & Iterations.ToString & " tries.")
        ListBox1.Items.Insert(1, "Tics 0:" & ssm.w0 _
                                & " 1:" & Format(ssm.w1 - ssm.w0, "###,###,##0") _
                                & " 1a:" & Format(ssm.w1a - ssm.w1, "###,###,##0") _
                                & " 2:" & Format(ssm.w2 - ssm.w1a, "###,###,##0") _
                                & " 3:" & Format(ssm.w3 - ssm.w2, "###,###,##0") _
                                & " 4:" & Format(ssm.w4 - ssm.w3, "###,###,##0") _
                                & ", tics/sec = " & Stopwatch.Frequency)
    End Sub
End Class

如果您有任何问题,请与我们联系。

答案 7 :(得分:0)

对于记录,这里有一些相当简单的Java代码,它使用递归来解决这个问题。它针对简单而不是性能进行了优化,尽管有100个元素,但它似乎非常快。使用1000个元素需要更长的时间,因此如果您处理更大量的数据,您可以更好地使用更复杂的算法。

public static List<Double> getMatchingAmounts(Double goal, List<Double> amounts) {
    List<Double> remaining = new ArrayList<Double>(amounts);

    for (final Double amount : amounts) {
        if (amount > goal) {
            continue;
        } else if (amount.equals(goal)) {
            return new ArrayList<Double>(){{ add(amount); }};
        }

        remaining.remove(amount);

        List<Double> matchingAmounts = getMatchingAmounts(goal - amount, remaining);
        if (matchingAmounts != null) {
            matchingAmounts.add(amount);
            return matchingAmounts;
        }
    }

    return null;
}