谁欠钱谁优化

时间:2010-12-29 13:48:40

标签: algorithm math optimization graph

假设你有n个人,每个人都欠钱。通常,应该可以减少需要进行的交易量。即如果X欠Y£4而Y欠X£8,那么Y只需要支付X£4(1个交易而不是2个)。

当X欠Y时,这变得更难,但Y欠欠Z的是谁。我可以看到你可以很容易地计算出一个特定的周期。当我把它想象成一个完全连通的图形时,它对我有帮助,边缘是每个人所欠的金额。

问题似乎是NP完全的,但是我可以使用什么样的优化算法来减少交易量?不一定非常有效,因为N对我来说很小。

编辑:

这个问题的目的是能够在会计系统中拥有一些可以在每个人登录时说出来的内容“你可以通过简单地向某人支付X金额来删除M笔交易金额,而其他人是Y金额”。因此,银行解决方案(尽管如果每个人都在同一时间支付,这是最佳的)在这里无法真正使用。

10 个答案:

答案 0 :(得分:6)

人们是否需要通过向某人​​支付他们实际欠个人钱的方式来清偿债务?如果没有,以下似乎很容易怀疑:

对于每个人,计算他们应该支付或应该收到的净额。

有欠钱的人支付净收入的人(欠款,金额)。在此之后,两个参与者中至少有一个没有任何欠款,不应该收到任何东西,因此可以从问题中删除。

假设我错过了某些内容,那么适用的约束(或严重错误)是什么?

答案 1 :(得分:4)

我创建了一个解决此问题的Android应用。您可以在旅行期间输入费用,甚至建议您“谁应该下次付款”。最后计算“谁应该向谁发送多少”。我的算法计算所需的最小交易数量,你可以设置“交易容忍度”,这可以进一步减少交易(你不关心1美元的交易)尝试一下,它叫做定居:

https://market.android.com/details?id=cz.destil.settleup

我的算法说明:

我有基本算法解决了n-1事务的问题,但它不是最优的。它的工作原理如下:从付款开始,我为每个成员计算余额。平衡就是他付出的代价,减去他应该付出的代价。我越来越平衡地对成员进行排序。然后我总是把最贫穷和最富有的人交易完成。其中至少有一个以零余额结束,并被排除在进一步计算之外。有了这个,交易数量不能比n-1差。它还最大限度地减少了交易中的金额。但它并不是最优的,因为它不会检测到可以在内部稳定的子组。

找到可以在内部解决的子群很难。我通过生成所有成员组合并检查子组中的余额总和是否等于零来解决它。我从2对开始,然后是3对......(n-1)对。可以使用组合生成器的实现。当我找到一个子组时,我使用上面描述的基本算法计算子组中的事务。对于每个找到的子组,都可以节省一笔交易。

解决方案是最佳的,但复杂性增加到O(n!)。这看起来很糟糕,但诀窍是现实中只有少数成员。我在Nexus One(1 Ghz处理器)上进行了测试,结果是:直到10名成员:< 100 ms,15名成员:1 s,18名成员:8 s,20名成员:55 s。因此,直到18名成员执行时间很好。 > 15个成员的解决方法可以仅使用基本算法(它快速且正确,但不是最佳)。

源代码:

源代码在报告中提供有关用捷克语编写的算法。源代码在最后,它是英文:

http://settleup.destil.cz/report.pdf

答案 2 :(得分:3)

任意提名一个人为银行家。

每个其他人将所有外发交易的总和减去收到的交易(因此存入或取款)转移给该人。

最多会有(n-1)个交易,这个交易非常小。它很快。这很简单。

鉴于转移资金的每个人都必须参与交易*,最好的情况是最差情况的两倍。**

*例外是银行家自己。快速优化是为了确保指定的银行家不是担任中立职位的人。

**进一步解释我的上限逻辑:

假设最优情况是A给B给1美元,C给D给1美元,E给中性= 2次交易。

然后根据这个逻辑,如果E是指定的银行家,A给E 1美元,E给B 1美元,C给E 1美元,E给1美元给D = 4次交易。

通过优化,确保您不为银行家选择中立的人,请选择A. A给出1美元给B,C给1美元给A. A给1美元给D = 3次交易。

答案 3 :(得分:2)

for each debt in debts
  debt.creditor.owed -= debt.amount
  debt.deptor.owed += debt.amount
end

for each person in persons
  if person.owed > 0 then
    deptors.add person
  else if person.owed < 0 then
    creditors.add person
  end
end

deptors.sort_by_owed_desc
creditor.sort_by_owed_asc

for each debtor in deptors
  while debtor.owed > 0
    creditor = creditors.top
    amount = min( debtor.owed, -creditor.owed)
    creditor.owed += amount
    debtor.owed -= amount
    if creditor.owed == 0 then
      creditors.remove_top
    end
    write debtor.name " owes " creditor.name " " amount "€"
  end
end

答案 4 :(得分:1)

考虑一下,我首先查看有向图中的每个循环,然后通过循环中最小边的值减少循环中的每个边,然后完全删除最小边。冲洗并重复。

答案 5 :(得分:1)

这是我使用的Python解决方案;这与Gunner的帖子一样,只有几行改动:

for i in N:
    for j in N:
        if i!=j and owes[i][j] > owes[j][i]:
            owes[i][j] -= owes[j][i]
            owes[j][i] = 0
for k in N:
    for i in N:
        for j in N:
            if k == i or i == j or k == j:
                continue
            if owes[j][k] > owes[i][j]:
                owes[i][k] += owes[i][j]
                owes[j][k] -= owes[i][j]
                owes[i][j] = 0;

努力享受。

您可以使用例如:

进行测试
owes = [[0,2,11], [4,0,7], [2,3,0]]
N = range(len(owes))

答案 6 :(得分:0)

我认为你需要构建一个不同的数据结构(一棵树,每次一个人都是根节点),它将检查每个人你可以“杀死”多少“交易”,而不是选择最好的一个,制作循环,并重新重建它。它不是o(N),我认为它是N ^ 2,但它不会给你最好的结果。这只是一种策略。

答案 7 :(得分:0)

使用Warshall算法可以解决这个问题。

for(i=0;i<n;i++)
  for(j=0;j<n;j++)
    if ( i!= j && owes[i][j] > owes[j][i] )
owes[i][j] -= (owes[i][j] - owes[j][i]), owes[j][i] = 0;

for(k=0;k<n;k++)
 for(i=0;i<n;i++)
  for(j=0;j<n;j++)
  {
if( k == i || i == j || k == j ) continue;
if ( owes[j][k] > owes[i][j] )
{
int diff = owes[j][k] - owes[i][j]; 
owes[i][j] = 0;
owes[i][k ] += diff;
owes[j][k] -= diff;
} 
}

算法完成后,所需的事务总数将是owes表中的肯定条目数。

我还没有验证算法是否可行,基于它可能有效的问题的性质。解是O(N ^ 3)。

答案 8 :(得分:0)

我认为你必须删除所有通过最小边缘值减少边缘的cicles和使用值0删除边缘。之后你将得到没有图形的图形。我认为你必须找到顶点,它们没有指向它们(人类只欠其他钱)。这个男人必须付钱,beacouse没有人为他们付钱。所以我的观点是你必须找到他们必须付钱的人。

答案 9 :(得分:0)

我有一个用matlab编写的问题的解决方案。它基于谁欠谁的矩阵。 (i,j)中的数字表示人j欠人数。 E.g。

B欠A 2 和A欠B 1

当然,在这种情况下,B应该只给A 1

是微不足道的

随着更多条目的变得越来越复杂。但是,根据我写的算法,我可以保证不会发生N-1次交易,其中N是这种情况下的人数2。

这是我写的代码。

function out = whooweswho(matrix)
%input sanitation
if ~isposintscalar(matrix)
    [N M] = size(matrix);
    if N ~= M
        error('Matrix must be square');
    end

    for i=1:N
        if matrix(N,N) ~= 0
            error('Matrix must have zero on diagonals');
        end
    end
else
    %construction of example matrix if input is a positive scalar
    disp('scalar input: Showing example of NxN matrix randomly filled');
    N = matrix;
    matrix = round(10*N*rand(N,N)).*(ones(N,N)-eye(N))
end

%construction of vector containing each persons balance
net = zeros(N,1);
for i=1:N
  net(i) = sum(matrix(i,:))-sum(matrix(:,i));
end

%zero matrix, so it can be used for the result
matrix = zeros(size(matrix));

%sum(net) == 0 always as no money dissappears. So if min(net) == 0 it
%implies that all balances are zero and we are done.
while min(net) ~= 0
  %find the poorest and the richest.
  [rec_amount reciever] = max(net);
  [give_amount giver] = min(net);
  %balance so at least one of them gets zero balance.
  amount =min(abs(rec_amount),abs(give_amount));
  net(reciever) = net(reciever) - amount;
  net(giver) = net(giver) + amount;
  %store result in matrix.
  matrix(reciever,giver) = amount;
end
%output equivalent matrix of input just reduced.
out = matrix;