簡體   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