Венгерский алгоритм решения задачи о назначениях
Венгерский алгоритм — алгоритм, решающий задачу о назначениях за полиномиальное время. Оригинальная версия была придумана и разработана Х. Куном в 1955 году и имела асимптотику
, но позже Эдмонс и Карп (а также, независимо от них, Томидзава) показали, что можно улучшить ее до .Постановка задачи
Пусть дан взвешенный полный двудольный граф
, нужно найти в нем полное паросочетание минимального веса. Вес паросочетания определяется как сумма весов его ребер. Далее будем обозначать левую и правую доли графа за и соответственно, вес ребра — как .Некоторые полезные утверждения
Лемма: |
Если веса всех ребер графа, инцидентных какой-либо вершине, изменить на одно и то же число, то в новом графе оптимальное паросочетание будет состоять из тех же ребер, что и в старом. |
Доказательство: |
Полное паросочетание для каждой вершины содержит ровно одно ребро, инцидентное этой вершине. Указанная операция изменит на одно и то же число вес любого паросочетания. Значит, ребро, которое принадлежало оптимальному паросочетанию в старом графе, в новом графе тоже будет ему принадлежать. |
Далее будем рассматривать только графы с неотрицательной весовой функцией, так как, согласно этой лемме, задачу о назначениях на остальных графах можно свести к задаче о назначениях на них.
Лемма: | |||||||||
Выделим в множествах и подмножества . Пусть . Прибавим ко всем весам ребер, инцидентных вершинам из . Затем отнимем от всех весов ребер, инцидентных вершинам из (далее для краткости эта операция обозначается как ). Тогда:
| |||||||||
Доказательство: | |||||||||
Рассмотрим матрицу весов графа. Не умаляя общности, можно сказать, что множества и состоят из первых элементов множеств и соответственно (мы упорядочиваем множества по номерам вершин). Тогда вся матрица делится на 4 блока: | |||||||||
Лемма: |
Если веса всех ребер графа неотрицательны и некоторое полное паросочетание состоит из ребер нулевого веса, то оно является оптимальным. |
Доказательство: |
Действительно, паросочетание с какими-то другими весами ребер имеет больший вес и оптимальным не является. |
Общий метод
Доказанные ранее утверждения позволяют придумать схему алгоритма, решающего задачу о назначениях: нужно найти полное паросочетание из ребер нулевого веса в графе, полученном из исходного преобразованиями, описанными в первых двух леммах.
Алгоритм, решающий задачу, работает с графом, как с матрицей весов.
- Вычитаем из каждой строки значение ее минимального элемента. Теперь в каждой строке есть хотя бы один нулевой элемент.
- Вычитаем из каждого столбца значение его минимального элемента. Теперь в каждом столбце есть хотя бы один нулевой элемент.
- Ищем в текущем графе полное паросочетание из ребер нулевого веса:
-
- Если оно найдено, то желаемый результат достигнут, алгоритм закончен.
- В противном случае, покроем нули матрицы весов минимальным количеством строк и столбцов (это не что иное, как нахождение минимального вершинного покрытия в двудольном графе). Пусть и — множества вершин минимального вершинного покрытия из левой и правой долей (то есть, строк и столбцов) соответственно, тогда применим преобразование . Для этого преобразования будет минимумом по всем ребрам между и , то есть, ребер нулевого веса здесь нет, поэтому, после его выполнения в матрице весов появится новый нуль. После этого перейдем к шагу 1.
Анализ времени работы
Поиск максимального паросочетания или минимального вершинного покрытия в двудольном графе совершается за
операций. При каждом повторении шагов 1-3 в матрице весов появляется новый нуль, который увеличивает размер максимального паросочетания хотя бы на 1, поэтому всего будет совершено итераций внешнего цикла. Поэтому, суммарная асимптотика работы алгоритма — .Алгоритм за
Предложенный метод корректен, но не является оптимальным по времени работы, оно может быть улучшено до
.Заметим, что искать максимальное паросочетание каждый раз заново необязательно, можно просто постепенно изменять его, получая новые нулевые ребра и строя дополняющие цепи на них (примерно так же, как и в алгоритме Куна, но здесь мы фактически строим целое дерево возможных дополняющих цепей).
При их построении нам потребуется сохранять некоторую дополнительную информацию. Для каждого столбца, если в нем есть элемент из текущего паросочетания, будем хранить номер строки этого элемента. Также, при построении дополняющей цепи, будем для каждого элемента паросочетания хранить, на какой добавленный нуль из того же столбца его нужно заменить, если цепь будет проходить через него, а для каждого добавленного нуля — элемент из паросочетания, находящийся в той же строке (если он существует). Эти данные помогут нам восстановить дополняющую цепь.
Будем рассматривать строки матрицы последовательно. Пусть в данный момент мы рассматриваем
-ую строку, а в первых строках максимальное паросочетание уже найдено.Множество строк
, из которых мы будем вычитать минимумы, вначале состоит только из строки , множество пусто. Применим к матрице операцию . Если новый нуль с координатами появился в столбце, не занятом паросочетанием, то дополняющая цепь найдена, — ее последнее ребро. Если нет, то существует элемент из паросочетания. Тогда добавим строку и столбец в множества и соответственно, отметим как замену для и повторим операцию.После того, как мы нашли дополняющую цепь, производим серию замен вдоль нее. Цепь состоит из элементов паросочетания и добавленных на текущей итерации нулей, причем для каждого элемента цепи (кроме, разумеется, последнего) известен следующий за ним. После того, как мы удалим из паросочетания все ребра цепи, принадлежащие ему и добавим не принадлежащие, суммарный размер паросочетания увеличится.
Краткий псевдокод данного алгоритма выглядит примерно так:
HungarianAlgorithm(A) 1 for i = 0..height-1 2 do 3 создать в матрице A новый нулевой элемент 4 while новый элемент приводит к коллизии 5 изменить метки элементов вдоль найденной цепочки 6 return найденное паросочетание
При добавлении строки с номером
на поиск дополняющей цепи требуется итераций, если каждая итерация требует действий, то общее время работы алгоритма составляет . При наивной реализации . Но можно не пересчитывать каждый раз все измененные элементы матрицы. Вместо них будем хранить и обновлять для каждой строки и каждого столбца минимум по данной строке/столбцу. Текущее значение элемента в каждый момент можно восстановить, используя эти вспомогательные массивы. При выполнении преобразования изменим соответствующие элементы массивов на . Так можно выполнять все требуемые действия за операций, тогда на выполнение всего алгоритма потребуется действий.Ссылки
Литература
- Асанов М., Баранский В., Расин В. — Дискретная математика: Графы, матроиды, алгоритмы — 2010, 368 стр.