【问题标题】:Adding values in various combinations以各种组合添加值
【发布时间】:2013-06-26 22:05:46
【问题描述】:

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

想象一下,有一个客户有 10 张未结清的发票,有一天他们给你一张支票,但不告诉你这张支票的用途。

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


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

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

  1. 一个
  2. A + B
  3. A + B + C
  4. A + C
  5. A + C + B
  6. B
  7. B + A
  8. B + A + C
  9. B + C
  10. B + C + A
  11. C
  12. C + A
  13. C + A + B
  14. C + B
  15. C + B + A

如果您删除重复项,则为您提供 7 种将它们添加在一起的独特方法:

  1. 一个
  2. A + B
  3. A + B + C
  4. A + C
  5. B
  6. B + C
  7. C

但是,这种情况在你有以下情况后就会分崩离析:

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

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

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

【问题讨论】:

  • 如果这是一个项目欧拉问题,应该这样标记。
  • 我想你会发现企业对如何做到这一点有非常明确的看法,这使得这个问题没有实际意义,例如,要么将其应用于最长的未结发票,要么可能应用于具有完全匹配发票的发票.或以此为借口打电话提醒客户发票逾期。
  • @Cletus:正是我要说的。
  • @Cletus:在这种特殊情况下,我在支票背面找到了发票号码......但我认为这个问题很有趣,我想找到一个可能的解决方案。

标签: algorithm optimization


【解决方案1】:

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

【讨论】:

  • 我认为这几乎可以描述问题(不幸的是我的谷歌技能昨天让我失望了)......所以答案似乎是否定的,不可能在现实的时间框架内完美解决。
  • 如果您以前没有听说过一些问题的名称,Google 很难找到它们。我从来没有实现过 DP 解决方案,所以我不能说它在 100 个输入下的表现如何。可能值得一试。
  • 其实只有动态规划很难实现。二元解决方案非常易于实现(我已经在 SQL 中完成)并且非常高效,最多可处理大约 20 多个值。
【解决方案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. 将列表拆分为 2 个长度约为一半的列表。 [O(n)]
  2. 查找子集和的集合。 [O(2n/2 n)]
  3. 循环遍历第一组最多 2 个floor(n/2)-1 个值,查看第二组中的另一个值是否总计为 k。 [O(2n/2 n)]

所以我认为总体而言它的运行时间为 O(2n/2 n) - 仍然很慢但要好得多。

【讨论】:

  • 感谢 PythonPower,您对此绝对正确...直到 Gamecat 也进行了此观察(哇哦),我才注意到。因此,从编程的角度来看,可能会使代码更高效(一个循环?),但它仍然需要计算排列。
【解决方案3】:

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

【讨论】:

  • 我认为你是对的......不幸的是,因为它需要完全匹配,我不认为启发式将是解决方案(除非可以创建最终测试所有可能性的算法,但从最有可能开始)。
  • 我认为这是背包问题,已知它是NP完全的。 (问题是,我不记得背包问题是否是具有恒定值的 NP 完全问题,并且 xkcd 卡通也不是可靠的证据。)您可能想在 Google 上搜索“背包问题”或“整体背包问题”。跨度>
【解决方案4】:

这是一个类似问题的变体。

但是你可以通过创建一个 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

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

【讨论】:

  • 这是一种有趣的方式来识别需要执行的计算(不敢相信我之前没有发现)......从编程的角度来看,这意味着我可以删除自调用函数,并使用简单的“for”循环。
【解决方案5】:

严格来说,这不是一个装箱问题。这是值的组合可能产生另一个值。

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

【讨论】:

  • 我不认为这是一个改变的问题,因为这些值是未知的,也不一定是一个原子单位(可以多次使用)......例如: 2、4、5、9、15只能用“2 + 4 + 5”或“2 + 9”击中目标11。
【解决方案6】:

我不知道它在实践中多久会起作用,因为这种过于简单的情况有很多个例外,但这里有一个想法:

在一个完美的世界中,发票将被支付到某个点。人们将支付 A、A+B 或 A+B+C,但不会支付 A+C——如果他们已经收到发票 C,那么他们已经收到了发票 B。在完美世界中,问题不在于找到一个组合,而在于沿着一条直线找到一个点。

您可以按照开具日期的顺序遍历未结发票,然后将每个发票金额添加到与目标数字进行比较的运行总计中,而不是强制使用发票总额的每种组合。

回到现实世界,在开始繁重的数字运算或追逐它们之前,您可以做一个简单的快速检查。它获得的任何点击都是奖励:)

【讨论】:

    【解决方案7】:

    这里是子集和问题的精确整数解的优化面向对象版本(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
    

    如果您有任何问题,请告诉我。

    【讨论】:

      【解决方案8】:

      为了记录,这里有一些相当简单的 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;
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-22
        • 1970-01-01
        • 2014-04-06
        • 2020-08-15
        • 1970-01-01
        • 1970-01-01
        • 2012-07-23
        • 2020-04-20
        • 1970-01-01
        相关资源
        最近更新 更多