Алгоритм Флойда

Материал из Викиконспекты
Перейти к: навигация, поиск

Алгоритм Флойда (алгоритм Флойда–Уоршелла) — алгоритм нахождения длин кратчайших путей между всеми парами вершин во взвешенном ориентированном графе. Работает корректно, если в графе нет циклов отрицательной величины, а в случае, когда такой цикл есть, позволяет найти хотя бы один такой цикл. Алгоритм работает за Θ(n3) времени и использует Θ(n2) памяти. Разработан в 1962 году.

Алгоритм

Постановка задачи

Текущий (синий) путь и потенциально более короткий (красный)

Дан взвешенный ориентированный граф G(V,E), в котором вершины пронумерованы от 1 до n.

ωuv={weight of uv,if uvE+,if uvE
Требуется найти матрицу кратчайших расстояний d, в которой элемент dij либо равен длине кратчайшего пути из i в j, либо равен +, если вершина j не достижима из i.

Описание

Обозначим длину кратчайшего пути между вершинами u и v, содержащего, помимо u и v, только вершины из множества {1..i} как d(i)uv, d(0)uv=ωuv.

На каждом шаге алгоритма, мы будем брать очередную вершину (пусть её номер — i) и для всех пар вершин u и v вычислять d(i)uv=min(d(i1)uv,d(i1)ui+d(i1)iv). То есть, если кратчайший путь из u в v, содержащий только вершины из множества {1..i}, проходит через вершину i, то кратчайшим путем из u в v является кратчайший путь из u в i, объединенный с кратчайшим путем из i в v. В противном случае, когда этот путь не содержит вершины i, кратчайший путь из u в v, содержащий только вершины из множества {1..i} является кратчайшим путем из u в v, содержащим только вершины из множества {1..i1}.

Код (в первом приближении)

d(0)uv=w
for iV
    for uV
        for vV
            d(i)uv=min(d(i1)uv,d(i1)ui+d(i1)iv)

В итоге получаем, что матрица d(n) и является искомой матрицей кратчайших путей, поскольку содержит в себе длины кратчайших путей между всеми парами вершин, имеющих в качестве промежуточных вершин вершины из множества {1..n}, что есть попросту все вершины графа. Такая реализация работает за Θ(n3) времени и использует Θ(n3) памяти.

Код (окончательный)

Утверждается, что можно избавиться от одной размерности в массиве d, т.е. использовать двумерный массив duv. В процессе работы алгоритма поддерживается инвариант ρ(u,v)duvd(i)uv, а, поскольку, после выполнения работы алгоритма ρ(u,v)=d(i)uv, то тогда будет выполняться и ρ(u,v)=duv.

Утверждение:
В течение работы алгоритма Флойда выполняются неравенства: ρ(u,v)duvd(i)uv.

После инициализации все неравенства, очевидно, выполняются. Далее, массив d может измениться только в строчке 5.

Докажем второе неравенство индукцией по итерациям алгоритма.

Пусть также duv — значение duv сразу после i1 итерации.

Покажем, что duvd(i)uv, зная, что duvd(i1)uv.

Рассмотрим два случая:

  • Значение d(i)uv стало меньше, чем d(i1)uv. Тогда d(i)uv=d(i1)ui+d(i1)iv (выполняется на шаге i1, по индукционному предположению) dui+div (в силу выполнения 7-ой строчки алгоритма на i-ой итерации и невозрастания элементов массива d) duv.
  • В ином случае всё очевидно: d(i)uv=d(i1)uvduvduv, и неравенство тривиально.


Докажем первое неравенство от противного.

Пусть неравенство было нарушено, рассмотрим момент, когда оно было нарушено впервые. Пусть это была i-ая итерация и в этот момент изменилось значение duv и выполнилось ρ(u,v)>duv. Так как duv изменилось, то duv=dui+div (так как ранее u,vV:ρ(u,v)duv) ρ(u,i)+ρ(i,v) (по неравенству треугольника) ρ(u,v).

Итак duvρ(u,v) — противоречие.
func floyd(w):
    d = ω                // изначально d=ω
    for iV
        for uV
            for vV
                d[u][v] = min(d[u][v], d[u][i] + d[i][v])

Данная реализация работает за время Θ(n3), но требует уже Θ(n2) памяти. В целом, алгоритм Флойда очень прост, и, поскольку в нем используются только простые операции, константа, скрытая в определении Θ весьма мала.

Пример работы

i=0 i=1 i=2 i=3 i=4
0.png Floyd 1.png Floyd 2.png Floyd algo 3.png Floyd 4.png
(×16×41×1×) (×16×41×1×) (×152×41×1×) (×152×41×1×) (×132×21×1×)

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

Алгоритм Флойда легко модифицировать таким образом, чтобы он возвращал не только длину кратчайшего пути, но и сам путь. Для этого достаточно завести дополнительный массив next, в котором будет храниться номер вершины, в которую надо пойти следующей, чтобы дойти из u в v по кратчайшему пути.

Модифицированный алгоритм

func floyd(w):
    d = ω               // изначально d=ω
    for iV
        for uV
            for vV
                if d[u][i] + d[i][v] < d[u][v]
                    d[u][v] = d[u][i] + d[i][v]
                    next[u][v] = i
func getShortestPath(u, v):
    if d[u][v] == 
        print "No path found"                 // между вершинами u и v нет пути
    c = u
    while c != v
        print c
        c = next[c][v]
    print v

Нахождение отрицательного цикла

Утверждение:
При наличии цикла отрицательного веса в матрице D появятся отрицательные числа на главной диагонали.
Так как алгоритм Флойда последовательно релаксирует расстояния между всеми парами вершин (i,j), в том числе и теми, у которых i=j, а начальное расстояние между парой вершин (i,i) равно нулю, то релаксация может произойти только при наличии вершины k такой, что d[i][k]+d[k][i]<0, что эквивалентно наличию отрицательного цикла, проходящего через вершину i.

Из доказательства следует, что для поиска цикла отрицательного веса необходимо, после завершения работы алгоритма, найти вершину i, для которой d[i][i]<0, и вывести кратчайший путь между парой вершин (i,i). При этом стоит учитывать, что при наличии отрицательного цикла расстояния могут уменьшаться экспоненциально. Для предотвращения переполнения все вычисления стоит ограничивать снизу величиной , либо проверять наличие отрицательных чисел на главной диагонали во время подсчета.

Построение транзитивного замыкания

Сформулируем нашу задачу в терминах графов: рассмотрим граф G=(V,E),|V|=n, соответствующий отношению R. Тогда необходимо найти все пары вершин (x,y), соединенных некоторым путем. Иными словами, требуется построить новое отношение T, которое будет состоять из всех пар (x,y) таких, что найдется последовательность x=x0,x1,,xk=y, где (xi1,xi)R,i=1,2,,k.

Псевдокод

Изначально матрица W заполняется соответственно отношению R, то есть W[i][j]=[(i,j)R]. Затем внешним циклом перебираются все элементы k множества X и для каждого из них, если он может использоваться, как промежуточный для соединения двух элементов i и j, отношение T расширяется добавлением в него пары (i,j).

for k = 1 to n
    for i = 1 to n
        for j = 1 to n
            W[i][j] = W[i][j] or (W[i][k] and W[k][j])

Доказательство

<wikitex>Назовем промежуточной вершину некоторого пути p=v0,v1,,vk, принадлежащую множеству вершин этого пути и отличающуюся от начальной и конечной вершин, то есть принадлежащую {v1,v2,,vk1}. Рассмотрим произвольную пару вершин i,jV и все пути между ними, промежуточные вершины которых принадлежат множеству вершин с номерами {1,2,,k}. Пусть p - некоторый из этих путей. Докажем по индукции (по числу промежуточных вершин в пути), что после i-ой итерации внешнего цикла будет верно утверждение - если в построенном графе между выбранной парой вершин есть путь, содержащий в качестве промежуточных только вершины из множества вершин с номерами {v1,v2,,vi}, то между ними будет ребро.

  • База индукции. Если у нас нет промежуточных вершин, что соответствует начальной матрице смежности, то утверждение выполнено: либо есть ребро (путь не содержит промежуточных вершин), либо его нет.
  • Индуктивный переход. Пусть предположение выполнено для i=k1. Докажем, что оно верно и для i=k Рассмотрим случаи (далее под вершиной будем понимать ее номер для простоты изложения):
    • k не является промежуточной вершиной пути p. Тогда все его промежуточные пути принадлежат множеству вершин с номерами {1,2,,k1}{1,2,,k}, то есть существует путь с промежуточными вершинами в исходном множестве. Это значит W[i][j] будет истиной. В противном случае W[i][j] будет ложью и на k-ом шаге ею и останется.
    • k является промежуточной вершиной предполагаемого пути p. Тогда этот путь можно разбить на два пути: ip1kp2j. Пусть как p1, так и p2 существуют. Тогда они содержат в качестве промежуточных вершины из множества {1,2,,k1}{1,2,,k} (так как вершина k - либо конечная, либо начальная, то она не может быть в множестве по нашему определению). Тогда W[i][k] и W[k][j] истинны и по индуктивному предположению посчитаны верно. Тогда и W[i][j] тоже истина. Пусть какого-то пути не существует. Тогда пути p тоже не может существовать, так как добраться, например, от вершины i до k по вершинам из множества {1,2,,k} невозможно по индуктивному предположению. Тогда вся конъюнкция будет ложной, то есть такого пути нет, откуда W[i][j] после итерации будет ложью.

Таким образом, после завершения внешнего цикла у нас будет W[i][j]=true, если между этими вершинами есть путь, содержащий в качестве промежуточных вершин из множества всех остальных вершин графа, что и есть транзитивное замыкание. </wikitex>

Оптимизация с помощью битовых масок

Строки матрицы W можно хранить с помощью массива битовых маск длиной k. Тогда последний цикл будет выполняться в k раз быстрее и сложность алгоритма снижается до O(n3k).
Пример реализации оптимизации с помощью битмасок:

unsigned int W[N][N / 32 + 1]
...
for k = 1 to n
    for i = 1 to n
        if бит с номером (k % 32) в маске a[i][k / 32] единичный
            for j = 1 to n
                W[i][j] = W[i][j] or W[k][j]

Источники информации

  • Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд — М.: Издательский дом «Вильямс», 2009. — ISBN 978-5-8459-0857-5.
  • Романовский И. В. Дискретный анализ: Учебное пособие для студентов, специализирующихся по прикладной математике и информатике. Изд. 3-е. — СПб.: Невский диалект, 2003. — 320 с. — ISBN 5-7940-0114-3.
  • Википедия - Алгоритм Флойда — Уоршелла
  • Wikipedia - Floyd–Warshall algorithm