Algorithm 确定组中最低付款额的算法 问题
最近,我被要求计算一起旅行的一群人所欠的钱,他们遇到了一个有趣的问题:既然你知道每个人欠另一个人的钱的数额,那么什么是合并人与人之间债务的通用算法,以便只需要支付最低数量的款项?以此为例:Algorithm 确定组中最低付款额的算法 问题,algorithm,language-agnostic,Algorithm,Language Agnostic,最近,我被要求计算一起旅行的一群人所欠的钱,他们遇到了一个有趣的问题:既然你知道每个人欠另一个人的钱的数额,那么什么是合并人与人之间债务的通用算法,以便只需要支付最低数量的款项?以此为例: 迈克欠约翰100英镑 约翰欠雷切尔200英镑 迈克欠雷切尔400英镑 我们可以通过如下方式重新安排债务,免除Mike和John之间的付款: 迈克欠约翰0英镑 约翰欠雷切尔100英镑 迈克欠雷切尔500英镑 我手工计算,因为这很简单,但是我的程序员很想找出一个通用的算法来为任意大的群体计算。对我来说,这
- 迈克欠约翰100英镑
- 约翰欠雷切尔200英镑
- 迈克欠雷切尔400英镑
- 迈克欠约翰0英镑
- 约翰欠雷切尔100英镑
- 迈克欠雷切尔500英镑
- 顶点是组中的人
- 根据欠款金额对边缘进行定向和加权。例如,迈克对瑞秋的优势是500磅,这意味着迈克欠瑞秋500磅
- 约束:每个节点的权重净和必须保持不变
- 目标是找到一个边数最少且仍满足约束的图
- 迈克欠约翰100英镑
- 约翰欠雷切尔200英镑
- 迈克欠雷切尔400英镑
- 迈克欠100英镑
- 约翰欠了100英镑
- 约翰欠200英镑
- 瑞秋欠了200英镑
- 迈克欠400英镑
- 瑞秋欠了400英镑
- 迈克欠500英镑
- 约翰欠100英镑
- 瑞秋欠了600英镑
- A欠25英镑的人
- B个人欠50英镑
- C人欠75英镑
- D人欠100英镑
- 被拖欠50英镑
- A欠10英镑的人
- B个人欠49英镑
- C人欠50英镑
- D人欠65英镑
- 被拖欠75英镑
- F人欠他99英镑
- 免除所有零债务的个人;他们不需要发送或接收任何人的钱
- 将所有给予者和接受者与相同的欠/欠金额配对。由于非零债务的每个节点的最小连通性为1,如果它们只是相互支付,那么它们的交易已经是最小的。从图表中删除它们
- 从偿还金额最大的个人开始,创建一个欠款少于该金额的所有接收人的列表。尝试所有的支付组合,直到找到一个能让大多数接收者满意的交易。”把剩余的债务存起来
- 转向下一个最大的给予者,等等
- 将所有剩余债务分配给剩余的接收者
- 我的意见是:你把这件事弄得太复杂了
将其视为一个“资金池”,并完全失去关系:
而不是:
正如其他海报所观察到的,这简化了问题。然而,“给予者”和“接受者”列表可能有一个最佳的顺序,但我们还没有确定一个简单的方法来确定这个顺序。仅仅找出接受者和给予者是不够的。虽然我认为这一策略是正确的,但它也不能确保找到尽可能少的支付金额的算法 比如说,
这个问题和被证明是NP难的问题有很多相似之处。唯一的区别是我们有多个大小不同的箱子,并且所有箱子中的总空间等于所有物品的总大小。这让我相信这个问题很可能是NP难的,但加上附加的约束,它可能在多项式时间内解决。虽然我同意安德鲁的观点,将其转化为图形问题可能过于复杂,但我不确定他的方法是否能产生最小数量的事务。这是你在现实生活中如何解决问题以避免头痛;把钱集中起来 看起来“正确”的几个步骤:
和往常一样,恐怕我对前两步很有把握,对其他的就没那么有把握了。无论如何,这听起来确实像是教科书上的问题;我相信有一个“正确”的答案。如果a、B和C对D、E和F各欠1美元,“列表”或“中央银行”解决方案会创建五个交易(例如a、B、C-$3->D、D-$3->E、F),而简单的解决方案会产生九个交易。然而,如果A只欠D,B只欠E,C只欠F,中央银行解决方案仍然会创建五个交易(A,B,C-$1->D,D-$1->E,F),而最好的解决方案只需要三个(A-$1->D,B-$1->E,C-$1->F)。这表明“列表”或“中央银行”解决方案总体上不是最优的 下面的贪婪算法可以用来为问题创建更好的解决方案,但它们并不总是最优的。让“债务[i,j]”表示我欠j的金额;最初,根据情况初始化此数组
repeat until last:
find any (i, j) such that |K = {k : debt[i,k] > 0 and debt[j,k] > 0}| >= 2
if such (i, j) found:
// transfer the loans from i to j
total = 0
for all k in K:
debt[j,k] += debt[i,k]
total += debt[i,k]
debt[i,k] = 0
person i pays 'total' to person j
else:
last
for every i, j in N:
if (debt[i,j] > 0)
person i pays debt[i,j] to person j
这个算法的关键是观察到如果A和B都欠C和D钱,而不是直接支付所需的四笔交易,
Input: {(Person, Net Profit)} //Net Profit < 0 is debt, Net Profit > 0 is credit.
Output: {(Payer, Payee, Amount paid)}
find_payments(input_list):
if input_list.length() > 2:
//More than two people to resolve payments between, the non-trivial case
max_change = input_list[0]
not_max_change = []
//Find person who has the highest change to their account, and
//the list of all others involved who are not that person
for tuple in input_list:
if abs(tuple[Net Profit]) > abs(max_change[Net Profit])
not_max_change.push(max_change)
max_change = tuple
else:
not_max_change.push(tuple)
//If the highest change person is owed money, they are paid by the
//person who owes the most money, and the money they are paid is deducted
//from the money they are still owed.
//If the highest change person owes money, they pay the person who
//is owed the most money, and the money they pay is deducted
//from the money they are still owe.
not_yet_resolved = []
if max_change[Net Profit] > 0:
//The person with the highest change is OWED money
max_owing = not_max_change[0]
//Find the remaining person who OWES the most money
//Find the remaining person who has the LOWEST Net Profit
for tuple in not_max_change:
if tuple[Net Paid] < max_owing[Net Paid]:
not_yet_resolved.push(max_owing)
max_owing = tuple
else:
not_yet_resolved.push(tuple)
//The person who has the max change which is positive is paid
//by the person who owes the most, reducing the amount of money
//they are owed. Note max_owing[Net Profit] < 0.
max_change = [max_change[Person], max_change[Net Profit]+max_owing[Net Profit]]
//Max_owing[Person] has paid max_owing[Net Profit] to max_change[Person]
//max_owing = [max_owing[Person], max_owing[Net Profit]-max_owing[Net Profit]]
//max_owing = [max_owing[Person], 0]
//This person is fully paid up.
if max_change[Net Profit] != 0:
not_yet_resolved.push(max_owing)
//We have eliminated at least 1 involved individual (max_owing[Person])
//because they have paid all they owe. This truth shows us
//the recursion will eventually end.
return [[max_owing[Person], max_change[Person], max_owing[Net Profit]]].concat(find_payments(not_yet_resolved))
if max_change[Net Profit] < 0:
//The person with the highest change OWES money
//I'll be way less verbose here
max_owed = not_max_change[0]
//Find who is owed the most money
for tuple in not_max_change:
if tuple[Net Paid] > max_owed[Net Paid]:
not_yet_resolved.push(max_owed)
max_owed = tuple
else:
not_yet_resolved.push(tuple)
//max_change pays the person who is owed the most.
max_change = [max_change[Person], max_change[Net Profit]+max_owed[Net Profit]]
if max_change[Net Profit] != 0:
not_yet_resolved.push(max_owing)
//Note position of max_change[Person] moved from payee to payer
return [[max_change[Person], max_owed[Person], max_owed[Net Profit]]].concat(find_payments(not_yet_resolved))
//Recursive base case
//Two people owe each other some money, the person who owes pays
//the person who is owed. Trivial.
if input_list.length() == 2:
if input_list[0][Net Profit] > input_list[1][Net Profit]:
return [[input_list[1][Person], input_list[0][Person], input_list[0][Net Profit]]];
else
return [[input_list[0][Person], input_list[1][Person], input_list[1][Net Profit]]];
max_change = (payee, $A); max_owing = (payer, $B)
|$A|>=|$B| by nature of 'max_change'
$A > 0 => $A >= |$B| //max_change is owed money
$B < 0 by nature of 'max_owing'
$A >= -$B => $A + $B >= 0 => Payee does not go into debt
max_change = (payee, $A); max_owed = (payer, $B)
|$A|>=|$B| by nature of 'max_change'
$A < 0 => -$A >= |$B| //max_change owes money
$B > 0 by nature of 'max_owed'
-$A >= $B => 0 >= $A + $B => Payee does not go into credit
Sum of Payments = 0 = $A + $B + Remainder = ($A + $B) + 0 + Remainder