После того, как @j_random_hacker объяснил эту проблему в комментариях np-hard, я потерял всякую надежду найти красивое решение и решил сделать то, что работает.
Следуя предложению @ user3386109, также в комментарияхЯ решил использовать minpath для решения проблемы.Поэтому я начал с использования [алгоритма Флойда-Варшалла], чтобы найти минимальные затраты на получение денег от человека к другому за каждую пару.Это дает матрицу минимальной стоимости перевода денег от человека в строке к человеку в столбце.Я также применил модификацию (она доступна в статье Википедии в разделе «Восстановление пути»), чтобы получить матрицу, которая приводит к следующему узлу (следующему прыжку, фактическому человеку, которому вы должны отправить свои деньги, если хотите связаться с человеком в столбце с помощьюминимальные затраты) для человека в строке, чтобы перевести деньги этому человеку в столбце.
Пример инициализированных матриц и результат после запуска алгоритма (важные элементы, которые изменились, отмечены красной точкой):
Затем я решил сделать простую рекурсию ветвей и границ.Каждый рекурсивный вызов получит список долгов, матрицу для всех транзакций и стоимость достижения этого состояния.Он должен вернуть TODO.Мы сохраняем глобальный подход к лучшему найденному решению, и в начале проверки рекурсивного вызова, если стоимость достижения этого состояния уже хуже, чем лучший глобальный результат, если это так, мы возвращаем «бесконечную» стоимость, чтобы показать, что это нерешение, которое нам нужно рассмотреть.
Затем мы выбираем кого-то, кто должен деньги в списке долгов, и для каждого человека, который должен получить деньги, мы создаем копии списка долгов и матрицы транзакций и моделируем транзакциюмаксимальная сумма денег между этими двумя людьми (если А должен заплатить 100, а С должен получить только 50, то максимальная - 50).Матрица транзакций изменяется путем увеличения всех транзакций в minpath для этих двух человек на сумму перевода.Стоимость увеличивается, если мы увеличили элемент, который ранее был нулевым.Затем мы называем рекурсию.Если список долгов достигает 0, решение найдено, глобальная минимальная стоимость обновляется, а возвращаемая стоимость равна 0.
В предыдущей версии я порождал рекурсию для каждой пары должника / получателя.Это дало ужасную производительность и оказалось ненужным, поскольку порядок транзакций не имеет значения, и любые долги, которые еще не были погашены, будут рассматриваться на более низких уровнях рекурсии.
Этот алгоритм казался правильным, но проблема все еще была!
В следующем примере:
- Лицо А должно заплатить 40 $
- Человек B должен заплатить 40 $
- Человек C должен заплатить 20 $
- Человек D должен получить 100 $
Алгоритм, как сейчас, делает A, B, C каждый делает перевод в D. Фактически лучшим способом было бы выбрать один из A, B или C, чтобы перевести все деньги в D только одним платежом.
В этом примереЛица A, B и C имеют одинаково высокую стоимость перевода в D, и следующий шаг для получения денег для всех - это перейти непосредственно в D. Однако оптимальным решением было бы сделать перевод каждоговсе свои деньги одному человеку и переведите их D сразу.Алгоритм не может распознать, что, поскольку кто-то уже сделал перевод человеку D, стоимость этой транзакции равна 0, и поэтому мы должны использовать этот новый путь.Чтобы решить эту проблему, я включил в рекурсию матрицу затрат и матрицу путей, в начале рекурсии мы установили все затраты на переводы, которые уже были сделаны в этой ветви рекурсии, равными 0, и запустили алгоритм Флойда-Варшалла.снова.Рекурсия использует эти матрицы вместо глобальных и передает их.Конечно, это означает умножение сложности на V ^ 3, но я нашел единственный способ решить эту проблему.
Кажется, что алгоритм работает сейчас, но я, вероятно, буду продолжать пытаться улучшить его, особенно в области читабельности кода.Полный код доступен по адресу: Мой проект gitlab, внутри папки с вычислениями
Извините за длинный ответ и задержку с его публикацией, но я нашел важным тщательно документировать свою работу.