不知道如何最好地解释它,除了使用示例......
想象一下,客户有10张未结清的发票,有一天他们会为您提供支票,但不要告诉您它的发票。
返回可以产生所需总数的所有可能值组合的最佳方法是什么?
我目前的想法是一种强力方法,它涉及使用自我调用函数,该函数运行所有可能性(see current version)。
例如,有3个数字,有15种方法可以将它们加在一起:
如果删除重复项,请提供7种独特的方法将它们添加到一起:
然而,这种情况在您:
之后会崩溃在哪里,我希望这个功能能够处理至少100个数字。
那么,关于如何改进它的任何想法? (用任何语言)
答案 0 :(得分:2)
这是subset sum problem的一个非常常见的变体,而且确实很难。链接页面上关于伪多项式时间动态规划解决方案的部分就是您所追求的。
答案 1 :(得分:2)
这是严格的可能性数量,不考虑重叠。我不确定你想要什么。
考虑任何单个值可以同时出现的状态 - 可以包含或排除。这是两个不同的状态,因此所有n个项目的不同状态的数量将是2 ^ n。然而,有一个国家不想要;该状态是指所有数字都不包括在内。
因此,对于任何n个数,组合的数量等于2 ^ n-1。
def setNumbers(n): return 2**n-1
print(setNumbers(15))
这些发现与组合和排列密切相关。
接下来,请记住我还没有阅读整篇维基百科的文章,我在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))
基本上算法的工作原理如下:
所以我认为总的来说它在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;
}