我们每天都会收到一些付款(Transaction
)。每个Transaction
都有ID
和Amount
。我们要求将大量这些交易与特定金额进行匹配。例如:
Transaction Amount
1 100
2 200
3 300
4 400
5 500
如果我们想要找到总计600的交易,你可以拥有多套(1,2,3),(2,4),(1,5)。
我找到了一个我已经改编的算法,其工作方式如下所述。 30次交易需要15ms。但交易数量平均约为740,最高接近6000. 这是一种更有效的搜索方式吗?
sum_up(TransactionList, remittanceValue, ref MatchedLists);
private static void sum_up(List<Transaction> transactions, decimal target, ref List<List<Transaction>> matchedLists)
{
sum_up_recursive(transactions, target, new List<Transaction>(), ref matchedLists);
}
private static void sum_up_recursive(List<Transaction> transactions, decimal target, List<Transaction> partial, ref List<List<Transaction>> matchedLists)
{
decimal s = 0;
foreach (Transaction x in partial) s += x.Amount;
if (s == target)
{
matchedLists.Add(partial);
}
if (s > target)
return;
for (int i = 0; i < transactions.Count; i++)
{
List<Transaction> remaining = new List<Transaction>();
Transaction n = new Transaction(0, transactions[i].ID, transactions[i].Amount);
for (int j = i + 1; j < transactions.Count; j++) remaining.Add(transactions[j]);
List<Transaction> partial_rec = new List<Transaction>(partial);
partial_rec.Add(new Transaction(n.MatchNumber, n.ID, n.Amount));
sum_up_recursive(remaining, target, partial_rec, ref matchedLists);
}
}
将Transaction
定义为:
class Transaction
{
public int ID;
public decimal Amount;
public int MatchNumber;
public Transaction(int matchNumber, int id, decimal amount)
{
ID = id;
Amount = amount;
MatchNumber = matchNumber;
}
}
答案 0 :(得分:1)
正如已经提到的,您的问题可以通过O(n*G)
中的伪多项式算法来解决,n
- 项数和G
- 您的目标总和。
第一部分问题:是否有可能实现目标总和G
。下面的伪/ python代码解决了它(在我的机器上没有C#):
def subsum(values, target):
reached=[False]*(target+1) # initialize as no sums reached at all
reached[0]=True # with 0 elements we can only achieve the sum=0
for val in values:
for s in reversed(xrange(target+1)): #for target, target-1,...,0
if reached[s] and s+val<=target: # if subsum=s can be reached, that we can add the current value to this sum and build an new sum
reached[s+val]=True
return reached[target]
这是什么想法?我们考虑值[1,2,3,6]
和目标总和7
:
0
。1
,并且必须选择是否采取。这留下了可能的总和{0,1}
。2
:导致可能的集合{0,1}
(不接受)+ {2,3}
(接受)。3
,我们可能会设置 a。,因为它不会取{0,1,2,3}
和 b。用于获取{3,4,5,6}
作为{0,1,2,3,4,5,6}
的可能总和。你的方法的不同之处在于有两种方法可以到达3
,你的递归将从那里开始两次(不需要)。一遍又一遍地计算基本相同的人员是你的方法的问题以及为什么提出的算法更好。
6
并将{0,1,2,3,4,5,6,7}
作为可能的总和。但是你还需要导致目标总和的子集,为此我们只记得采用哪个元素来实现当前的子总和。此版本返回一个子集,该子集产生目标总和,否则为None
:
def subsum(values, target):
reached=[False]*(target+1)
val_ids=[-1]*(target+1)
reached[0]=True # with 0 elements we can only achieve the sum=0
for (val_id,val) in enumerate(values):
for s in reversed(xrange(target+1)): #for target, target-1,...,0
if reached[s] and s+val<=target:
reached[s+val]=True
val_ids[s+val]=val_id
#reconstruct the subset for target:
if not reached[target]:
return None # means not possible
else:
result=[]
current=target
while current!=0:# search backwards jumping from predecessor to predecessor
val_id=val_ids[current]
result.append(val_id)
current-=values[val_id]
return result
作为另一种方法,您可以使用memoization加快当前解决方案,记住状态(subsum, number_of_elements_not considered)
是否有可能实现目标总和。但我想说标准的动态编程在这里不太容易出错。
答案 1 :(得分:0)
是
我目前无法提供完整的代码,但是在找到匹配项(O平方)之前,不要迭代每个事务列表两次,请尝试以下概念:
而不是O ^ 2,你可以将它降低到4 * O,这会在速度上产生明显的差异。
祝你好运!答案 2 :(得分:0)
动态编程可以有效地解决这个问题: 假设您有n个交易且最大交易金额为m。 我们可以用O(nm)的复杂度来解决它。
在Knapsack problem学习。 对于这个问题,我们可以为pre i事务定义子集的数量,加起来总和:dp [i] [sum]。 等式:
for i 1 to n:
dp[i][sum] = dp[i - 1][sum - amount_i]
dp [n] [sum]是你需要的数字,你需要添加一些技巧来获得所有子集。 块引用
答案 3 :(得分:0)
你在这里有一些实际的假设,可以通过聪明的分支修剪来实现蛮力:
以下是一些代码:
public static List<T[]> SubsetSums<T>(T[] items, int target, Func<T, int> amountGetter)
{
Stack<T> unusedItems = new Stack<T>(items.OrderByDescending(amountGetter));
Stack<T> usedItems = new Stack<T>();
List<T[]> results = new List<T[]>();
SubsetSumsRec(unusedItems, usedItems, target, results, amountGetter);
return results;
}
public static void SubsetSumsRec<T>(Stack<T> unusedItems, Stack<T> usedItems, int targetSum, List<T[]> results, Func<T,int> amountGetter)
{
if (targetSum == 0)
results.Add(usedItems.ToArray());
if (targetSum < 0 || unusedItems.Count == 0)
return;
var item = unusedItems.Pop();
int currentAmount = amountGetter(item);
if (targetSum >= currentAmount)
{
// case 1: use current element
usedItems.Push(item);
SubsetSumsRec(unusedItems, usedItems, targetSum - currentAmount, results, amountGetter);
usedItems.Pop();
// case 2: skip current element
SubsetSumsRec(unusedItems, usedItems, targetSum, results, amountGetter);
}
unusedItems.Push(item);
}
我已经针对100k输入运行它,产生大约1k的结果导致25毫安以下,所以它应该能够轻松处理你的740案例。