# теория · шаг 7 из 10
Дерево Гомори-Ху: минимальные разрезы для всех пар за n − 1 поток
Коротко
В неориентированной сети часто нужен минимальный разрез не для одной пары s, t, а для ВСЕХ пар:
какие два узла связаны надёжнее всего, где самое слабое место между любыми двумя площадками. Пар
n·(n−1)/2, но для ответов достаточно семейства из n−1 разрезов, представленного деревом.
Самих минимальных разрезов может быть больше; различных значений их величины не больше n−1.
Дерево Гомори-Ху - дерево на тех же вершинах с весами на рёбрах, где минимальный
разрез между любыми u и v равен наименьшему весу на пути между ними в дереве. Строится оно n − 1
вычислением максимального потока. Наименьший вес - минимальная суммарная ёмкость
разреза всей сети. При единичных ёмкостях это рёберная связность: минимальное
число рёбер, удаление которых делает граф несвязным.
Если ты знаешь, что хранит дерево Гомори-Ху, как его строят n − 1 потоком и как по нему ответить на запрос о разрезе, листай до «Теперь сам».
Как думаешь, сколько разных разрезов у всех пар
Сеть каналов связи между 24 площадками. Для каждой пары площадок нужно знать, какую суммарную
пропускную способность надо выбить, чтобы их разъединить. Это 276 пар - 276 вычислений
максимального потока.
Прежде чем читать дальше, попробуй угадать: если разрез {a, b} | {c, d} минимальный между a и c,
то для пары b и d, стоящих по разные стороны того же разреза, он тоже годится как разрез. Может
быть, много пар делят одни и те же разрезы? Сколько тогда различных ответов среди 276?
На что это похоже
Генеалогическое древо слабых мест. Вместо таблицы «насколько крепко связаны каждые двое» в семье рисуют дерево, где у каждой ветки подписано, насколько она прочна. Чтобы узнать, как прочна связь двух родственников, находишь их на дереве и смотришь на самую слабую ветку между ними. Таблица на сотни ячеек свелась к дереву на два десятка подписей.
Механизм: дерево разрезов и построение Гасфилда
СВОЙСТВО
мин. разрез(u, v) = наименьший вес ребра на пути u … v в дереве
и более того: удаление этого ребра делит вершины ровно так,
как этот минимальный разрез
ПОСТРОЕНИЕ (вариант Гасфилда, без сжатия вершин)
otec[i] = 0 для всех i
для i = 1 .. n−1:
t = otec[i]
найти мин. разрез между i и t - величина c, сторона S ∋ i
для j != i, j != 0:
если j ∈ S и otec[j] == t: otec[j] = i
если t != 0 и otec[t] ∈ S:
otec[i], otec[t] = otec[t], i
вес[i], вес[t] = вес[t], c
иначе: вес[i] = c
Почему достаточно n−1 представителей. Можно выбрать согласованную систему минимальных
разрезов, которая укладывается в дерево: каждое ребро задаёт один разрез, рёбер n−1.
Любая величина ответа для пары равна весу одного из этих рёбер, поэтому различных
величин не больше n−1. Это не ограничивает число всех оптимальных разделений.
В цикле из четырёх единичных рёбер их шесть: четыре изолируют одну вершину, ещё два
разделяют цикл на пары соседей. Все имеют ёмкость 2, то есть значение ответа всего одно.
Почему пересадка otec[j] = i. Вершины, оказавшиеся на стороне i в разрезе между i и otec[i],
дальше «висят» на i. Переподвешивать иногда нужно уже обработанных потомков тоже:
их известный вес при этом сохраняется. Если старый родитель вершины t оказался на
стороне i, меняем местами i и t в дереве, перенося старый вес и новый разрез.
Без этой перестройки недостаточно проверить только величины ответов:
разбиение, полученное удалением ребра, тоже должно иметь указанную ёмкость.
Объясним сохранение известного веса при переподвешивании обработанной вершины j. До шага j и i - дети одного родителя t. Удаление ребра j–t отделяет поддерево j ёмкости α. Вершина i в него не входит. После подвешивания j под i это же поддерево остаётся по одну сторону нового ребра j–i, поэтому ёмкость его разреза не изменилась и равна α.
Почему α остаётся минимумом для новой пары? Новый i–t-разрез S содержит j, поэтому его ёмкость β не меньше минимальной j–t-ёмкости α. Любой разрез между j и i отделяет t от одной из этих вершин, значит стоит не меньше α или β, а потому не меньше α. Старое поддерево j даёт разрез ровно α, что завершает обоснование. Использован инвариант: для уже обработанных рёбер разделение дерева реализует сохранённую минимальную ёмкость. Для ещё необработанной вершины вес определяется позднее собственным вызовом потока.
Наименьший вес рёбер дерева - минимальная ёмкость разреза всей сети: меньше разреза у сети нет ни между какими двумя вершинами.
Правило
Дерево Гомори-Ху хранит минимальные разрезы всех пар неориентированной сети: разрез между u и v
равен наименьшему весу на пути между ними в дереве. Его строят n − 1 максимальным потоком: для
каждой вершины i - разрез с текущим родителем, затем переподвешивание вершин и,
когда необходимо, обмен положения i и её родителя с переносом весов.
При единичных ёмкостях минимум веса в дереве равен рёберной связности.
Разбор: четыре площадки
каналы: a–b 3, b–c 2, a–c 1, c–d 4 порядок вершин: a b c d
i = b, родитель a: мин. разрез a | b = 4 ({a} против остальных: 3 + 1)
сторона b: {b c d} → c и d (родитель a) переподвешиваем к b
i = c, родитель b: мин. разрез c | b = 3 ({c d} против {a b}: 2 + 1)
сторона c: {c d} → d переподвешиваем к c
i = d, родитель c: мин. разрез d | c = 4
дерево: a —4— b —3— c —4— d
запрос a … d: путь a–b–c–d, наименьшее 3
проверка: разрез {c d} | {a b} = b–c 2 + a–c 1 = 3 ✓
Три потока ответили на все шесть пар. Самое слабое место всей сети - ребро дерева весом 3: разделить
сеть дешевле, чем отрезав {c, d}, нельзя.
Разбор: 24 площадки и проверка всех пар
Программа строит дерево Гомори-Ху для случайной сети и затем проверяет его ответ для каждой из 276
пар отдельным вычислением потока.
import random
from collections import deque
rng = random.Random(20)
n = 24
w = [[0] * n for _ in range(n)] # неориентированная сеть каналов
for _ in range(70):
u, v = rng.randrange(n), rng.randrange(n)
if u != v:
c = rng.randint(1, 9)
w[u][v] += c
w[v][u] += c
potokov = 0
def razrez(s, t): # мин. разрез: величина и сторона s
global potokov
potokov += 1
cap = [row[:] for row in w]
znach = 0
while True:
otec, och = {s: None}, deque([s])
while och and t not in otec:
u = och.popleft()
for v in range(n):
if cap[u][v] > 0 and v not in otec:
otec[v] = u
och.append(v)
if t not in otec:
return znach, set(otec) # достижимые из s - его сторона
put, v = [], t
while otec[v] is not None:
put.append((otec[v], v))
v = otec[v]
d = min(cap[a][b] for a, b in put)
for a, b in put:
cap[a][b] -= d
cap[b][a] += d
znach += d
otec, ves = [0] * n, [0] * n # дерево Гомори-Ху по Гасфилду
for i in range(1, n):
t = otec[i]
cut, storona = razrez(i, t)
for j in range(1, n):
if j != i and j in storona and otec[j] == t:
otec[j] = i
if t != 0 and otec[t] in storona:
otec[i], otec[t] = otec[t], i
ves[i], ves[t] = ves[t], cut
else:
ves[i] = cut
def po_derevu(u, v): # минимум рёбер дерева на пути u–v
sos = {x: [] for x in range(n)}
for i in range(1, n):
sos[i].append((otec[i], ves[i]))
sos[otec[i]].append((i, ves[i]))
luchshee, och = {u: float("inf")}, deque([u])
while och:
x = och.popleft()
for y, c in sos[x]:
if y not in luchshee:
luchshee[y] = min(luchshee[x], c)
och.append(y)
return luchshee[v]
print("потоков на дерево:", potokov)
potokov = 0
for u in range(n):
for v in range(u + 1, n):
assert po_derevu(u, v) == razrez(u, v)[0]
print("потоков на проверку всех пар:", potokov, "- все совпали")
for edge in range(1, n):
side, stack = set(), [edge]
while stack:
v = stack.pop()
if v in side:
continue
side.add(v)
for j in range(1, n):
if j != edge:
if j == v:
stack.append(otec[j])
elif otec[j] == v:
stack.append(j)
cap = sum(w[a][b] for a in side
for b in range(n) if b not in side)
assert cap == ves[edge]
print("разрезы по рёбрам дерева:", n - 1, "- все совпали")
print("минимальная ёмкость разреза сети:", min(ves[1:]))
потоков на дерево: 23
потоков на проверку всех пар: 276 - все совпали
разрезы по рёбрам дерева: 23 - все совпали
минимальная ёмкость разреза сети: 10
Проверять только величины недостаточно. Возьми треугольник с ёмкостями
0–1:1, 0–2:2, 1–2:3. Разрез между 1 и 0 стоит 3 и отделяет {1,2};
между 2 и 1 стоит 4 и отделяет {0,2}. Если только подвесить 2 под 1,
получится дерево 0–1 веса 3 и 1–2 веса 4. Величины ответов верны,
но удаление второго ребра изолирует {2}, чей настоящий разрез стоит 5.
Условие otec[t] ∈ S исправляет именно этот случай: после обмена дерево
имеет рёбра 0–2 веса 3 и 2–1 веса 4. Теперь удаление рёбер даёт разрезы
нужной ёмкости. Поэтому программа проверяет и все пары, и каждое разделение
по ребру, суммируя ёмкости пересекающих его каналов исходной сети.
Двадцать три потока построили дерево, двести семьдесят шесть проверок подтвердили каждый ответ.
Наименьший разрез сети - 10: меньшей пропускной способностью её не разделить нигде.
Что ломается без этого
Первая ошибка - строить дерево для ОРИЕНТИРОВАННОЙ сети: там разрез u → v и v → u различаются, и
симметричное дерево всех пар в общем случае не существует. Вторая - брать в качестве стороны разреза любые
вершины, а не достижимые из i в остаточной сети: переподвешивание выйдет неверным, и дерево начнёт
врать для части пар. Третья - путать дерево Гомори-Ху с остовным деревом сети: его рёбра не обязаны
быть каналами, а веса - не ёмкости каналов, а величины разрезов (в примере ребро дерева a–b весит
4, хотя канал a–b - 3). Четвёртая - вычислять рёберную связность n·(n−1)/2 потоками: хватает
n − 1 даже без дерева - зафиксировать одну вершину и найти разрезы до всех остальных.
Зачем это в работе
Анализ надёжности сетей: насколько трудно разъединить два дата-центра, какая пара площадок связана
хуже всех, какие каналы стоит дублировать. Кластеризация графов: рёбра дерева с малыми весами - места,
где граф естественно распадается на группы. Задачи на «все пары» вообще: если ответ для пары зависит
от минимального разреза, дерево Гомори-Ху сводит n² вычислений к n и даёт структуру, по которой
отвечают на запрос обходом дерева.
Теперь сам
Дерево Гомори-Ху: p —5— q —2— r —7— s, r —6— x. Каков минимальный разрез между p и x? Между s
и x? Чему равна минимальная ёмкость разреза всей сети?
Ответ. p … x: путь p–q–r–x, веса 5, 2, 6, минимум 2. s … x: путь s–r–x, веса 7, 6,
минимум 6. Наименьшая ёмкость - минимальный вес дерева, 2: ребро q–r делит сеть на {p, q} и
{r, s, x}, и это самый дешёвый разрез во всей сети.
Главное
Для всех пар неориентированной сети достаточно n−1 разрезов-представителей:
дерево Гомори-Ху даёт величину разреза между u и v как минимум веса на пути.
Самих оптимальных разбиений может быть больше. Строится дерево n−1
потоком с переподвешиванием вершин на сторону найденного разреза. Наименьший вес дерева -
минимальная ёмкость разреза всей сети. В программе 23 потока заменили 276, и каждая пара подтвердилась.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий