1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10

# теория · шаг 2 из 10

Форд-Фалкерсон: право передумать

Коротко

Жадность на потоках не работает: пустив поток по неудачному пути, ты можешь перекрыть себе дорогу к оптимуму. Лечится это одной идеей - остаточной сетью. Рядом с каждым ребром, по которому уже идёт поток, заводится ОБРАТНОЕ ребро той же величины: разрешение отменить сделанный выбор. Дальше алгоритм прост до неприличия: ищи любой путь из s в t по рёбрам с ненулевым остатком (дополняющий путь), пускай по нему столько, сколько пропускает самое узкое ребро, повторяй. Когда пути кончились - поток максимален, а вершины, достижимые из s в остаточной сети, задают минимальный разрез. Это теорема о максимальном потоке и минимальном разрезе.

Если ты умеешь строить остаточную сеть, знаешь, зачем в ней обратные рёбра, и можешь по готовому потоку предъявить минимальный разрез, листай до «Теперь сам».

Как думаешь, почему жадности мало

Сеть из четырёх вершин, все пропускные способности равны единице: s→a, s→b, a→b, a→t, b→t. Пусти поток жадно: возьми первый попавшийся путь s → a → b → t и пропусти по нему единицу. Теперь посмотри, остался ли ещё хоть один путь из s в t по незанятым рёбрам.

Не осталось: s→a занято, a→b занято, b→t занято. Жадность говорит «величина 1, всё». А глазами видно 2: s → a → t и s → b → t. Одно неудачное решение отрезало половину ответа - и исправлять его надо не перебором, а механизмом.

На что это похоже

Касса с очередью в два окна. Ты встал в левое, оно застряло, а правое поехало. Умение перейти - это и есть обратное ребро: ты не отменяешь свой приход в кассу, ты отменяешь СВОЙ ВЫБОР окна. Алгоритм ниже устроен так же: он не разбирает поток заново, он лишь разрешает части потока переложиться на другой путь, причём разрешение это оформлено обычным ребром в графе.

Механизм: остаточная сеть и дополняющий путь

остаточная пропускная способность:
    c_f(u, v) = c(u, v) − f(u, v)        сколько ещё влезет вперёд
    c_f(v, u) = c(v, u) + f(u, v)        сколько можно отменить назад
                                         (c(v, u) = 0, если встречного ребра нет)

остаточная сеть - рёбра с c_f > 0

алгоритм Форда-Фалкерсона:
    поток пустой
    пока в остаточной сети есть путь из s в t:
        узкое место = минимум c_f по рёбрам пути
        прибавить узкое место к потоку вдоль пути
        пересчитать остаточную сеть
    вернуть поток

Пускать поток «вдоль пути» значит: для прямого ребра прибавить, для обратного - отнять у того ребра, которому оно принадлежит. Отсюда и право передумать: проход по обратному ребру физически означает «часть потока, которая шла здесь, дальше пойдёт иначе».

def max_flow(cap, s, t):            # cap[u][v] - матрица пропускных способностей
    flow = 0
    while True:
        path = find_path(cap, s, t)  # любой путь по рёбрам с cap > 0:
                                     # список пар (u, v); как его искать -
                                     # следующий урок, там это и решает всё
        if not path:
            return flow
        delta = min(cap[u][v] for u, v in path)
        for u, v in path:
            cap[u][v] -= delta       # вперёд осталось меньше
            cap[v][u] += delta       # назад стало можно больше
        flow += delta

Обрати внимание на приём: отдельной «остаточной сети» в коде нет - матрица cap и ЕСТЬ остаточная сеть, а сам поток по ребру вычисляется как «сколько из него ушло». Так пишут почти всегда.

Матрицу заводят размера V × V и заполняют нулями, а потом проставляют пропускные способности рёбер: cap = [[0] * V for _ in range(V)], затем cap[u][v] = c для каждого ребра. Нули важны - именно в них и растут обратные рёбра.

Правило

Остаточная сеть - это исходные рёбра с недоиспользованным запасом плюс обратные рёбра, равные уже пущенному потоку. Дополняющий путь - любой путь из s в t по остаточной сети; по нему пускают столько, сколько пропускает самое узкое ребро пути. Когда дополняющих путей не осталось, поток максимален. ТЕОРЕМА: величина максимального потока равна пропускной способности минимального разреза, и сам этот разрез строится так - S = вершины, достижимые из s в остаточной сети, T = все остальные. При целых пропускных способностях алгоритм заканчивается, а максимальный поток целый.

Разбор: как обратное ребро возвращает потерянную единицу

Та же сеть из вступления: все пять рёбер пропускают по единице.

шаг 1. путь s → a → b → t, узкое место 1
       прямые рёбра:  c_f(s,a)=0  c_f(a,b)=0  c_f(b,t)=0
       обратные:      c_f(a,s)=1  c_f(b,a)=1  c_f(t,b)=1
       величина потока: 1

шаг 2. ищем путь в ОСТАТОЧНОЙ сети:
       s → b (осталось 1) → a (обратное ребро, 1) → t (осталось 1)
       узкое место 1, величина потока: 2

шаг 3. путей из s больше нет: c_f(s,a)=0 и c_f(s,b)=0

Что произошло на втором шаге физически: единица, шедшая a → b, отменена, вместо неё в b приходит поток прямо из s, а освободившаяся единица из a уходит в t. Итоговый поток - s→a→t и s→b→t, ровно тот, который видно глазами. Алгоритм пришёл к нему, ни разу ничего не перебирая и не «переигрывая» шаг 1: он просто прошёл по обратному ребру.

Проверим ответ разрезом: S = {s}, T = {a, b, t}, рёбра слева направо - s→a (1) и s→b (1), пропускная способность 2. Совпало с потоком, значит оба оптимальны.

Разбор: минимальный разрез достаётся бесплатно

Возьмём сеть из прошлого урока: s→a = 6, a→b = 2, b→t = 6, s→c = 4, c→t = 3. Прогоним алгоритм.

путь s → a → b → t, узкое место min(6, 2, 6) = 2      поток 2
путь s → c → t,     узкое место min(4, 3) = 3         поток 5
дополняющих путей нет

Теперь достаём разрез. Идём обходом из s по рёбрам с ненулевым остатком:

c_f(s,a) = 6 − 2 = 4 > 0   →  a достижима
c_f(a,b) = 2 − 2 = 0       →  через b не пройти
c_f(s,c) = 4 − 3 = 1 > 0   →  c достижима
c_f(c,t) = 3 − 3 = 0       →  через t не пройти
S = {s, a, c},  T = {b, t}

Пропускная способность этого разреза - c(a,b) + c(c,t) = 2 + 3 = 5, и она равна величине потока. Это и есть доказательство теоремы в миниатюре: рёбра из S в T обязаны быть насыщены (иначе сосед был бы достижим), а рёбра из T в S обязаны быть пусты (иначе обратное ребро вело бы в T). Подставь это в неравенство из прошлого урока - оба «меньше либо равно» превращаются в равенства.

Отсюда важный практический вывод: минимальный разрез не ищут отдельно. Его получают из готового потока одним обходом за O(V + E) - и именно так отвечают на вопрос «что именно является узким местом», а не «сколько».

Что ломается без этого

Первое: писать жадный алгоритм без обратных рёбер. Он даёт правдоподобный, но меньший ответ, и на маленьких примерах может совпадать с верным - дефект всплывёт на проде. Второе: искать узкое место пути после того, как прибавил поток, а не до: величины изменятся. Третье: забыть, что обратное ребро может существовать и «само по себе», если в исходной сети есть рёбра в обе стороны - их остатки складываются, и путать их нельзя. Четвёртое: считать, что раз путей нет, то надо остановиться, но при этом искать пути только по ИСХОДНЫМ рёбрам - это та же жадность, вид сбоку. Пятое: применять алгоритм к вещественным пропускным способностям и ждать завершения: при иррациональных числах Форд-Фалкерсон может работать бесконечно, сходясь к величине, которая даже не является максимумом. При целых всё хорошо: каждый шаг прибавляет минимум единицу, а величина ограничена сверху - это ровно то рассуждение про меру, с которого начинался курс.

Зачем это в работе

Форд-Фалкерсон - не столько готовый алгоритм, сколько каркас: все практические алгоритмы потока (следующий урок) отличаются от него только тем, КАК выбирается дополняющий путь. Пара «поток плюс разрез» ценна сама по себе: величина отвечает «сколько», а разрез - «что чинить». В сетевом планировании это список каналов, которые надо расширять; в сегментации изображения - граница между объектом и фоном; в задаче выбора проектов - список того, от чего надо отказаться. Ещё одно следствие, которым пользуются постоянно: при целых пропускных способностях максимальный поток целый. Именно поэтому задачи вида «назначить людей на работы» решаются потоком и дают честный ответ «да/нет» на каждое назначение, а не «полчеловека».

Теперь сам

Сеть: s→a = 3, s→b = 2, a→b = 5, a→t = 2, b→t = 3. Прогони алгоритм, начав с пути s → a → b → t, и предъяви минимальный разрез.

Ответ. Шаг 1: путь s → a → b → t, узкое место min(3, 5, 3) = 3. Поток 3. Остатки: c_f(s,a) = 0, c_f(a,b) = 2, c_f(b,t) = 0, обратные - c_f(a,s) = 3, c_f(b,a) = 3, c_f(t,b) = 3.

Шаг 2: ищем путь. Из s доступно только s→b (остаток 2). Из b вперёд нельзя (b→t насыщено), но есть обратное ребро b→a с остатком 3. Из a - a→t с остатком 2. Путь s → b → a → t, узкое место min(2, 3, 2) = 2. Поток 5.

Шаг 3: из s обе исходящие насыщены (c_f(s,a) = 0, c_f(s,b) = 0), путей нет. Ответ: 5.

Разрез: достижимые из s в остаточной сети - только сам s. Значит S = {s}, T = {a, b, t}, пропускная способность c(s,a) + c(s,b) = 3 + 2 = 5. Совпало с потоком.

Обрати внимание на шаг 2: поток прошёл b → a против исходного ребра a→b. Физически это значит, что из трёх единиц, ушедших по a→b, две вернулись обратно и поехали в t напрямую, а их место в b заняли две единицы из s. Ни одного пересчёта с нуля - только обратное ребро.

Главное

Остаточная сеть - прямые рёбра с оставшимся запасом плюс обратные рёбра, равные уже пущенному потоку; обратное ребро - это право отменить сделанный выбор. Форд-Фалкерсон пускает поток по любому дополняющему пути в остаточной сети, беря величину узкого места, и останавливается, когда путей не осталось. Теорема о максимальном потоке и минимальном разрезе: величина максимального потока равна пропускной способности минимального разреза, а сам разрез - это «достижимые из s в остаточной сети» против всех остальных, и достаётся он одним обходом. При целых пропускных способностях алгоритм конечен, а максимальный поток цел.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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