简体   繁体   English

简化债务加权有向图的算法

[英]Algorithm to simplify a weighted directed graph of debts

I've been using a little python script I wrote to manage debt amongst my roommates.我一直在使用我编写的一个小 python 脚本来管理室友之间的债务。 It works, but there are some missing features, one of which is simplifying unnecessarily complicated debt structures.它有效,但缺少一些功能,其中之一是简化不必要的复杂债务结构。 For example, if the following weighted directed graph represents some people and the arrows represent debts between them (Alice owes Bob $20 and Charlie $5, Bob owes Charlie $10, etc.):例如,如果下面的加权有向图表示一些人,箭头表示他们之间的债务(Alice 欠 Bob 20 美元,Charlie 5 美元,Bob 欠 Charlie 10 美元,等等):

图1

It is clear that this graph should be simplified to the following graph:很明显这个图应该简化为下图:

graph1-简化

There's no sense in $10 making its way from Alice to Bob and then from Bob to Charlie if Alice could just give it to Charlie directly.如果 Alice 可以直接将 10 美元交给 Charlie,那么 10 美元从 Alice 传给 Bob 再从 Bob 传给 Charlie 是没有意义的。

The goal, then, in the general case is to take a debt graph and simplify it (ie produce a new graph with the same nodes but different edges) such that那么,在一般情况下,目标是采用债务图并对其进行简化(即生成一个具有相同节点但不同边的新图),以便

  1. No node has edges pointing both in and out of it (no useless money changing hands)没有节点的边同时指向它的内外(没有无用的钱易手)
  2. All nodes have the same "flow" through them as they did in the original graph (it is identical in terms of where the money ends up).所有节点都具有与原始图中相同的“流量”(就资金最终流向而言是相同的)。

By "flow", I mean the value of all inputs minus all outputs (is there a technical term for this? I am no graph theory expert). “流”是指所有输入减去所有输出的值(有专门的术语吗?我不是图论专家)。 So in the example above, the flow values for each node are:所以在上面的例子中,每个节点的流量值是:

  • Bob: +10鲍勃:+10
  • Alice: -25爱丽丝:-25
  • Charlie: +15查理:+15

You can see that the first and second graphs have the same flow through each node, so this is a good solution.您可以看到第一张图和第二张图通过每个节点的流量相同,因此这是一个很好的解决方案。 There are some other easy cases, for example, any cycle can be simplified by removing the lowest valued edge and subtracting its value from all other edges.还有其他一些简单的情况,例如,可以通过删除最低值的边并从所有其他边中减去它的值来简化任何循环。

This:这个:

图2

should be simplified into this:应该简化为:

graph2-简化

I can't imagine that no one has studied this problem;我无法想象没有人研究过这个问题; I just don't know what terms to search for to find info on it (again, not a graph theory expert).我只是不知道要搜索什么术语来查找有关它的信息(同样,不是图论专家)。 I've been looking for several hours to no avail, so my question is this: what is an algorithm that will produce a simplification (new graph) according to the conditions specified above for any weighted directed graph?我一直在寻找几个小时无济于事,所以我的问题是:根据上面为任何加权有向图指定的条件,将产生简化(新图)的算法是什么?

Here is an academic paper which investigates this problem in great detail. 这是一篇学术论文,详细研究了这个问题。 There is also some sample code for the different algorithms in Section 8 towards the end. 到最后,第8节还提供了一些针对不同算法的示例代码。

Verhoeff, T. (2004). Verhoeff,T。(2004)。 Settling multiple debts efficiently : an invitation to computing science. 有效地解决多笔债务:计算机科学的邀请。 Informatics in Education, 3(1), 105-126. 教育信息学,3(1),105-126。

Simple algorithm 简单算法

You can find in O(n) how much money who is expecting to get or pay. 您可以在O(n)中找到期望获得或支付多少钱。 So you could simply create two lists, one for debit and the other for credit, and then balance the head of the two lists until they are empty. 因此,您可以简单地创建两个列表,一个用于借记,另一个用于贷记,然后平衡两个列表的标题,直到它们为空。 From your first example: 从第一个示例:

  • Initial state: Debit: (A: 25), Credit: (B: 15, C: 10) 初始状态:借方:(A:25),贷方:(B:15,C:10)
  • First transaction, A:15 -> B: Debit: (A: 10), Credit: (C: 10) 第一笔交易,A:15-> B:借方:(A:10),贷方:(C:10)
  • Second transaction, A:10 -> C: Debit: (), Credit: () 第二次交易,A:10-> C:借方:(),贷方:()

The transactions define the edges of your graph. 事务定义了图形的边缘。 For n persons involved, there will be at most n-1 transactions=edges. 对于涉及的n个人,最多将有n-1个交易=边。 In the beginning, the total length of both lists is n. 首先,两个列表的总长度为n。 In each step, at least one of the lists (debit/credit) gets shorter by one, and in the last both lists disappear at once. 在每个步骤中,至少一个列表(借方/贷方)变短了一个,最后,两个列表都立即消失了。

The issue is that, in general, this graph doesn't have to be similar to the original graph, which, as I get your intention, is a requirement. 问题在于,一般而言,该图不必与原始图相似,正如我所希望的那样,这是必需的。 (Is it? There are cases where the optimal solution consists of adding new edges. Imagine A owing B and B owing C the same amount of money, A should pay C directly but this edge is not in the graph of debts.) (是吗?在某些情况下,最佳解决方案包括添加新边。想象一下,由于A欠B且B欠C的钱相同,A应该直接付给C,但此边不在债务图中。)

Less transactions 交易减少

If the goal is just to construct an equivalent graph, you could search the creditor and debitor lists (as in the section above) for exact matches, or for cases where the sum of credit matches the debit of one person (or the other way round). 如果目标只是构造一个等效的图,则可以搜索债权人和借方清单(如上一节中)以查找完全匹配,或者在贷方总和与一个人的借方相匹配的情况下(或相反) )。 Look for bin packing . 寻找垃圾箱包装 For other cases you will have no other choice than splitting the flows, but even the simple algorithm above produces a graph which has one fewer edge than there are persons involved -- at most. 对于其他情况,除了拆分流之外,您别无选择,但是即使是上面的简单算法,生成的图的边缘也比所涉及的人员少(最多)。

EDIT : Thanks to j_random_hacker for pointing out that a solution with less than n-1 edges is possible iff there is a group of persons whose total debts matches the credit of another group of persons: Then, the problem can be split into two subproblems with a total cost of n-2 edges for the transaction graph. 编辑 :感谢j_random_hacker指出,如果有一组人的总债务与另一组人的信用相匹配,则可以解决少于n-1条边的问题:然后,该问题可以分为两个子问题:交易图的n-2条边的总成本。 Unfortunately, the subset sum problem is NP-hard. 不幸的是, 子集和的问题是NP难的。

A flow problem? 流量有问题吗?

Perhaps this also can be transformed to a min-cost flow problem . 也许这也可以转化为最小成本流动问题 If you want just to simplify your original graph, you construct a flow on it, the edge capacities are the original amounts of debit/credit. 如果只想简化原始图形,则在其上构造一个流程,边容量为原始借方/贷方金额。 The debitors serve as inflow nodes (through a connector node which serves all debitors with edges of capacity that equals their total debt), the creditors are used as outflow nodes (with a similar connector node). 借方用作流入节点(通过连接器节点,该节点为所有借方提供容量等于其总债务的借方),债权人用作流出节点(具有类似的连接器节点)。

If you want to minimize the number of transactions, you will prefer keeping the "big" transactions and reducing the "small" ones. 如果要最小化事务数,则最好保留“大”事务,而减少“小”事务。 Hence, the cost of each edge could be modeled as the inverse of the flow on that edge. 因此,可以将每个边的成本建模为该边上流量的倒数。

I've actually encountered this problem in exactly the same situation as you :) 我实际上在与您完全相同的情况下遇到了这个问题:)

I think krlmlr's various solution don't quite solve the problem exactly. 我认为krlmlr的各种解决方案不能完全解决问题。 I'll have a think about how to solve it exactly (in the minimum-edges sense), but in the meantime, a practical alternative solution to your problem is to invent a new person, Steve : 我将考虑如何准确地解决问题(在最小边缘的意义上),但是与此同时,解决您问题的一种可行替代方案是发明一个新人Steve

  1. Steve is not actually a person. 史蒂夫实际上不是一个人。 Steve is just a bucket, with a piece of paper attached to it. 史蒂夫只是一个水桶,上面有一张纸。
  2. Everyone calculates the net amount that they owe (or are owed, if negative), and writes it on the piece of paper, alongside their name. 每个人都会计算出他们所欠的净额(或负数,如果是负数的话),并将其写在纸上,并附上他们的名字。
  3. Anyone whose net position is that they owe money gives that net amount of money to Steve when they can, and crosses off their name. 任何净负债状况是欠他们钱的人,只要有可能就将净额交给史蒂夫,然后划掉他们的名字。
  4. Everyone whose net position is that they are owed money takes that money from Steve when they see Steve has it, and crosses off their name. 知道自己欠钱的净头寸的每个人,都会在看到史蒂夫有钱时从史蒂夫那里拿走那笔钱,然后划掉自己的名字。

If a person who owes money can't pay all of it at once, they can just give Steve what they can currently afford, and take this amount off the total-owing figure against their name. 如果一个欠债的人不能一次付清所有的钱,他们可以给史蒂夫他们目前可以负担的,然后从欠债总额中减去这笔钱。 Likewise if you are owed more money than Steve currently has on hand, you can take all of the money he currently has, and take that amount off the total-owed against your name. 同样,如果所欠您的钱比史蒂夫目前所拥有的更多,您可以提取他目前所拥有的所有钱,并从您的名字中减去所欠的总金额。

If everyone agrees at the start to pay Steve only the full amount, then every net-ower makes exactly one deposit, and every net-owed person make exactly one withdrawal (although this may require multiple checks on Steve to see whether he currently has sufficient cash on hand). 如果每个人都同意一开始只向Steve支付全额款项,那么每个净债务人都只存一笔存款,而每个净债务人都只存一笔提取额(尽管这可能需要对Steve进行多次检查,以查看他目前是否有足够的钱手上的现金)。 The good thing about Steve is that he's always around, and is never too busy to sort out finances. 史蒂夫(Steve)的好处是他一直在身边,从不忙于理财。 Unfortunately he's very gullible, so Alice, Bob and Charlie need to already trust one another not to take advantage of him. 不幸的是,他很容易被骗,所以爱丽丝,鲍勃和查理需要彼此信任,才能不利用他。

Taking hints from the algorithm suggested by @krlmlr I have written this code with a sample example which consists of 2 heaps, one for debit and the other for credit and it iteratively balances the maximum values from both the heaps.从 @krlmlr 建议的算法中得到提示,我用一个示例示例编写了这段代码,该示例由 2 个堆组成,一个用于借方,另一个用于贷方,它迭代地平衡两个堆的最大值。 Code might look confusing because python only has min_heap and I needed to create a max_heap for the people that need to receive the value (credit heap), so I multiplied the values by -1.代码可能看起来很混乱,因为 python 只有 min_heap 而我需要为需要接收值的人创建一个 max_heap(信用堆),所以我将这些值乘以 -1。

https://github.com/Shivashish1010/Simplify-Debts/blob/master/simplify_debts.py https://github.com/Shivashish1010/Simplify-Debts/blob/master/simplify_debts.py

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM