Алгоритм Голдберга-Тарьяна

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

Алгоритм Голдберга-Тарьяна (англ Goldberg-Tarjan) - алгоритм, решающий задачу нахождения максимального потока в транспортной сети за [math]O(VE \log(VE))[/math]. Можно считать модификацией Алгоритма Диница.

Идея

Вспомним Алгоритм Диница. Пусть есть сеть [math]G^0_f [/math] — некоторый ациклический граф, [math]S[/math], [math]T[/math] — исток и сток соответственно. Схема Алгоритма Диница :

  1. При помощи обхода в глубину находим путь из [math]S[/math] в [math]T[/math].
  2. Находим ребро с минимальной пропускной способностью
  3. Вдоль пути увеличиваем поток на минимальную пропускную способность

Попытаемся ускорить процесс поиска пути из [math]S[/math] в [math]T[/math]. Для этого, для каждой вершины зафиксируем какое-либо, не более, чем одно, исходящее из нее ребро. Граф ацикличен, значит зафиксированные ребра будут образовывать лес корневых деревьев. Корнем каждого дерева будет вершина, у которой нет зафиксированного ребра. В каждой вершине будем дополнительно хранить остаточную пропускную способность исходящего зафиксированного ребра.


Пусть каждое дерево поддерживает следующие операции:

  1. Вычислить минимум на пути от вершины до корня
  2. Прибавить константу к числам на пути от вершины до корня.
  3. Отрезать поддерево по ребру. Отрезанное поддерево отделяется и существует независимо от исходного дерева.
  4. Подвесить дерево. Пусть есть дерево с корнем [math]A[/math], дерево с вершиной [math]B[/math]. Операция позволяет Создать ребро из [math]A[/math] в [math]B[/math] и тем самым подвесить дерево к вершине.

Заметим, что именно эти операции поддерживает Linking-Cutting Tree и умеет их выполнять за [math]O(\log(N))[/math]

Поиск пути

Научимся находить путь из [math]S[/math] в [math]T[/math] в описанной выше сети при помощи леса корневых деревьев. Будем отдельно хранить дерево с потоками и дерево с пропускными способностями.

Пусть [math]U[/math] — корень дерева, в котором лежит [math]S[/math].

  • Заметим, что если [math]S[/math], [math]T[/math] лежат в одном дереве, то путь ищется легко. Это путь по дереву до корня.
  • Если [math]S[/math], [math]T[/math] лежат в разных деревьях. Просмотрим все ненасыщенные исходящие ребра. Будем рассматривать их также, как и в Алгоритме Диница, с глобальным итератором. Т.е начинать просмотр будем с последнего подошедшего ребра. Если ребро не подошло - больше его не рассматриваем. Пусть просматриваем какое-то ненасыщенное ребро, ведущее в некоторую вершину [math]V[/math]:
  1. Подвесим корень [math]U[/math] через это ребро к вершине V (Операция 3).
  2. В [math]U[/math] записываем число, равное остаточной пропускной способности ребра.
  • Будем повторять, пока [math]S[/math] и [math]T[/math] не окажутся в одном дереве.

Улучшение пути

Путь из [math]S[/math] в [math]T[/math] найден, теперь научимся улучшать путь. Нужно обновить значения пропускных способностей и потоков через вершины этого пути. Тогда:

  1. При помощи [math](1)[/math] запроса можно найти узкое место на этом пути и его пропускную способность.
  2. При помощи [math](2)[/math] запроса можно вычесть из всех ребер на этом пути пропускную способность узкого места, а также, прибавить ее к потоку.

Пусть после [math](2)[/math] запроса появилось нулевое ребро. Запрос минимума от [math]S[/math] до корня будет возвращать 0. Поэтому, такие ребра нужно отрезать, выполнив [math](4)[/math] запрос по этому ребру.

Алгоритм

Объединим вышесказанное в Алгоритм Голдберга-Татьяна. Пусть дана сеть. Требуется в этой сети найти поток [math]f(S, T) [/math] максимальной величины.

  1. Для каждого ребра [math](u, v)[/math] данной сети [math]G[/math] зададим [math]f(u, v) = 0[/math]
  2. Пока есть путь из [math]S[/math] в [math]T[/math]:
    1. Выполняем запрос [math](1)[/math], находим узкое место и пропускную способность
    2. Обновляем значения потока и пропускной способности при помощи запроса [math](2)[/math]
    3. Обрезаем нулевые ребра при помощи запроса [math](4)[/math]

Время работы

Linking-Cutting Tree выполняет все вышеописанные запросы за [math]O(\log(N))[/math], оценим время работы алгоритма.

Очевидно, что просмотров ребер суммарно [math]O(\log(E))[/math], как и в алгоритме Диница. Переход к следующему ребру происходит в следующих случаях:

  1. просматриваемое ребро насыщено
  2. дерево разрезается по нулевому ребру
  3. ребро не лежит в сети кратчайших путей

На каждый просмотр тратится не [math]O(1)[/math] а [math]O(\log(V))[/math], потому что перед тем, как посмотреть на следующее ребро делается запрос. Значит время работы этой части — [math]O(E \log(V))[/math].

Следующий шаг в алгоритме Диница - сумма длин путей. Раньше считалось за [math]O(V^2)[/math]. Потому, что на каждый путь DFS тратил время, пропорциональное длине этого пути. Сейчас тратится только [math]O(\log(V))[/math] на каждый путь. Если путь найден, значит до него дошли, значит это соответствует одному запросу. Поэтому тратим на каждый путь тоже логарифм [math]O(\log(V))[/math].

Тогда имеем ассимптотику [math]O(E\log(V) + V \log(V)) = O(VE \log(VE))[/math]