假设一群朋友在一个月内分担了一些费用,并且需要在最后偿还债务。每个朋友都有他们应该给予/接收的一定数量的钱(债务和应收款项之和为零),但是所有事情都必须通过直接转账(无中央汇款,只有一个人到另一个人的钱)来解决。有成本。
例如,假设A,B和C这3个人。
每笔交易的费用可以通过以下矩阵来描述(行中的人支付列中的人)。
鉴于这些成本,最佳解决方案将是
这将结算所有交易成本为2的债务。我如何将其概括化?
我所能找到的只是简化链条债务(人A欠人B欠人C,所以人A欠人C)。
我找到的最接近的是this,但没有交易费用。
背景故事(如果有人感兴趣):
我们住在一个有8人的房子里,每个月我们用自己的钱支付账单,并在电子表格中进行注释,因此在月底,我们公平地分担了费用。但是,我们在不同的银行有帐户,其中一些帐户需要向其他银行转帐的费用,因此我们宁愿保留同一银行的交易。
答案 0 :(得分:1)
我找到了另一个更简单的解决方案。我们仍在谈论与转移金额成比例的转移成本。您可以建立一个与人一样多的节点的简单图形,然后运行网络单纯形算法。 Python示例:
import networkx as nx
G = nx.DiGraph()
G.add_node('A', demand=-100) # A has to send 100
G.add_node('B', demand=50) # B will receive 50
G.add_node('C', demand=50) # C will receive 50
G.add_edge('A', 'B', weight=1)
G.add_edge('A', 'C', weight=5)
G.add_edge('B', 'A', weight=1)
G.add_edge('B', 'C', weight=1)
G.add_edge('C', 'A', weight=2)
G.add_edge('C', 'B', weight=2)
print nx.network_simplex(G)
输出(150, {'A': {'C': 0, 'B': 100}, 'C': {'A': 0, 'B': 0}, 'B': {'A': 0, 'C': 50}})
答案 1 :(得分:0)
万一银行收取了转账金额的一部分,您的任务就是找到min-cost max flow。
您的图表应具有5层。
(direction)
| Source layer: S
| / | \
| Exchange: A --- B --- C (complete graph)
| \ | /
V Sink : T
源连接到节点A,B,C ... S的容量-> A是A必须支付的金额,如果A没有钱则为0。边缘的成本为0。
在交换层A,B,C ...相互连接(完整图)。
A-> B的容量是无限的,成本是将$ 1从A转移到B后必须支付的费用(所有对相同)。
节点已连接到接收器。 A->接收器的容量是A会收到多少,如果A没有收到钱,则容量为0。边缘的成本为0。
在上图上从根源到根宿运行最小成本最大流量算法,例如Edmonds-Karp +循环取消。您最好找到一个库(例如C ++的Boost Graph Library),而不是自己实现算法。
答案 2 :(得分:0)
@j_random_hacker在评论中解释了此问题很困难之后,我失去了提出一个漂亮解决方案的全部希望,并着手做一个可行的解决方案。
在@ user3386109的建议之后,也在评论中,我基于minpath解决了这个问题。因此,我首先使用[Floyd-Warshall算法]来找出每人从一个人到另一个人赚钱的最低成本。这产生了用于将钱从行中的人转移到列中的人的最小成本的矩阵。我还应用了修改(路径重建部分的Wikipedia文章中提供了此修改),以获取产生下一个节点的矩阵(下一跳,如果要与该列中的人员联系,您必须实际汇款的人)最低费用),以便行中的人将钱转给列中的人。
初始化矩阵的示例以及运行算法后的结果(更改的重要元素带有红点):
然后,我决定进行简单的分支定界递归。每个递归调用都会收到债务清单,到目前为止所有事务的矩阵以及达到此状态的成本。它必须返回TODO。我们为找到的最佳解决方案保留一个全局变量,并在递归调用开始时检查到达此状态的成本是否已经比全局最佳成本差,如果是,则返回“无限”成本以表明不是我们需要考虑的解决方案。
然后我们在债务清单中选择一个欠款的人,并为每个必须收款的人创建债务清单和交易矩阵的副本,并模拟这两个人之间最大笔钱的交易(如果A必须支付100,但C仅需要获得50,最高为50)。通过将这两个人的最小路径中的所有交易增加所转移的金额,可以修改交易矩阵。如果增加以前为零的元素,则成本会增加。然后,我们称为递归。如果债务清单达到0,则找到解决方案,更新了全球最低成本,返回的成本为0。
在以前的版本中,我为每对欠款/收款人生成了一个递归。由于交易顺序无关紧要,而且还没有结清的任何债务,我们都会以较低的递归级别来处理,这产生了可怕的性能,并且证明是不必要的。
该算法看似正确,但是仍然存在问题!
目前的算法是使A,B,C各自转账至D。实际的最佳方法是选择A,B或C中的一种,以便仅一次付款即可将所有资金转入D。
在此示例中,人A,B和C都具有相同的极高成本转移到D,而他们所有人向D赚钱的下一跳就是直接去D。但是,最佳解决方案就是让每个人都将所有资金转移给一个人,然后一次性将其全部转移给D。该算法无法识别出由于某人已经向D人进行了转移,因此该交易的成本为0,因此我们应该使用此新路径。为了解决此问题,我在递归中包括一个成本矩阵和一个路径矩阵,在递归开始时,我们将在此递归分支中已进行的所有转移成本设置为0,然后运行Floyd-Warshall算法再次。递归使用这些矩阵而不是全局矩阵并将其传递。当然,这意味着将复杂度乘以V ^ 3,但这是我发现解决此问题的唯一方法。
该算法现在似乎正在运行,但是我可能会继续尝试改进它,尤其是在代码可读性方面。完整的代码可在以下位置获得: My gitlab project, inside the calculations folder
很抱歉,回答很长且发布得很晚,但是我发现重要的是要对我的工作进行彻底记录。