Алгоритм Беллмана-Форда: пути с отрицательными весами

Алгоритм Беллмана-Форда - это способ найти кратчайшие пути от одной вершины графа до всех остальных, когда в графе есть рёбра с отрицательными весами. Имена в названии - Ричард Беллман и Лестер Форд: они независимо описали схему в 1956-1958 годах. В отличие от Дейкстры, Беллман-Форд корректно работает при отрицательных весах и умеет обнаруживать отрицательные циклы - пути, по которым можно бесконечно уменьшать сумму. Платой за это является сложность : алгоритм медленнее Дейкстры, но универсальнее.
Зачем нужен - где Дейкстра ломается
Дейкстра - жадный алгоритм. На каждом шаге он берёт вершину с минимальным текущим и считает, что её расстояние больше уже не улучшится. Это рассуждение справедливо только при неотрицательных весах: добавляя к новое ребро, мы можем только увеличить сумму.
Если в графе есть отрицательное ребро, рассуждение разваливается. Минимальный пример - три вершины:
A → B (вес 5) A → C (вес 4) C → B (вес -3)
Дейкстра, начиная из , сначала «закрепит» со значением , потому что это меньше, чем у . После этого она уже не вернётся к , хотя реальный кратчайший путь - длиной . Ответ окажется неверным. Беллман-Форд же ничего не закрепляет преждевременно и спокойно найдёт .
Отрицательные веса - не экзотика. Они возникают в задачах с прибылью и убытком (положительные - расходы, отрицательные - доходы), в финансовом arbitrage, в задачах линейного программирования и в системах с разностными ограничениями.
Главная идея - динамическая релаксация
Беллман-Форд опирается на простое наблюдение. Кратчайший путь из в любую вершину без повторений содержит не более рёбер (иначе по принципу Дирихле в нём была бы вершина дважды и цикл, который можно выбросить). Значит, если повторить операцию релаксации всех рёбер графа раз, любое корректное кратчайшее расстояние успеет «дойти» до своей вершины.
Релаксация ребра - то же, что у Дейкстры:
Разница в порядке. Дейкстра выбирает «лучшую» вершину для расслабления по очереди и обрабатывает каждую один раз. Беллман-Форд тупо проходит по всем рёбрам в произвольном порядке и повторяет такой проход раз. За счёт повторов отрицательные ребра успевают «доехать» до своих потомков.
После первой итерации точно посчитаны кратчайшие пути из одного ребра. После второй - из двух рёбер. После -й - из не более чем рёбер. После -й - все, какие возможны.
Псевдокод
function bellman_ford(V, E, s):
d[s] = 0
d[v] = ∞ для всех v ≠ s
Повторить V - 1 раз:
для каждого ребра (u, v, w) ∈ E:
если d[u] + w < d[v]:
d[v] = d[u] + w
# Проверка отрицательного цикла
для каждого ребра (u, v, w) ∈ E:
если d[u] + w < d[v]:
return "Отрицательный цикл достижим из s"
return d
Восстановление пути устроено как и у Дейкстры: в момент успешной релаксации запоминаем , а потом идём от целевой вершины назад до .
На бумажке руками такой обход можно прогнать только на маленьких графах из 4-5 вершин. Опиши свой граф ниже, выбери режим - и собранный запрос откроет чат с пошаговой трассировкой: что в массиве после каждой итерации, есть ли отрицательный цикл и, если есть, какие вершины в него входят.
Обнаружение отрицательных циклов
Если в графе есть цикл, сумма весов в котором отрицательна, понятия «кратчайшего пути» в строгом смысле не существует - каждый новый обход цикла уменьшает суммарную длину, и инфимум равен . Беллман-Форд умеет такой цикл обнаружить.
Идея проверки. После итераций все корректные кратчайшие пути уже стабилизировались - никакая релаксация не должна давать улучшения. Если на -й итерации хоть одно ребро всё ещё уменьшает , значит, мы упёрлись в отрицательный цикл, достижимый из . Какая именно вершина лежит на цикле - можно восстановить, пройдя раз по от : за столько шагов гарантированно попадём на сам цикл, а дальше обходом извлечём его вершины.
В практике эта проверка ценнее самих кратчайших путей. Классический пример - arbitrage detection в финансовых сетях. Вершины - валюты, рёбра - обменные курсы. Если за вес ребра взять , где - курс, то произведение курсов по циклу превращается в сумму весов. Отрицательный цикл по этим весам означает, что произведение курсов по кругу больше единицы - то есть из 1 рубля через получается больше 1 рубля. Это и есть арбитражная возможность. Беллман-Форд её ищет за полиномиальное время.
Подробный пример
Граф из пяти вершин с одним отрицательным ребром:
s → A (вес 6) s → B (вес 7) A → C (вес 5) A → B (вес 8) A → D (вес -4) B → C (вес -3) B → D (вес 9) C → B (вес -2) D → s (вес 2) D → C (вес 7)
Начальное состояние: , остальные - . Будем обходить рёбра в порядке, в котором они выписаны.
Итерация 1. (через ). Из : . Из : , .
Итерация 2. Из релаксируется . Из потом , . Из снова (через ).
Итерация 3. Из : . Из : , и так далее.
Заметим, что значения продолжают уменьшаться. Дойдя до -й итерации, мы получим конкретный набор . На пятой проверочной итерации релаксации всё ещё проходят - потому что цикл имеет суммарный вес . Алгоритм сообщит об отрицательном цикле и не вернёт расстояний. Это и есть штатная работа.
Если бы ребра не было (или его вес был бы , а не ), цикла бы не существовало, и алгоритм за итерации сошёлся бы к корректному набору расстояний.
Сложность и сравнение с Дейкстрой
Внешний цикл выполняется раз, внутри - проход по всем рёбрам. Итого:
Для сравнения, Дейкстра с двоичной кучей:
В плотных графах () Беллман-Форд работает за , Дейкстра - за . В разреженных () - против . Беллман-Форд почти всегда хуже по скорости. Но он умеет то, чего Дейкстра не умеет.
Существует улучшение - SPFA (Shortest Path Faster Algorithm): вместо слепого обхода всех рёбер на каждой итерации поддерживается очередь вершин, у которых только что изменилось - именно их рёбра имеет смысл расслаблять дальше. В среднем SPFA работает существенно быстрее , хотя в худшем случае оценка та же. На случайных графах SPFA обычно обгоняет даже Дейкстру.
Применения
- Маршрутизация в сетях. Протокол RIP (Routing Information Protocol) - классический distance-vector протокол, опирается на Беллмана-Форда. Каждый маршрутизатор хранит вектор расстояний до всех узлов и обновляет его по обновлениям соседей. RIP ограничен 15 хопами как раз потому, что без ограничения отрицательные обновления могут «гулять» по сети.
- Arbitrage detection. В валютных парах, как мы разобрали выше, отрицательный цикл = арбитражная возможность.
- Линейное программирование. В задачах LP с ограничениями вида (системы разностных ограничений) Беллман-Форд находит допустимое решение либо доказывает его отсутствие.
- Алгоритм Джонсона. Для задачи о всех парах кратчайших путей в графе с отрицательными весами Джонсон сначала запускает Беллмана-Форда, чтобы перевзвесить рёбра в неотрицательные, а потом раз - Дейкстру. Итоговая сложность - лучше, чем прямой Флойд-Уоршалл , на разреженных графах.
Если в графе нет отрицательных весов - используйте Дейкстру, она всегда быстрее. Беллман-Форд берите, только если в задаче явно есть отрицательные веса или если нужна именно проверка на отрицательный цикл.
Частые ошибки
- Делают итераций вместо . Корректных итераций ровно ; -я нужна только для проверки отрицательного цикла. Лишний проход не вреден, но запутывает: непонятно, считаешь ты ещё пути или уже проверяешь цикл.
- Игнорируют недостижимые вершины. Если , выражение в большинстве языков переполнится и станет отрицательным. Сравнение нужно защищать:
if d[u] != ∞ and d[u] + w < d[v]. - Считают, что любой отрицательный цикл будет найден. Беллман-Форд обнаружит только циклы, достижимые из стартовой вершины. Для поиска любого отрицательного цикла в графе можно добавить виртуальную вершину с рёбрами нулевого веса ко всем и запустить из .
- Путают с Флойдом-Уоршаллом. Беллман-Форд - single-source ( все). Флойд-Уоршалл - all-pairs (все все), за .
- Применяют к ориентированному циклу, считая его обычным ребром. Цикл с весами и - отрицательный. На неориентированном графе любое отрицательное ребро автоматически даёт отрицательный цикл , поэтому Беллман-Форд на неориентированных графах с отрицательными весами почти всегда бесполезен.
FAQ
В чём принципиальная разница с алгоритмом Дейкстры? Дейкстра жадно фиксирует вершину с минимальным и больше к ней не возвращается - это даёт скорость , но ломается при отрицательных весах. Беллман-Форд ничего не фиксирует, повторяет релаксацию раз и работает корректно при любых весах, кроме отрицательных циклов.
Можно ли получить кратчайший путь, а не только длину? Да, как у Дейкстры: завести массив , обновлять его в момент релаксации, в конце восстанавливать путь от целевой вершины назад до .
Что делать, если в графе есть отрицательный цикл, но мне всё равно нужны кратчайшие пути для остальных вершин? Запустить Беллмана-Форда как обычно. Вершины, в которые нельзя «дойти через цикл», сохранят корректные значения. Вершины, на пути к которым лежит отрицательный цикл, нужно пометить специальным флагом - это делается отдельным BFS/DFS из вершин, для которых на -й итерации сработала релаксация.
Коротко
Беллман-Форд за проход по всем рёбрам находит кратчайшие пути из стартовой вершины во все остальные при любых весах, включая отрицательные. На -й итерации он же обнаруживает отрицательные циклы, достижимые из старта. Сложность хуже Дейкстры, но цена честная: при отрицательных весах Дейкстра попросту неверна. Главные практические сценарии - distance-vector маршрутизация (RIP), arbitrage detection в финансовых сетях и подготовительный шаг в алгоритме Джонсона.
Читайте также

Алгоритм Дейкстры: как найти кратчайший путь в графе
Разбираем алгоритм Дейкстры на взвешенном графе по шагам: как он находит кратчайшие пути от стартовой вершины и почему ломается на ребрах с отрицательными весами.

Алгоритм Прима - как построить остовное дерево по шагам
Разбираем, как алгоритм Прима шаг за шагом строит минимальное остовное дерево графа: идея жадного выбора, лемма о разрезе и трассировка на конкретном примере.

Радиус и диаметр графа: как считать эксцентриситет
Что такое радиус и диаметр графа, как через эксцентриситет вершин найти центр и периферию. Формулы, пошаговый пример и связь с расстояниями в графе.