Алгоритм

Алгоритм Флойда-Уоршелла используется для нахождения длины кратчайшего пути между всеми парами вершин во взвешенном графе за .

В основе алгоритма лежит достаточно простое ДП: пусть - длина кратчайшего пути между вершинами и , проходящего только через промежуточные вершины ( и не считаются).

Научимся пересчитывать пути при увеличении k: пусть мы хотим улучшить длину пути между и при увеличении на . У нас есть два варианта: не использовать вершину (тогда ), или использовать, пройдя через неё. Утверждается, что кратчайший путь из в в получается объединением кратчайших путей из в и из в (используя промежуточные вершины до , так как уже не является промежуточной). Тогда .

Сразу же попробуем сократить количество используемой памяти. Заметим, что для улучшения любого пути нам нужны только предыдущие значения длин путей для . Можно просто откинуть последнее измерение () в массиве, приняв - текущая минимальная длина пути из в , которую мы постепенно улучшаем.

В качестве начальных значений для ДП, просто запишем, что кратчайший путь из каждой вершины в саму себя равен : ; кратчайший путь между двумя вершинами, между которыми есть ребро равен его длине: ; а кратчайший путь между всеми остальными парами вершин равен бесконечности: . Заметьте, что начальные значения не используют никаких промежуточных вершин, поэтому начинать работу алгоритма нужно с .

Реализация

Для реализации нам не требуется хранить сам граф (в виде матрицы или списка смежности). Просто при вводе рёбер будем заполнять начальные значения ДП. Затем по очереди используем каждую вершину от до для улучшения пути между всеми парами вершин.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>

using namespace std;

const int INF = 1e9 + 7;

int dp[1000][1000];

int main() {
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            dp[i][j] = INF;
        }
    }

    for (int i = 0; i < n; i++) {
        dp[i][i] = 0;
    }

    for (int i = 0; i < m; i++) {
        int u, v, len;
        cin >> u >> v >> len;
        u--, v--;
        dp[u][v] = dp[v][u] = len;
    }

    for (int k = 0; k < n; k++) {        //текущая вершина, используемая для улучшения
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
            }
        }
    }

    //Массив dp содержит длины кратчайших путей между всеми парами вершин
}

Поведение при наличии отрицательных циклов

В отличие от алгоритма Дейкстры, алгоритм Флойда-Уоршелла может корректно работать при наличии в графе рёбер с отрицательным весом. Для этого стоит немного изменить реализацию, добавив явную проверку на равенство длины пути бесконечности. Без неё в массиве могут появляться расстояния , , и т.д. Они могут вызвать проблемы при достаточно больших длинах рёбер, поэтому перепишем алгоритм следующим образом:

for (int k = 0; k < n; k++) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (dp[i][k] < INF && dp[k][j] < INF) {   //явная проверка
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
            }
        }
    }
}

Однако существуют графы, в которых отрицательный вес имеют не простые пути, а циклы. Они лишают смысла задачу о нахождении кратчайшего пути, так как позволяют получать пути бесконечно малой длины. С помощью алгоритма Флойда-Уоршелла можно легко проверять в графе наличие таких циклов: если после окончания работы алгоритма существует вершина такая, что , то она входит в отрицательный цикл.