E036. Floyd-Warshall algorithm нахождения кратчайших путей между всеми парами вершин
Источник: e-maxx.ru/algo, страница PDF 113.
Дан ориентированный или неориентированный взвешенный 图
с
vertexми. it is required find значения
всех величин
— длины кратчайшего пути из вершины
в вершину
. Предполагается, что 图 не содержит циклов отрицательного веса (тогда ответа между некоторыми парами вершин может просто не существовать — он будет бесконечно маленьким). Этот 算法 был одновременно опубликован в статьях Роберта Флойда (Robert Floyd) и Стивена Уоршелла (Варшалла) (Stephen Warshall) в 1962 г., по имени которых этот 算法 и называется в настоящее время. Впрочем, в 1959 г. Бернард Рой (Bernard Roy) опубликовал практически такой же 算法, но его публикация осталась незамеченной.
Описание 算法а
Ключевая идея 算法а — разбиение процесса поиска кратчайших путей на фазы.
Перед
-ой фазой (
) считается, что в матрице расстояний
сохранены длины таких кратчайших
путей, которые содержат в качестве внутренних вершин только вершины из множества
(вершины 图а мы нумеруем, начиная с единицы).
Иными словами, перед
-ой фазой величина
равна длине кратчайшего пути из вершины
в вершину
,
если этому пути разрешается заходить только в вершины с номерами, меньшими (начало и конец пути не считаются). Легко убедиться, что чтобы это свойство выполнилось для первой фазы, достаточно в матрицу расстояний
записать матрицу смежности 图а:
— стоимости ребра из вершины
в вершину
. При этом,
если между какими-то vertexми ребра нет, то записать следует величину "бесконечность"
. Из вершины в саму
себя всегда следует записывать величину
, это критично для 算法а.
Пусть теперь мы находимся на
-ой фазе, и хотим пересчитать матрицу
таким образом, чтобы
она соответствовала требованиям уже для
-ой фазы. Зафиксируем какие-то вершины
и
. У нас возникает
два принципиально разных случая:
● 最短路径 из вершины
в вершину
, которому разрешено дополнительно проходить через
вершины
, совпадает с кратчайшим путём, которому разрешено проходить через
вершины множества
.
В этом случае величина
не изменится при переходе с
-ой на
-ую фазу.
● "Новый" 最短路径 стал лучше "старого" пути.
Это означает, что "новый" 最短路径 проходит через вершину
. Сразу отметим, что мы не потеряем
общности, рассматривая далее только простые пути (т.е. пути, не проходящие по какой-то вершине дважды).
Тогда заметим, что если мы разобьём этот "новый" путь вершиной
на две половинки (одна идущая
, а другая
—
), то каждая из этих половинок уже не заходит в вершину
. Но тогда получается, что длина каждой из
этих половинок была посчитана ещё на
-ой фазе или ещё раньше, и нам достаточно взять просто
сумму
, она и даст длину "нового" кратчайшего пути.
Объединяя эти два случая, получаем, что на
-ой фазе it is required пересчитать длины кратчайших путей между
всеми парами вершин
и
следующим образом:
new_d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
Таким образом, вся работа, которую it is required произвести на
-ой фазе — это перебрать все пары вершин и
пересчитать длину кратчайшего пути между ними. В результате после выполнения
-ой фазы в матрице
расстояний
будет записана длина кратчайшего пути между
и
, либо
, если пути между этими vertexми
не существует. Последнее замечание, которое следует сделать, — то, что можно не создавать отдельную
матрицу
для временной матрицы кратчайших путей на
-ой фазе: все изменения можно делать сразу
в матрице
. В самом деле, если мы улучшили (уменьшили) какое-то значение в матрице расстояний, мы не могли ухудшить тем самым длину кратчайшего пути для каких-то других пар вершин, обработанных позднее.
Asymptotic complexity 算法а, очевидно, составляет
.
实现
На 输入 программе подаётся 图, заданный в виде матрицы смежности — двумерного 数组а
размера
,
в котором каждый element задаёт длину ребра между соответствующими vertexми.
it is required, чтобы выполнялось
для любых
.
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
Предполагается, что если между двумя какими-то vertexми нет ребра, то в матрице смежности было записано какое-то большое number (достаточно большое, чтобы оно было больше длины любого пути в этом 图е); тогда это edge всегда будет невыгодно брать, и 算法 сработает правильно. Правда, если не принять специальных мер, то при наличии в 图е рёбер отрицательного веса,
в результирующей матрице могут появиться числа вида
,
, и т.д., которые, конечно, по-
прежнему означают, что между соответствующими vertexми вообще нет пути. Поэтому при наличии в 图е отрицательных рёбер 算法 Флойда лучше написать так, чтобы он не выполнял переходы из тех состояний, в которых уже стоит "нет пути":
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
if (d[i][k] < INF && d[k][j] < INF)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
Восстановление самих путей
Легко поддерживать дополнительную информацию — так называемых "предков", по которым можно будет восстанавливать сам 最短路径 между любыми двумя заданными vertexми в виде последовательности вершин.
Для этого достаточно кроме матрицы расстояний
поддерживать также матрицу предков
, которая
для каждой пары вершин будет содержать номер фазы, на которой было получено кратчайшее расстояние между ними. Понятно, что этот номер фазы является не чем иным, как "средней" вершиной искомого кратчайшего пути, и
теперь нам просто надо find 最短路径 между vertexми
и
, а также между
и
. Отсюда получается простой рекурсивный 算法 восстановления кратчайшего пути.
Случай вещественных весов
Если веса рёбер 图а не целочисленные, а вещественные, то следует учитывать погрешности, неизбежно возникающие при работе с типами с плавающей точкой. Применительно к 算法у Флойда неприятным спецэффектом этих погрешностей становится то, что найденные 算法ом расстояния могут уйти сильно в минус из-за накопившихся ошибок. В самом деле,
если на первой фазе имела место ошибка
, то на второй итерации эта ошибка уже может превратиться в
,
на третьей — в
, и так далее. Чтобы этого не происходило, сравнения в 算法е Флойда следует делать с учётом погрешности:
if (d[i][k] + d[k][j] < d[i][j] - EPS)
d[i][j] = d[i][k] + d[k][j];
Случай отрицательных циклов
Если в 图е есть циклы отрицательного веса, то формально Floyd-Warshall algorithm неприменим к такому 图у.
На самом же деле, для тех пар вершин
и
, между которыми нельзя зайти в цикл отрицательного вес,
算法 отработает корректно. Для тех же пар вершин, ответа для которых не существует (по причине наличия отрицательного цикла на пути между ними), 算法 Флойда найдёт в качестве ответа какое-то number (возможно, сильно отрицательное, но не обязательно). Тем не менее, можно улучшить 算法 Флойда, чтобы он аккуратно обрабатывал такие пары вершин
и выводил для них, на示例,
. Для этого можно сделать, на示例, следующий критерий "не существования пути". Итак, пусть на данном
图е отработал обычный 算法 Флойда. Тогда между vertexми
и
не существует кратчайшего пути тогда
и только тогда, когда найдётся такая vertex
, достижимая из
и из которой достижима
, для которой
выполняется
. Кроме того, при использовании 算法а Флойда для 图ов с отрицательными циклами следует помнить, что возникающие в процессе работы расстояния могут сильно уходить в минус, экспоненциально с каждой фазой. Поэтому следует принять меры против целочисленного переполнения, ограничив все расстояния снизу какой-
нибудь величиной (на示例,
). Более подробно об этой задаче см. отдельную статью: "Нахождение отрицательного цикла в 图е".
C# 解法
自动草稿,提交前请检查using System;
using System.Collections.Generic;
using System.Linq;
public static class AlgorithmDraft
{
// Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
new_d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
if (d[i][k] < INF && d[k][j] < INF)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
if (d[i][k] + d[k][j] < d[i][j] - EPS)
d[i][j] = d[i][k] + d[k][j];
}
C++ 解法
匹配/原始new_d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
if (d[i][k] < INF && d[k][j] < INF)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
if (d[i][k] + d[k][j] < d[i][j] - EPS)
d[i][j] = d[i][k] + d[k][j];
Java 解法
自动草稿,提交前请检查import java.util.*;
import java.math.*;
public class AlgorithmDraft {
// Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
new_d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
for (int k=0; k<n; ++k)
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
if (d[i][k] < INF && d[k][j] < INF)
d[i][j] = min (d[i][j], d[i][k] + d[k][j]);
if (d[i][k] + d[k][j] < d[i][j] - EPS)
d[i][j] = d[i][k] + d[k][j];
}
Материал разбит как 算法ическая 题目: изучить постановку, понять асимптотику и реализовать 算法 на выбранном языке.
Vacancies for this task
活跃职位 with overlapping task tags are 已显示.