← Все вопросы

Венгерский алгоритм для задачи о назначениях — в чём идея и когда применять вместо min-cost flow?

Задан 16 месяцев назад1.3к просмотров2 ответа
9

Дана квадратная матрица стоимостей $cost[i][j]$ — назначить каждого работника на ровно одну задачу так, чтобы суммарная стоимость минимальна (assignment problem). Знаю, что это можно через min-cost max-flow, но все говорят про венгерский алгоритм. В чём его идея, какая асимптотика и когда он выгоднее?

2 ответа

15
✓ Принятый ответ — помог автору

Задача о назначениях — найти совершенное паросочетание в полном двудольном графе $n\times n$ минимальной суммарной стоимости. Это частный случай min-cost max-flow, но венгерский алгоритм решает его за чистый $O(n^3)$, что обычно быстрее и проще, чем общий MCMF.

Идея (метод потенциалов / двойственность). Поддерживаем потенциалы $u_i$ (для строк) и $v_j$ (для столбцов) так, что $u_i + v_j \le cost[i][j]$ всегда. «Жёсткие» рёбра, где $u_i + v_j = cost[i][j]$, образуют граф, в котором ищем паросочетание. Если совершенное паросочетание в жёстких рёбрах найдено — оно оптимально (по двойственности ЛП). Если нет — корректируем потенциалы вдоль чередующегося дерева, добавляя новые жёсткие рёбра, и повторяем.

Компактная $O(n^3)$ реализация (формат «один столбец за итерацию», 1-индексация, матрица a[1..n][1..m], $n\le m$):

vector<int> u(n+1), v(m+1), p(m+1), way(m+1);
for(int i=1;i<=n;i++){
    p[0]=i; int j0=0;
    vector<long long> minv(m+1, LLONG_MAX);
    vector<char> used(m+1,false);
    do{
        used[j0]=true; int i0=p[j0], j1=-1; long long delta=LLONG_MAX;
        for(int j=1;j<=m;j++) if(!used[j]){
            long long cur=a[i0][j]-u[i0]-v[j];
            if(cur<minv[j]){minv[j]=cur; way[j]=j0;}
            if(minv[j]<delta){delta=minv[j]; j1=j;}
        }
        for(int j=0;j<=m;j++)
            if(used[j]){u[p[j]]+=delta; v[j]-=delta;} else minv[j]-=delta;
        j0=j1;
    } while(p[j0]!=0);
    do{int j1=way[j0]; p[j0]=p[j1]; j0=j1;} while(j0);
}
// p[j] — какая строка назначена столбцу j; стоимость = -v[0]

Асимптотика: $O(n^2 m)$, для квадратной $O(n^3)$. Память $O(nm)$.

Когда выгоднее MCMF: когда граф разреженный (не все пары допустимы) или есть нетривиальные пропускные/несбалансированные доли — там общий min-cost flow гибче. Для плотной квадратной матрицы стоимостей венгерский алгоритм — золотой стандарт.

6

Пара практических замечаний к этой реализации:

  1. Максимизация вместо минимизации: если нужно максимизировать суммарную стоимость, замените все $cost[i][j]$ на $-cost[i][j]$ (или $\text{BIG}-cost$) и решайте на минимум.
  2. Запрещённые пары: ставьте им очень большую стоимость (заведомо больше любой валидной суммы), но осторожно с переполнением — берите long long и не складывайте «бесконечности» бесконтрольно.
  3. Прямоугольный случай $n \le m$ код выше обрабатывает напрямую — назначаем каждую из $n$ строк, столбцов может быть больше.

Этот шаблон очень компактный и его стоит просто положить в свою библиотеку наизусть — задачи на assignment встречаются регулярно.

Ваш ответ

Войдите, чтобы ответить на вопрос.