MAXimal | |
добавлено: 10 Jun 2008 19:37 Содержание [скрыть] Алгоритм Форда-БеллманаПусть дан ориентированный взвешенный граф В отличие от алгоритма Дейкстры, этот алгоритм применим также и к графам, содержащим рёбра отрицательного веса. Впрочем, если граф содержит отрицательный цикл, то, понятно, кратчайшего пути до некоторых вершин может не существовать (по причине того, что вес кратчайшего пути должен быть равен минус бесконечности); впрочем, этот алгоритм можно модифицировать, чтобы он сигнализировал о наличии цикла отрицательного веса, или даже выводил сам этот цикл. Алгоритм носит имя двух американских учёных: Ричарда Беллмана (Richard Bellman) и Лестера Форда (Lester Ford). Форд фактически изобрёл этот алгоритм в 1956 г. при изучении другой математической задачи, подзадача которой свелась к поиску кратчайшего пути в графе, и Форд дал набросок решающего эту задачу алгоритма. Беллман в 1958 г. опубликовал статью, посвящённую конкретно задаче нахождения кратчайшего пути, и в этой статье он чётко сформулировал алгоритм в том виде, в котором он известен нам сейчас. Описание алгоритмаМы считаем, что граф не содержит цикла отрицательного веса. Случай наличия отрицательного цикла будет рассмотрен ниже в отдельном разделе. Заведём массив расстояний Сам алгоритм Форда-Беллмана представляет из себя несколько фаз. На каждой фазе просматриваются все рёбра графа, и алгоритм пытается произвести релаксацию (relax, ослабление) вдоль каждого ребра Утверждается, что достаточно РеализацияДля алгоритма Форда-Беллмана, в отличие от многих других графовых алгоритмов, более удобно представлять граф в виде одного списка всех рёбер (а не Простейшая реализацияКонстанта struct edge { int a, b, cost; }; int n, m, v; vector<edge> e; const int INF = 1000000000; void solve() { vector<int> d (n, INF); d[v] = 0; for (int i=0; i<n-1; ++i) for (int j=0; j<m; ++j) if (d[e[j].a] < INF) d[e[j].b] = min (d[e[j].b], d[e[j].a] + e[j].cost); // вывод d, например, на экран } Проверка "if (d[e[j].a] < INF)" нужна, только если граф содержит рёбра отрицательного веса: без такой проверки бы происходили релаксации из вершин, до которых пути ещё не нашли, и появлялись бы некорректные расстояния вида Улучшенная реализацияЭтот алгоритм можно несколько ускорить: зачастую ответ находится уже за несколько фаз, а оставшиеся фазы никакой полезной работы не происходит, лишь впустую просматриваются все рёбра. Поэтому будем хранить флаг того, изменилось что-то на текущей фазе или нет, и если на какой-то фазе ничего не произошло, то алгоритм можно останавливать. (Эта оптимизация не улучшает асимптотику, т.е. на некоторых графах по-прежнему будут нужны все С такой оптимизацией становится вообще ненужным ограничивать вручную число фаз алгоритма числом void solve() { vector<int> d (n, INF); d[v] = 0; for (;;) { bool any = false; for (int j=0; j<m; ++j) if (d[e[j].a] < INF) if (d[e[j].b] > d[e[j].a] + e[j].cost) { d[e[j].b] = d[e[j].a] + e[j].cost; any = true; } if (!any) break; } // вывод d, например, на экран } Восстановление путейРассмотрим теперь, как можно модифицировать алгоритм Форда-Беллмана так, чтобы он не только находил длины кратчайших путей, но и позволял восстанавливать сами кратчайшие пути. Для этого заведём ещё один массив Заметим, что алгоритм Форда-Беллмана работает по такой же логике: он, предполагая, что кратчайшее расстояние до одной вершины уже посчитано, пытается улучшить кратчайшее расстояние до другой вершины. Следовательно, в момент улучшения нам надо просто запоминать в Приведём реализацию Форда-Беллмана с восстановлением пути до какой-то заданной вершины void solve() { vector<int> d (n, INF); d[v] = 0; vector<int> p (n, -1); for (;;) { bool any = false; for (int j=0; j<m; ++j) if (d[e[j].a] < INF) if (d[e[j].b] > d[e[j].a] + e[j].cost) { d[e[j].b] = d[e[j].a] + e[j].cost; p[e[j].b] = e[j].a; any = true; } if (!any) break; } if (d[t] == INF) cout << "No path from " << v << " to " << t << "."; else { vector<int> path; for (int cur=t; cur!=-1; cur=p[cur]) path.push_back (cur); reverse (path.begin(), path.end()); cout << "Path from " << v << " to " << t << ": "; for (size_t i=0; i<path.size(); ++i) cout << path[i] << ' '; } } Здесь мы сначала проходимся по предкам, начиная с вершины Доказательство алгоритмаВо-первых, сразу заметим, что для недостижимых из Докажем теперь следующее утверждение: после выполнения Иными словами, для любой вершины Доказательство. Рассмотрим произвольную вершину Последнее, что надо заметить — это то, что любой кратчайший путь не может иметь более Случай отрицательного циклаВыше мы везде считали, что отрицательного цикла в графе нет (уточним, нас интересует отрицательный цикл, достижимый из стартовой вершины Нетрудно понять, что алгоритм Форда-Беллмана сможет бесконечно делать релаксации среди всех вершин этого цикла и вершин, достижимых из него. Следовательно, если не ограничивать число фаз числом Отсюда мы получаем критерий наличия достижимого цикла отрицательного веса: если после Более того, если такой цикл обнаружился, то алгоритм Форда-Беллмана можно модифицировать таким образом, чтобы он выводил сам этот цикл в виде последовательности вершин, входящих в него. Для этого достаточно запомнить номер вершины Реализация: void solve() { vector<int> d (n, INF); d[v] = 0; vector<int> p (n, -1); int x; for (int i=0; i<n; ++i) { x = -1; for (int j=0; j<m; ++j) if (d[e[j].a] < INF) if (d[e[j].b] > d[e[j].a] + e[j].cost) { d[e[j].b] = max (-INF, d[e[j].a] + e[j].cost); p[e[j].b] = e[j].a; x = e[j].b; } } if (x == -1) cout << "No negative cycle from " << v; else { int y = x; for (int i=0; i<n; ++i) y = p[y]; vector<int> path; for (int cur=y; ; cur=p[cur]) { path.push_back (cur); if (cur == y && path.size() > 1) break; } reverse (path.begin(), path.end()); cout << "Negative cycle: "; for (size_t i=0; i<path.size(); ++i) cout << path[i] << ' '; } } Поскольку при наличии отрицательного цикла за d[e[j].b] = max (-INF, d[e[j].a] + e[j].cost); В приведённой выше реализации ищется отрицательный цикл, достижимый из некоторой стартовой вершины Дополнительно на тему этой задачи — см. отдельную статью "Поиск отрицательного цикла в графе". Задачи в online judgesСписок задач, которые можно решить с помощью алгоритма Форда-Беллмана:
См. также список задач в статье "Поиск отрицательного цикла".
| |