Algorithm 找出集合中的哪些数字组合加起来等于给定的总数
我的任务是帮助一些会计师解决他们遇到的一个常见问题——给出交易清单和总存款,哪些交易是存款的一部分?例如,假设我有一个数字列表:Algorithm 找出集合中的哪些数字组合加起来等于给定的总数,algorithm,math,nested-loops,accounting,Algorithm,Math,Nested Loops,Accounting,我的任务是帮助一些会计师解决他们遇到的一个常见问题——给出交易清单和总存款,哪些交易是存款的一部分?例如,假设我有一个数字列表: 1.00 2.50 3.75 8.00 我知道我的存款总额是10.50,我很容易看出它是由8.00和2.50交易组成的。然而,考虑到100笔交易和数百万美元的存款,这很快就会变得更加困难 在测试蛮力解决方案(这需要很长时间才能实现)时,我有两个问题: 有了一个大约60个数字的列表,似乎可以为任何合理的总数找到十几个或更多的组合我期望一个组合可以满足我的总体需求,或者
1.00
2.50
3.75
8.00
我知道我的存款总额是10.50
,我很容易看出它是由8.00
和2.50
交易组成的。然而,考虑到100笔交易和数百万美元的存款,这很快就会变得更加困难
在测试蛮力解决方案(这需要很长时间才能实现)时,我有两个问题:
- 拿下列表中的下一项,看看把它加到你的跑步总量中是否会使你的总量与目标相符。如果是,则将当前链作为匹配项放在一边。如果它没有达到您的目标,请将其添加到您的跑步总数中,将其从明细金额列表中删除,然后再次调用此流程
谢谢你的帮助 它是一类类似于0-1背包问题的NP完全问题,可以通过动态规划在多项式时间内求解
但在算法结束时,您还需要检查总和是否符合您的要求。如果我正确理解您的问题,您有一组事务,您只想知道其中哪些可以包含在给定的总数中。因此,如果有4个可能的事务,那么有2^4=16个可能的集合需要检查。这个问题是,对于100个可能的事务,搜索空间有2^100=1267650600228229401496703205376个可能的组合要搜索。对于组合中的1000个潜在交易,它将增长到 1071508607186267320948425049060001181051404811705536074437503883703511249361224931983788156955812759467291755314682587145285692314043598457746985748574803345674824230985421074605623711418754182153647498358194126739876761655946077069145711964776767676766042316526286767656680376 必须测试的设置。暴力很难解决这些问题
相反,使用能够处理问题的解算器。但即使如此,我也不确定你是否能在不使用蛮力的情况下生成所有可能解决方案的完整枚举。背包问题的这种特殊情况称为。根据你的数据,你可以首先查看每个事务的美分部分。就像在您最初的示例中一样,您知道2.50必须是总数的一部分,因为它是唯一一组增加到50的非零分交易。C#version 设置测试:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
// subtotal list
List<double> totals = new List<double>(new double[] { 1, -1, 18, 23, 3.50, 8, 70, 99.50, 87, 22, 4, 4, 100.50, 120, 27, 101.50, 100.50 });
// get matches
List<double[]> results = Knapsack.MatchTotal(100.50, totals);
// print results
foreach (var result in results)
{
Console.WriteLine(string.Join(",", result));
}
Console.WriteLine("Done.");
Console.ReadKey();
}
}
如果重复小计,则会出现重复结果(预期效果)。实际上,您可能希望使用带有某个ID的小计元组,以便将其与数据关联起来。有一个便宜的Excel加载项可以解决此问题:
不是一个超级高效的解决方案,但这里有一个coffeescript实现
组合
返回列表中元素的所有可能组合
combinations = (list) ->
permuations = Math.pow(2, list.length) - 1
out = []
combinations = []
while permuations
out = []
for i in [0..list.length]
y = ( 1 << i )
if( y & permuations and (y isnt permuations))
out.push(list[i])
if out.length <= list.length and out.length > 0
combinations.push(out)
permuations--
return combinations
这里有一个例子
list = [7.2, 3.3, 4.5, 6.0, 2, 4.1]
total = 7.2 + 2 + 4.1
console.log(find_components(total, list))
返回
[7.2,2,4.1]
超级用户网站上发布的Excel Solver插件有一个很好的解决方案(如果您有Excel)查看背包问题。除非您有更多信息,否则将有多种解决方案。double
有64位,并且您的数字来自其中的一小部分,因此在60个元素中有许多解决方案,有2^60个子集的和映射到double。这正是它的本质,在阅读了关于它的维基文章之后。似乎已经有一些方法比我现在做的更有效,但我希望有一些捷径可以让我更节省搜索空间-也许可以识别哪些数字与集合中的其他数字相加,然后避免重复检查它们。谢谢你的链接!可能是数学上的一个小问题。。。给定一组4个金额,我们需要对包含1、2、3和4个金额的所有子集求和。这是C(4,r)的和,其中r从1到4变化。即C(4,1)+C(4,2)+C(4,3)+C(4,4)=(4)+(6)+(4)+(1)=152^4。无论如何,这仍然会导致暴力解决方案的失败。唯一的另一个子集是零情况,在零情况下,根本不使用任何事务。如果从搜索中排除该情况,那么在集合中只有1个元素更少,对于2 ^ n-1个集合要考虑。对于大n来说,这个差别对我来说似乎微不足道
combinations = (list) ->
permuations = Math.pow(2, list.length) - 1
out = []
combinations = []
while permuations
out = []
for i in [0..list.length]
y = ( 1 << i )
if( y & permuations and (y isnt permuations))
out.push(list[i])
if out.length <= list.length and out.length > 0
combinations.push(out)
permuations--
return combinations
find_components = (total, list) ->
# given a list that is assumed to have only unique elements
list_combinations = combinations(list)
for combination in list_combinations
sum = 0
for number in combination
sum += number
if sum is total
return combination
return []
list = [7.2, 3.3, 4.5, 6.0, 2, 4.1]
total = 7.2 + 2 + 4.1
console.log(find_components(total, list))