1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

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

Кун, Кёниг и Холл: алгоритм и две теоремы

Коротко

Алгоритм Куна - это Форд-Фалкерсон, записанный без слова «поток». Он перебирает левые вершины и для каждой пытается найти дополняющий путь обходом в глубину; нашёл - паросочетание выросло на единицу, не нашёл - эта вершина так и останется свободной. Цена O(V·E). Две теоремы объясняют ответ БЕЗ алгоритма. Кёниг: в двудольном графе размер максимального паросочетания равен размеру минимального вершинного покрытия. Холл: паросочетание, занимающее всю левую долю, существует тогда и только тогда, когда любое подмножество левых вершин имеет не меньше соседей, чем его размер. Обе дают сертификат: «столько-то - и вот почему больше нельзя».

Если ты умеешь написать Куна, знаешь теорему Кёнига вместе с построением покрытия и формулируешь условие Холла с дефицитом, листай до «Теперь сам».

Как думаешь, почему трое не разойдутся

Трое работников, и все трое умеют только одну и ту же задачу. Сколько работ раздашь? Одну, это очевидно. А теперь скажи не «сколько», а ПОЧЕМУ: какое короткое наблюдение доказывает, что двух быть не может, не перебирая вариантов?

Наблюдение такое: все рёбра графа упираются в одну задачу. Достаточно «накрыть» эту одну вершину - и накрыты все рёбра. Размер такого накрытия и есть предел для паросочетания: одно ребро покрытия не может обслужить две пары. Это и есть теорема Кёнига, увиденная на трёх вершинах.

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

Сторож и коридоры. Паросочетание - это непересекающиеся маршруты, вершинное покрытие - посты охраны, мимо которых не пройти. Чем больше непересекающихся маршрутов, тем больше постов нужно; чем меньше постов хватает, тем меньше маршрутов возможно. В двудольном графе эти два числа СОВПАДАЮТ, и потому каждое из них доказывает другое.

Механизм: обход в глубину по чередующимся путям

Кун:
    match_r[j] - кто занял правую вершину j, или -1
    для каждой левой вершины v:
        пометки used[] обнулить
        если try(v): размер += 1

    try(v):
        для каждого соседа j вершины v:
            если j не помечена:
                пометить j
                если match_r[j] == -1 или try(match_r[j]):
                    match_r[j] = v
                    вернуть True
        вернуть False

Рекурсия try(match_r[j]) и есть «переставить»: занявшего правую вершину просят подвинуться, и он ищет себе другое место по тем же правилам. Пометки used не дают ходить по кругу и обнуляются перед КАЖДОЙ новой левой вершиной - это важнее, чем кажется, и забывают об этом чаще всего.

Нумерация здесь двойная, и путать её нельзя: v - номер ЛЕВОЙ вершины, j - номер ПРАВОЙ, adj[v] хранит правые номера, match_r[j] и used[j] индексируются правыми. В рекурсию уходит match_r[j] - снова левый номер.

def try_kuhn(v, adj, match_r, used):
    for j in adj[v]:
        if not used[j]:
            used[j] = True
            if match_r[j] == -1 or try_kuhn(match_r[j], adj, match_r, used):
                match_r[j] = v
                return True
    return False

Цена: одна попытка стоит O(E), попыток |L|, итого O(V·E). Хопкрофт-Карп ищет дополняющие пути пачками и даёт O(E·√V) - это тот же Диниц на единичных пропускных способностях.

Правило

Алгоритм Куна: для каждой левой вершины запускай обход в глубину по чередующимся путям, обнуляя пометки перед каждым запуском; нашёл дополняющий путь - переключи его. Цена O(V·E), у Хопкрофта-Карпа O(E·√V). ТЕОРЕМА КЁНИГА: в двудольном графе максимальное паросочетание равно минимальному вершинному покрытию. ТЕОРЕМА ХОЛЛА: паросочетание, занимающее всю левую долю, существует тогда и только тогда, когда |N(S)| >= |S| для КАЖДОГО подмножества S левой доли; в общем случае размер максимального паросочетания равен |L| − max(|S| − |N(S)|).

Разбор: три работника и одна задача

Граф: слева А, Б, В, справа 1, 2, 3. Рёбра: А-1, Б-1, В-1. Больше никаких.

Прогон Куна:

v = А: сосед 1 свободен          → пара А-1, размер 1
v = Б: сосед 1 занят Аней,
       пробуем переставить Аню:
       у Ани других соседей нет  → путь не найден, Б остаётся свободным
v = В: то же самое              → свободен

Размер 1. Теперь сертификат по Кёнигу: возьмём вершинное покрытие {1} - одна вершина, и все три ребра ею накрыты. Значит паросочетание не больше 1 (каждое его ребро требует своей вершины покрытия), а единица достигнута - ответ доказан.

Холл на том же графе: возьмём S = {А, Б, В}. Соседи этого множества - N(S) = {1}, то есть |N(S)| = 1, а |S| = 3. Условие |N(S)| >= |S| нарушено, дефицит 3 − 1 = 2. Формула даёт |L| − дефицит = 3 − 2 = 1 - тот же ответ, полученный вообще без алгоритма.

Разбор: как достать вершинное покрытие из паросочетания

Кёниг не просто равенство - он конструктивен, и это его практическая часть. Пусть паросочетание уже максимально.

Z строится обходом от СВОБОДНЫХ левых вершин, чередуя рёбра:
    из левой вершины идём по рёбрам ВНЕ паросочетания  → добавляем правые
    из правой вершины идём по ребру ПАРОСОЧЕТАНИЯ      → добавляем левую
    (свободные левые вершины сами входят в Z)

покрытие = (левые, НЕ попавшие в Z)  ∪  (правые, попавшие в Z)

Проверим на графе из прошлого урока: слева A, B, C, справа 1, 2, 3, рёбра A-1, A-2, B-1, C-2, C-3, максимальное паросочетание B-1, A-2, C-3 - размер 3, свободных левых вершин нет. Тогда Z пусто, покрытие = все левые вершины = {A, B, C}, размер 3. Совпало с паросочетанием.

А вот пример поинтереснее: рёбра А-1, Б-1, В-1 из первого разбора. Паросочетание А-1, свободные левые - Б и В. Из Б идём по ребру Б-1 в вершину 1 (ребро не в паре), из 1 по ребру пары возвращаемся в А. Значит Z = {Б, В, 1, А}. Покрытие: левые не из Z - никого; правые из Z - вершина 1. Итого {1}, размер 1 ✓.

Зачем это нужно на практике: покрытие - это ответ на вопрос «где узкое место». В расписании оно говорит, какие именно аудитории или станки создают затор, а не просто «больше 7 пар не выйдет».

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

Первое: не обнулять used перед каждой новой левой вершиной - алгоритм пропустит существующие дополняющие пути и вернёт заниженный ответ. Второе: обнулять used ВНУТРИ рекурсии - тогда обход зациклится. Третье: применять Кёнига к недвудольному графу: там равенство неверно (простейший контрпример - треугольник: паросочетание 1, покрытие 2). Четвёртое: проверять условие Холла перебором всех подмножеств - их 2^|L|; на практике считают максимальное паросочетание и смотрят, занята ли вся левая доля. Холл нужен как ОБЪЯСНЕНИЕ и как источник контрпримера, а не как алгоритм. Пятое: думать, что Кун работает и на общем графе, потому что код не проверяет двудольность, - проверять её надо самому, иначе ответ будет молча неверным.

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

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

Теперь сам

Слева A, B, C, D, справа 1, 2, 3, 4. Рёбра: A-1, A-2, B-1, B-2, C-1, C-2, D-3. Найди максимальное паросочетание, вершинное покрытие того же размера и объясни ответ по Холлу.

Ответ. Паросочетание: A-1, B-2, D-3 - размер 3. Больше не получится: вершины A, B, C тянутся только к 1 и 2, а их всего две.

Покрытие: {1, 2, D} - размер 3. Проверим, что накрыты все рёбра: A-1, B-1, C-1 накрыты вершиной 1; A-2, B-2, C-2 - вершиной 2; D-3 - вершиной D. Размеры совпали, значит по Кёнигу и паросочетание максимально, и покрытие минимально.

Холл: S = {A, B, C} даёт N(S) = {1, 2}, то есть |N(S)| = 2 < 3 = |S|. Дефицит равен 1, и формула |L| − дефицит = 4 − 1 = 3 совпадает с найденным размером. Вершина 4 при этом не участвует вовсе - в паросочетании ей просто нет пары, и на ответ это не влияет.

Главное

Алгоритм Куна: для каждой левой вершины ищи дополняющий путь обходом в глубину, обнуляя пометки перед каждым запуском; рекурсивный вызов для занявшего правую вершину - это и есть «подвинься». Цена O(V·E), Хопкрофт-Карп даёт O(E·√V). Теорема Кёнига: в двудольном графе максимальное паросочетание равно минимальному вершинному покрытию, и покрытие строится по готовому паросочетанию - «левые вне Z» плюс «правые внутри Z», где Z достижимо чередующимися путями из свободных левых. Теорема Холла: всю левую долю можно занять тогда и только тогда, когда у каждого подмножества соседей не меньше, чем его самого; в общем случае размер равен |L| минус максимальный дефицит.

Комментарии

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

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

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