E036. Floyd-Warshall algorithm нахождения кратчайших путей между всеми парами вершин

e-maxx algorithm original: C/C++ #algorithm #emaxx #graph #shortest-path
O texto da tarefa é traduzido do russo para o idioma selecionado. O código permanece sem alterações.

Источник: e-maxx.ru/algo, страница PDF 113.

Дан ориентированный или неориентированный взвешенный grafo

с

vertexми. it is required find значения

всех величин

— длины кратчайшего пути из вершины

в вершину

. Предполагается, что grafo не содержит циклов отрицательного веса (тогда ответа между некоторыми парами вершин может просто не существовать — он будет бесконечно маленьким). Этот Algoritmo был одновременно опубликован в статьях Роберта Флойда (Robert Floyd) и Стивена Уоршелла (Варшалла) (Stephen Warshall) в 1962 г., по имени которых этот Algoritmo и называется в настоящее время. Впрочем, в 1959 г. Бернард Рой (Bernard Roy) опубликовал практически такой же Algoritmo, но его публикация осталась незамеченной.

Описание Algoritmoа

Ключевая идея Algoritmoа — разбиение процесса поиска кратчайших путей на фазы.

Перед

-ой фазой (

) считается, что в матрице расстояний

сохранены длины таких кратчайших

путей, которые содержат в качестве внутренних вершин только вершины из множества

(вершины grafoа мы нумеруем, начиная с единицы).

Иными словами, перед

-ой фазой величина

равна длине кратчайшего пути из вершины

в вершину

,

если этому пути разрешается заходить только в вершины с номерами, меньшими (начало и конец пути не считаются). Легко убедиться, что чтобы это свойство выполнилось для первой фазы, достаточно в матрицу расстояний

записать матрицу смежности grafoа:

— стоимости ребра из вершины

в вершину

. При этом,

если между какими-то vertexми ребра нет, то записать следует величину "бесконечность"

. Из вершины в саму

себя всегда следует записывать величину

, это критично для Algoritmoа.

Пусть теперь мы находимся на

-ой фазе, и хотим пересчитать матрицу

таким образом, чтобы

она соответствовала требованиям уже для

-ой фазы. Зафиксируем какие-то вершины

и

. У нас возникает

два принципиально разных случая:

● caminho mais curto из вершины

в вершину

, которому разрешено дополнительно проходить через

вершины

, совпадает с кратчайшим путём, которому разрешено проходить через

вершины множества

.

В этом случае величина

не изменится при переходе с

-ой на

-ую фазу.

● "Новый" caminho mais curto стал лучше "старого" пути.

Это означает, что "новый" caminho mais curto проходит через вершину

. Сразу отметим, что мы не потеряем

общности, рассматривая далее только простые пути (т.е. пути, не проходящие по какой-то вершине дважды).

Тогда заметим, что если мы разобьём этот "новый" путь вершиной

на две половинки (одна идущая

, а другая

—

), то каждая из этих половинок уже не заходит в вершину

. Но тогда получается, что длина каждой из

этих половинок была посчитана ещё на

-ой фазе или ещё раньше, и нам достаточно взять просто

сумму

, она и даст длину "нового" кратчайшего пути.

Объединяя эти два случая, получаем, что на

-ой фазе it is required пересчитать длины кратчайших путей между

всеми парами вершин

и

следующим образом:

new_d[i][j] = min (d[i][j], d[i][k] + d[k][j]);

Таким образом, вся работа, которую it is required произвести на

-ой фазе — это перебрать все пары вершин и

пересчитать длину кратчайшего пути между ними. В результате после выполнения

-ой фазы в матрице

расстояний

будет записана длина кратчайшего пути между

и

, либо

, если пути между этими vertexми

не существует. Последнее замечание, которое следует сделать, — то, что можно не создавать отдельную

матрицу

для временной матрицы кратчайших путей на

-ой фазе: все изменения можно делать сразу

в матрице

. В самом деле, если мы улучшили (уменьшили) какое-то значение в матрице расстояний, мы не могли ухудшить тем самым длину кратчайшего пути для каких-то других пар вершин, обработанных позднее.

Asymptotic complexity Algoritmoа, очевидно, составляет

.

Implementação

На Entrada программе подаётся grafo, заданный в виде матрицы смежности — двумерного arrayа

размера

,

в котором каждый 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 (достаточно большое, чтобы оно было больше длины любого пути в этом grafoе); тогда это edge всегда будет невыгодно брать, и Algoritmo сработает правильно. Правда, если не принять специальных мер, то при наличии в grafoе рёбер отрицательного веса,

в результирующей матрице могут появиться числа вида

,

, и т.д., которые, конечно, по-

прежнему означают, что между соответствующими vertexми вообще нет пути. Поэтому при наличии в grafoе отрицательных рёбер Algoritmo Флойда лучше написать так, чтобы он не выполнял переходы из тех состояний, в которых уже стоит "нет пути":

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]);

Восстановление самих путей

Легко поддерживать дополнительную информацию — так называемых "предков", по которым можно будет восстанавливать сам caminho mais curto между любыми двумя заданными vertexми в виде последовательности вершин.

Для этого достаточно кроме матрицы расстояний

поддерживать также матрицу предков

, которая

для каждой пары вершин будет содержать номер фазы, на которой было получено кратчайшее расстояние между ними. Понятно, что этот номер фазы является не чем иным, как "средней" вершиной искомого кратчайшего пути, и

теперь нам просто надо find caminho mais curto между vertexми

и

, а также между

и

. Отсюда получается простой рекурсивный Algoritmo восстановления кратчайшего пути.

Случай вещественных весов

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

если на первой фазе имела место ошибка

, то на второй итерации эта ошибка уже может превратиться в

,

на третьей — в

, и так далее. Чтобы этого не происходило, сравнения в Algoritmoе Флойда следует делать с учётом погрешности:

if (d[i][k] + d[k][j] < d[i][j] - EPS)

d[i][j] = d[i][k] + d[k][j];

Случай отрицательных циклов

Если в grafoе есть циклы отрицательного веса, то формально Floyd-Warshall algorithm неприменим к такому grafoу.

На самом же деле, для тех пар вершин

и

, между которыми нельзя зайти в цикл отрицательного вес,

Algoritmo отработает корректно. Для тех же пар вершин, ответа для которых не существует (по причине наличия отрицательного цикла на пути между ними), Algoritmo Флойда найдёт в качестве ответа какое-то number (возможно, сильно отрицательное, но не обязательно). Тем не менее, можно улучшить Algoritmo Флойда, чтобы он аккуратно обрабатывал такие пары вершин

и выводил для них, наExemplo,

. Для этого можно сделать, наExemplo, следующий критерий "не существования пути". Итак, пусть на данном

grafoе отработал обычный Algoritmo Флойда. Тогда между vertexми

и

не существует кратчайшего пути тогда

и только тогда, когда найдётся такая vertex

, достижимая из

и из которой достижима

, для которой

выполняется

. Кроме того, при использовании Algoritmoа Флойда для grafoов с отрицательными циклами следует помнить, что возникающие в процессе работы расстояния могут сильно уходить в минус, экспоненциально с каждой фазой. Поэтому следует принять меры против целочисленного переполнения, ограничив все расстояния снизу какой-

нибудь величиной (наExemplo,

). Более подробно об этой задаче см. отдельную статью: "Нахождение отрицательного цикла в grafoе".

C# solução

rascunho automático, revisar antes de enviar
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++ solução

correspondente/original
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 solução

rascunho automático, revisar antes de enviar
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];
}

Материал разбит как Algoritmoическая Tarefa: изучить постановку, понять асимптотику и реализовать Algoritmo на выбранном языке.

Vacancies for this task

vagas ativas with overlapping task tags are mostradas.

Todas as vagas
Ainda não há vagas ativas.