EssayAI
Блог
Блог
Математика и алгоритмы

Венгерский алгоритм: задача о назначениях

24 февраля 2026Время чтения: 9 минут
#венгерский алгоритм#задача назначения#двудольные графы#оптимизация#дискретная математика
Венгерский алгоритм: задача о назначениях

Венгерский алгоритм - классический способ за полиномиальное время решить задачу о назначениях: распределить nn работников по nn работам так, чтобы суммарная стоимость была минимальной. Идея опубликована Гарольдом Куном в 1955 году (он сослался на работы венгров Кёнига и Эгервари - отсюда название), а в 1957 году Манкр показал оценку O(n4)O(n^4), позже улучшенную до O(n3)O(n^3). В современной формулировке это эталонный пример ЛП-двойственности: алгоритм одновременно строит допустимое назначение и допустимые двойственные переменные, а сильный двойственный результат гарантирует оптимальность.

Постановка задачи о назначениях

Дано nn работников, nn работ и матрица стоимостей C=(cij)Rn×nC = (c_{ij}) \in \mathbb{R}^{n \times n}, где cijc_{ij} - затраты, если работник ii выполнит работу jj. Нужно выбрать перестановку π:{1,,n}{1,,n}\pi : \{1,\dots,n\} \to \{1,\dots,n\}, минимизирующую

i=1nci,π(i)min.\sum_{i=1}^{n} c_{i,\pi(i)} \to \min.

Если задача про максимум прибыли - переходим к минимуму через cij=Mcijc'_{ij} = M - c_{ij}, где MmaxcijM \ge \max c_{ij}. Если работ больше, чем работников, или наоборот - добавляем фиктивные строки/столбцы с нулевой стоимостью.

Поставленная задача - частный случай минимального по весу совершенного паросочетания в полном двудольном графе Kn,nK_{n,n}, где левые вершины - работники, правые - работы, а вес ребра (i,j)(i, j) равен cijc_{ij}. Полный перебор перестановок даёт n!n! вариантов - для n=20n = 20 это уже 21018\sim 2 \cdot 10^{18}, неприемлемо. Венгерский алгоритм закрывает задачу за O(n3)O(n^3).

Идея ЛП-двойственности

Запишем задачу как линейное программирование с переменными xij[0,1]x_{ij} \in [0, 1]:

mini,jcijxij,jxij=1,ixij=1,xij0.\min \sum_{i,j} c_{ij} x_{ij}, \quad \sum_j x_{ij} = 1, \quad \sum_i x_{ij} = 1, \quad x_{ij} \ge 0.

Полиэдр такой задачи - это политоп Биркгофа (двунаправленно стохастические матрицы), и его вершины - именно перестановочные матрицы. Поэтому ЛП-релаксация даёт целочисленный оптимум: задача о назначениях точно решается симплекс-методом, но в нём O(n3)O(n^3) не гарантировано.

Двойственная задача: найти потенциалы ui,vju_i, v_j такие, что

ui+vjciji,j,maxiui+jvj.u_i + v_j \le c_{ij} \quad \forall i, j, \qquad \max \sum_i u_i + \sum_j v_j.

По теореме сильной двойственности оптимумы прямой и двойственной задач совпадают. Условие дополняющей нежёсткости: xij>0x_{ij} > 0 только если ui+vj=ciju_i + v_j = c_{ij}. Рёбра, на которых равенство достигнуто, называют жёсткими (tight); они образуют равенственный подграф G=G_=. Если в нём существует совершенное паросочетание - это и есть оптимальное назначение.

Венгерский алгоритм поддерживает допустимые u,vu, v и постепенно увеличивает паросочетание в G=G_=. Когда оно становится совершенным - оптимум найден.

Алгоритм по шагам

Классическая «школьная» формулировка работает прямо с матрицей CC и оперирует нулями: место ui+vj=ciju_i + v_j = c_{ij} занимает cijuivj=0c_{ij} - u_i - v_j = 0. Шаги:

  1. Редукция строк. Из каждой строки вычесть её минимум. В каждой строке появляется хотя бы один ноль.
  2. Редукция столбцов. Из каждого столбца вычесть его минимум. В каждом столбце появляется хотя бы один ноль.
  3. Покрытие нулей. Найти минимальное число линий (горизонтальных и вертикальных), покрывающих все нули матрицы. Если линий ровно nn - назначение возможно: выбрать nn независимых нулей (по одному в каждой строке и каждом столбце) и выписать перестановку π\pi.
  4. Обновление. Если линий меньше nn, пусть θ\theta - минимум среди непокрытых элементов. Вычесть θ\theta из всех непокрытых строк и прибавить θ\theta к покрытым столбцам (или, эквивалентно: вычесть θ\theta из непокрытых элементов, прибавить θ\theta к дважды покрытым, не трогать однократно покрытые). Это сохраняет двойственную допустимость, создаёт новый ноль и не теряет старых. Вернуться к шагу 3.

Поиск минимального покрытия нулей сводится к теореме Кёнига: число линий в покрытии равно размеру максимального паросочетания на нулях. То есть алгоритм Куна для невзвешенного двудольного графа здесь - внутренняя процедура.

Сложность: от O(n4)O(n^4) к O(n3)O(n^3)

Каждая итерация добавляет хотя бы одну новую жёсткую дугу или увеличивает паросочетание. Прямолинейная реализация Манкра 1957 года тратит O(n2)O(n^2) на поиск паросочетания и O(n2)O(n^2) на обновление, всего O(n4)O(n^4) - итераций тоже O(n2)O(n^2).

Оптимизация до O(n3)O(n^3) держит для каждого правого узла jj минимум mini(cijuivj)\min_i (c_{ij} - u_i - v_j) по уже обработанным левым узлам и пересчитывает его за O(n)O(n) при добавлении нового левого узла. Тогда поиск увеличивающей цепи через жёсткий подграф вместе с обновлением потенциалов укладывается в O(n2)O(n^2) на итерацию, а итераций ровно nn - по одной на каждую вершину левой доли. Итого:

T=O(n3),память=O(n2).T = O(n^3), \quad \text{память} = O(n^2).

Это та самая «Jonker–Volgenant»-реализация (1987), которая используется в большинстве библиотек, включая SciPy linear_sum_assignment.

Корректность

Алгоритм одновременно поддерживает три инварианта:

  1. Двойственная допустимость: после редукции и каждого обновления ui+vjciju_i + v_j \le c_{ij} для всех i,ji, j. На покрытых строках/столбцах знак сохраняется явно, на непокрытых - за счёт правильного знака θ\theta.
  2. Жёсткое паросочетание: текущее MM состоит только из жёстких рёбер (ui+vj=ciju_i + v_j = c_{ij}).
  3. Прогресс: за итерацию либо растёт M|M|, либо появляется новое жёсткое ребро. Поэтому через O(n2)O(n^2) итераций (а в O(n3)O(n^3)-варианте - через ровно nn внешних итераций) M=n|M| = n.

Когда M=n|M| = n, паросочетание совершенно, все его рёбра жёсткие, и

ici,π(i)=i(ui+vπ(i))=iui+jvj.\sum_i c_{i, \pi(i)} = \sum_i (u_i + v_{\pi(i)}) = \sum_i u_i + \sum_j v_j.

Правая часть - значение двойственной задачи, и по сильной двойственности оно совпадает с оптимумом прямой. Значит, найденное назначение оптимально.

Типовая задача 4 на 4

Матрица стоимостей:

C=(9278643758187694).C = \begin{pmatrix} 9 & 2 & 7 & 8 \\ 6 & 4 & 3 & 7 \\ 5 & 8 & 1 & 8 \\ 7 & 6 & 9 & 4 \end{pmatrix}.

Редукция строк (минимумы 2,3,1,42, 3, 1, 4):

(7056310447073250).\begin{pmatrix} 7 & 0 & 5 & 6 \\ 3 & 1 & 0 & 4 \\ 4 & 7 & 0 & 7 \\ 3 & 2 & 5 & 0 \end{pmatrix}.

Редукция столбцов (минимумы 3,0,0,03, 0, 0, 0):

(4056010417070250).\begin{pmatrix} 4 & 0 & 5 & 6 \\ 0 & 1 & 0 & 4 \\ 1 & 7 & 0 & 7 \\ 0 & 2 & 5 & 0 \end{pmatrix}.

Минимальное покрытие нулей - 4 линии (строки 2 и 4, столбцы 2 и 3). Назначение существует: например, 121 \to 2, 212 \to 1, 333 \to 3, 444 \to 4. Сумма c1,2+c2,1+c3,3+c4,4=2+6+1+4=13c_{1,2} + c_{2,1} + c_{3,3} + c_{4,4} = 2 + 6 + 1 + 4 = 13. Это и есть оптимум.

Применения

  • Распределение задач. Сотрудники на проекты, водители на маршруты, врачи на смены - везде, где у пары есть числовая «стоимость» и нужно покрыть все позиции.
  • Шипп-планирование. Назначение судов на причалы, грузовиков на доки, контейнеров на платформы. Часто матрица не квадратная - её дополняют фиктивными столбцами.
  • Computer vision matching. Сопоставление детекций между кадрами в трекинге (SORT, DeepSORT), ключевых точек, ассоциация лиц с треками. Стоимость - расстояние между признаками; алгоритм даёт глобальный оптимум.
  • Экономика. Двусторонние рынки с трансфертами - модель Шепли-Шубика; венгерский алгоритм находит конкурентное равновесие.

Сравнение с потоковыми и аукционным алгоритмами

  • Min-cost max-flow. Задача о назначениях - частный случай: исток → работники → работы → сток, пропускные способности 1, стоимости cijc_{ij}. SSP с потенциалами даёт O(n3logn)O(n^3 \log n). Гибче (другие ограничения, не-квадратные матрицы), но константа выше - базовый поток без стоимостей считается алгоритмом Эдмондса-Карпа.
  • Аукционный алгоритм Бертсекаса (1979). Работники-«покупатели» итеративно повышают цены работ. O(n3)O(n^3) в худшем случае, хорошо параллелится. Применяется в радарном трекинге.
  • Венгерский (Куна-Манкр) - классика, лучшая средняя константа на квадратной плотной матрице, O(n3)O(n^3) гарантированно.

Для nn до 103\sim 10^3 - почти всегда венгерский. Для n104n \sim 10^4 и разреженных матриц - потоковые или аукцион.

Частые ошибки

  • Забывают про двойную редукцию. Только строки или только столбцы - не работает: должна быть и горизонтальная, и вертикальная.
  • Неправильное обновление при недостающем покрытии. Нужно вычитать θ\theta из непокрытых строк (или элементов) и прибавлять к покрытым столбцам - иначе нарушится двойственная допустимость и алгоритм зациклится.
  • Применяют к не-квадратной матрице без дополнения. Если mnm \ne n, нужно дополнить фиктивными работниками/работами с нулевой (или MM для максимума) стоимостью. Иначе совершенного паросочетания не будет.
  • Решают «максимум» как «минимум» без замены знака. Корректно: cij=Mcijc'_{ij} = M - c_{ij} с MmaxcijM \ge \max c_{ij}. Просто менять знак нельзя: появятся отрицательные значения и редукция выдаст ерунду.
  • Считают, что любое покрытие минимально. Покрытие должно быть минимальным по числу линий (по теореме Кёнига = размеру максимального паросочетания на нулях). Если линий nn, но они не минимальны, можно ошибочно решить, что назначение нашлось.

FAQ

Чем венгерский алгоритм отличается от алгоритма Куна? Алгоритм Куна решает невзвешенную задачу - найти максимальное паросочетание в двудольном графе, O(VE)O(VE). Венгерский алгоритм работает со взвешенной задачей и минимизирует сумму стоимостей при покрытии всех вершин, O(n3)O(n^3). Кун - внутренняя процедура венгерского алгоритма на шаге поиска покрытия нулей.

Почему сложность именно O(n3)O(n^3), а не быстрее? Внешний цикл - nn итераций (по числу левых вершин). На каждой итерации поиск увеличивающей цепи через жёсткий подграф плюс обновление потенциалов стоят O(n2)O(n^2). Существуют асимптотически более быстрые методы для специальных классов матриц (например, O(n2.5)O(n^{2.5}) для двоичных), но в общем случае O(n3)O(n^3) - лучшая известная оценка для строго детерминированных алгоритмов.

Как обрабатывать прямоугольную матрицу m×nm \times n? Дополнить меньшую сторону фиктивными вершинами с нулевой стоимостью до квадратной. После решения отбросить назначения на фиктивные элементы - они и так не несут затрат. Современные реализации (SciPy linear_sum_assignment) делают это автоматически.

Коротко

Венгерский алгоритм решает задачу о назначениях за O(n3)O(n^3): минимизирует сумму стоимостей при распределении nn работников по nn работам. Идея - ЛП-двойственность: алгоритм поддерживает допустимые двойственные переменные ui,vju_i, v_j и постепенно увеличивает совершенное паросочетание в равенственном подграфе. Школьная формулировка с матрицей: вычитание минимумов из строк и столбцов, поиск минимального покрытия нулей линиями, обновление матрицы вычитанием минимума по непокрытым элементам. Корректность гарантируется сильной двойственностью ЛП. Применяется в логистике, computer vision, биоинформатике, экономике двусторонних рынков. На больших и разреженных задачах уступает потоковым и аукционным алгоритмам, но для плотных квадратных матриц до n103n \sim 10^3 - стандарт.

Доверьте текст нейросети EssayAI

Открыть EssayAI

Бесплатно, на русском языке и без VPN

Читайте также