繁体   English   中英

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

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

我一直在使用我编写的一个小 python 脚本来管理室友之间的债务。 它有效,但缺少一些功能,其中之一是简化不必要的复杂债务结构。 例如,如果下面的加权有向图表示一些人,箭头表示他们之间的债务(Alice 欠 Bob 20 美元,Charlie 5 美元,Bob 欠 Charlie 10 美元,等等):

图1

很明显这个图应该简化为下图:

graph1-简化

如果 Alice 可以直接将 10 美元交给 Charlie,那么 10 美元从 Alice 传给 Bob 再从 Bob 传给 Charlie 是没有意义的。

那么,在一般情况下,目标是采用债务图并对其进行简化(即生成一个具有相同节点但不同边的新图),以便

  1. 没有节点的边同时指向它的内外(没有无用的钱易手)
  2. 所有节点都具有与原始图中相同的“流量”(就资金最终流向而言是相同的)。

“流”是指所有输入减去所有输出的值(有专门的术语吗?我不是图论专家)。 所以在上面的例子中,每个节点的流量值是:

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

您可以看到第一张图和第二张图通过每个节点的流量相同,因此这是一个很好的解决方案。 还有其他一些简单的情况,例如,可以通过删除最低值的边并从所有其他边中减去它的值来简化任何循环。

这个:

图2

应该简化为:

graph2-简化

我无法想象没有人研究过这个问题; 我只是不知道要搜索什么术语来查找有关它的信息(同样,不是图论专家)。 我一直在寻找几个小时无济于事,所以我的问题是:根据上面为任何加权有向图指定的条件,将产生简化(新图)的算法是什么?

这是一篇学术论文,详细研究了这个问题。 到最后,第8节还提供了一些针对不同算法的示例代码。

Verhoeff,T。(2004)。 有效地解决多笔债务:计算机科学的邀请。 教育信息学,3(1),105-126。

简单算法

您可以在O(n)中找到期望获得或支付多少钱。 因此,您可以简单地创建两个列表,一个用于借记,另一个用于贷记,然后平衡两个列表的标题,直到它们为空。 从第一个示例:

  • 初始状态:借方:(A:25),贷方:(B:15,C:10)
  • 第一笔交易,A:15-> B:借方:(A:10),贷方:(C:10)
  • 第二次交易,A:10-> C:借方:(),贷方:()

事务定义了图形的边缘。 对于涉及的n个人,最多将有n-1个交易=边。 首先,两个列表的总长度为n。 在每个步骤中,至少一个列表(借方/贷方)变短了一个,最后,两个列表都立即消失了。

问题在于,一般而言,该图不必与原始图相似,正如我所希望的那样,这是必需的。 (是吗?在某些情况下,最佳解决方案包括添加新边。想象一下,由于A欠B且B欠C的钱相同,A应该直接付给C,但此边不在债务图中。)

交易减少

如果目标只是构造一个等效的图,则可以搜索债权人和借方清单(如上一节中)以查找完全匹配,或者在贷方总和与一个人的借方相匹配的情况下(或相反) )。 寻找垃圾箱包装 对于其他情况,除了拆分流之外,您别无选择,但是即使是上面的简单算法,生成的图的边缘也比所涉及的人员少(最多)。

编辑 :感谢j_random_hacker指出,如果有一组人的总债务与另一组人的信用相匹配,则可以解决少于n-1条边的问题:然后,该问题可以分为两个子问题:交易图的n-2条边的总成本。 不幸的是, 子集和的问题是NP难的。

流量有问题吗?

也许这也可以转化为最小成本流动问题 如果只想简化原始图形,则在其上构造一个流程,边容量为原始借方/贷方金额。 借方用作流入节点(通过连接器节点,该节点为所有借方提供容量等于其总债务的借方),债权人用作流出节点(具有类似的连接器节点)。

如果要最小化事务数,则最好保留“大”事务,而减少“小”事务。 因此,可以将每个边的成本建模为该边上流量的倒数。

我实际上在与您完全相同的情况下遇到了这个问题:)

我认为krlmlr的各种解决方案不能完全解决问题。 我将考虑如何准确地解决问题(在最小边缘的意义上),但是与此同时,解决您问题的一种可行替代方案是发明一个新人Steve

  1. 史蒂夫实际上不是一个人。 史蒂夫只是一个水桶,上面有一张纸。
  2. 每个人都会计算出他们所欠的净额(或负数,如果是负数的话),并将其写在纸上,并附上他们的名字。
  3. 任何净负债状况是欠他们钱的人,只要有可能就将净额交给史蒂夫,然后划掉他们的名字。
  4. 知道自己欠钱的净头寸的每个人,都会在看到史蒂夫有钱时从史蒂夫那里拿走那笔钱,然后划掉自己的名字。

如果一个欠债的人不能一次付清所有的钱,他们可以给史蒂夫他们目前可以负担的,然后从欠债总额中减去这笔钱。 同样,如果所欠您的钱比史蒂夫目前所拥有的更多,您可以提取他目前所拥有的所有钱,并从您的名字中减去所欠的总金额。

如果每个人都同意一开始只向Steve支付全额款项,那么每个净债务人都只存一笔存款,而每个净债务人都只存一笔提取额(尽管这可能需要对Steve进行多次检查,以查看他目前是否有足够的钱手上的现金)。 史蒂夫(Steve)的好处是他一直在身边,从不忙于理财。 不幸的是,他很容易被骗,所以爱丽丝,鲍勃和查理需要彼此信任,才能不利用他。

从 @krlmlr 建议的算法中得到提示,我用一个示例示例编写了这段代码,该示例由 2 个堆组成,一个用于借方,另一个用于贷方,它迭代地平衡两个堆的最大值。 代码可能看起来很混乱,因为 python 只有 min_heap 而我需要为需要接收值的人创建一个 max_heap(信用堆),所以我将这些值乘以 -1。

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