Венгерский алгоритм
Венгерский метод — это комбинаторной оптимизации алгоритм , который решает задачу назначения за полиномиальное время и который предвосхитил более поздние методы просто-двойственности . Он был разработан и опубликован в 1955 году Гарольдом Куном , который дал ему название «Венгерский метод», поскольку алгоритм во многом основывался на более ранних работах двух венгерских математиков, Денеша Кенига и Йене Эгервари . [1] [2] Однако в 2006 году было обнаружено, что Карл Густав Якоби решил проблему назначения в 19 веке, и это решение было опубликовано посмертно в 1890 году на латыни. [3]
Джеймс Манкрес рассмотрел алгоритм в 1957 году и заметил, что он (строго) полиномиален . [4] С тех пор этот алгоритм известен также как алгоритм Куна-Мункреса или алгоритм назначения Манкреса . Временная сложность исходного алгоритма составляла , однако Эдмондс , Карп и независимо Томизава заметили, что его можно модифицировать для достижения время бега. [5] [6] Форд и Фулкерсон распространили этот метод на общие задачи максимального потока в форме алгоритма Форда – Фулкерсона .
Проблема
[ редактировать ]Пример
[ редактировать ]В этом простом примере есть три работника: Алиса, Боб и Кэрол. Один из них должен мыть ванную, другой подметать полы, а третий моет окна, но каждый из них требует разной оплаты за разные задачи. Проблема состоит в том, чтобы найти наименее затратный способ распределения заданий. Проблему можно представить в виде матрицы затрат работников, выполняющих работу. Например:
- ЗадачаРабочий
Чистый
ванная комнатаМести
полыСтирать
окнаАлиса $8 $4 $7 Боб $5 $2 $3 Кэрол $9 $4 $8
Венгерский метод, примененный к приведенной выше таблице, даст минимальные затраты: это 15 долларов, которые достигаются за счет того, что Алиса убирает ванную, Кэрол подметает полы, а Боб моет окна. Это можно подтвердить с помощью грубой силы:
- ЧистыйМести
Алиса Боб Кэрол Алиса — $17 $16 Боб $18 — $18 Кэрол $15 $16 —
Формулировка матрицы
[ редактировать ]В матричной формулировке нам дана размера n × n матрица , где элемент в i -й строке и j -м столбце представляет стоимость назначения j -го задания i -му работнику. Нам нужно найти распределение работ между работниками так, чтобы каждая работа была назначена одному работнику, а каждому работнику была назначена одна работа, так, чтобы общая стоимость назначения была минимальной.
Это можно выразить как перестановку строк матрицы стоимости C для минимизации следа матрицы:
где P — матрица перестановок . (Точно так же столбцы можно переставлять с помощью CP .)
Если цель состоит в том, чтобы найти назначение, которое дает максимальную стоимость, проблему можно решить путем отрицания матрицы стоимости C .
Формулировка двудольного графа
[ редактировать ]Алгоритм эквивалентно можно описать, сформулировав задачу с использованием двудольного графа. Имеем полный двудольный граф с n рабочими вершинами ( S ) и n рабочими вершинами ( T ), и каждое ребро ( E ) имеет стоимость . Мы хотим найти идеальное сочетание с минимальной общей стоимостью.
Алгоритм в терминах двудольных графов
[ редактировать ]Давайте вызовем функцию потенциал , если для каждого . Значение представляет собой потенциала y сумму потенциала по всем вершинам: .
Стоимость каждого идеального паросочетания равна, по крайней мере, значению каждого потенциала: общая стоимость паросочетания равна сумме стоимостей всех ребер; стоимость каждого ребра равна как минимум сумме потенциалов его концов; поскольку сопоставление идеальное, каждая вершина является конечной точкой ровно одного ребра; следовательно, общая стоимость равна, по крайней мере, общему потенциалу.
Венгерский метод находит идеальное совпадение и потенциал, при котором стоимость сопоставления равна потенциальному значению. Это доказывает, что оба они оптимальны. Фактически, венгерский метод находит идеальное совпадение узких ребер : ребро называется тесным для потенциала y, если . Обозначим подграф узких через ребер . Стоимость идеального соответствия в (если он есть) равен значению y .
В ходе алгоритма мы поддерживаем y и ориентацию потенциал (обозначается ), которое обладает тем свойством, что ребра, ориентированные от к S , образуют паросочетание M. T Изначально y везде равен 0, и все ребра ориентированы от S к T (поэтому M пусто). На каждом шаге мы либо изменяем y , чтобы его значение увеличивалось, либо изменяем ориентацию, чтобы получить паросочетание с большим количеством ребер. Мы сохраняем инвариант, что все ребра M узкие. Мы закончили, если M — идеальное паросочетание.
На общем этапе пусть и — вершины, не покрытые M (так что состоит из вершин в S без входящего ребра и состоит из вершин из T без выходящего ребра). Пусть Z — множество вершин, достижимых в от по направленному пути. Это можно вычислить с помощью поиска в ширину .
Если непусто, то измените ориентацию всех ребер вдоль направленного пути в от к . Таким образом, размер соответствующего паросочетания увеличивается на 1.
Если пусто, то пусть
Δ корректно определен, поскольку хотя бы одно такое ребро должно существовать всякий раз, когда соответствие еще не достигло максимально возможного размера (см. следующий раздел); оно положительно, потому что между ними нет резких границ. и . Увеличить y на Δ в вершинах и уменьшим y на Δ в вершинах . Полученный y по-прежнему является потенциалом, и хотя график изменяется, он по-прежнему содержит M (см. следующие подразделы). Ориентируем новые ребра S к T. от По определению ∆ множество Z вершин, достижимых из увеличивается (обратите внимание, что количество узких ребер не обязательно увеличивается).
Мы повторяем эти шаги до тех пор, пока M не станет идеальным паросочетанием, и в этом случае оно дает назначение минимальной стоимости. Время работы этой версии метода : M увеличивается n раз, и в фазе, где M остается неизменным, происходит не более n потенциальных изменений (поскольку Z увеличивается каждый раз). Время, достаточное для потенциального изменения, равно .
Доказательство того, что алгоритм прогрессирует
[ редактировать ]Мы должны показать, что до тех пор, пока размер совпадения не достигает максимально возможного, алгоритм всегда способен добиться прогресса — то есть либо увеличить количество совпадающих ребер, либо сузить хотя бы одно ребро. Достаточно показать, что на каждом шаге выполняется хотя бы одно из следующих утверждений:
- M имеет максимально возможный размер.
- содержит дополняющий путь.
- G содержит путь со свободным хвостом : путь из некоторой вершины в в вершину в который состоит из любого количества (возможно, нуля) узких ребер, за которыми следует одно свободное ребро. Таким образом, задний свободный край пути со свободным хвостом исходит из , гарантируя, что ∆ корректно определена.
Если M имеет максимально возможный размер, мы, конечно, закончили. В противном случае, по лемме Бержа должен существовать дополняющий путь P относительно M в базовом графе G. , Однако этот путь может не существовать в : Хотя каждое ребро с четным номером в P является плотным по определению M , ребра с нечетными номерами могут быть свободными и, следовательно, отсутствовать в . Одна конечная точка P находится в , другой в ; wlog, предположим, что он начинается в . Если каждое ребро на P узкое, то оно остается увеличивающим путем в и мы закончили. В противном случае, пусть быть первым свободным ребром на P . Если тогда мы нашли свободный путь и все готово. В противном случае v достижима по некоторому другому пути Q с узкими ребрами из вершины в . Позволять — подпуть P , начинающийся с v и продолжающийся до конца, и пусть — путь, образованный путешествием по Q до вершины на достигается, а затем продолжается до конца . Обратите внимание, что — расширяющий путь в G по крайней мере, на одно свободное ребро меньше, чем у P. , П можно заменить на и этот процесс рассуждений повторялся (формально, с использованием индукции по числу свободных ребер) до тех пор, пока не возникнет увеличивающий путь в путь со свободным хвостом в G. или найден
Доказательство того, что корректировка потенциала y оставляет M неизменным.
[ редактировать ]Чтобы показать, что каждое ребро в M остается после корректировки y , достаточно показать, что для произвольного ребра в либо обе его конечные точки, либо ни одна из них не находятся в Z. M С этой целью пусть быть ребром в M от T до S . Легко понять, что если v находится в Z, то и u тоже должно быть, поскольку каждое ребро в M узкое. Теперь предположим, в сторону противоречия, что но . ты сам не можешь быть внутри потому что это конечная точка совмещенного ребра, поэтому должен существовать некоторый направленный путь узких ребер из вершины в тебе . Этот путь должен избегать v , поскольку по предположению он не находится в Z , поэтому вершина, непосредственно предшествующая u на этом пути, является какой-то другой вершиной. . является плотным ребром из T в S и, таким образом, находится в M . Но тогда M содержит два ребра, которые имеют общую вершину u , что противоречит тому факту, что M является паросочетанием. каждое ребро в M имеет либо оба конца, либо ни одного конца в Z. Таким образом ,
Доказательство того, что y остается потенциальным
[ редактировать ]Чтобы показать, что y остается потенциалом после корректировки, достаточно показать, что ни у одного ребра общий потенциал не увеличился за пределы его стоимости. это уже установлено Для ребер в M в предыдущем абзаце, поэтому рассмотрим произвольное uv из S в T. ребро Если увеличивается на Δ , то либо , в этом случае уменьшается на Δ , оставляя общий потенциал края неизменным, или , и в этом случае определение ∆ гарантирует, что . Таким образом, y остается потенциалом.
Алгоритм за O ( n 3 ) время
[ редактировать ]Предположим, есть рабочие места и рабочие ( ). Мы описываем, как вычислить для каждого префикса заданий минимальную общую стоимость назначения каждого из этих заданий отдельным работникам. В частности, мы добавляем задание и своевременно обновлять общую стоимость , что дает общую временную сложность . Обратите внимание, что это лучше, чем когда количество рабочих мест невелико по сравнению с числом рабочих.
Добавление j-го задания за время O ( jW )
[ редактировать ]Мы используем те же обозначения, что и в предыдущем разделе, хотя при необходимости модифицируем их определения. Позволять обозначим множество первых рабочие места и обозначаем множество всех рабочих.
Перед На шаге алгоритма мы предполагаем, что у нас есть паросочетание на который соответствует всем вакансиям в и потенциалы удовлетворяющее следующему условию: согласование строго по потенциалам, потенциалы всех несовпадающих рабочих равны нулю, а потенциалы всех согласованных рабочих неположительны. Заметим, что такие потенциалы свидетельствуют об оптимальности согласования.
Во время шаге, мы добавляем эта работа формировать и инициализировать . Всегда каждая вершина в будет доступен из эта работа в . Пока не содержит работника, которому не назначена работа, пусть
и обозначать любой при котором достигается минимум. После регулировки потенциалов способом, описанным в предыдущем разделе, теперь имеется узкая граница к .
- Если не имеет сопоставления, то мы имеем дополняющий путь в подграфе узких ребер из к . После переключения сопоставления по этому пути мы теперь сопоставили первый рабочих мест, и эта процедура завершается.
- В противном случае мы добавляем и работа, соответствующая этому, чтобы .
Корректировка потенциалов занимает время. Пересчет и после изменения потенциалов и также можно сделать в время. Случай 1 может произойти не более раз до того, как произойдет случай 2, и процедура завершится, что дает общую временную сложность .
Реализация на C++
[ редактировать ]Для удобства реализации в коде ниже добавлен дополнительный рабочий такой, что хранит отрицание суммы всех вычислено до сих пор. После добавлено задание и обновлено сопоставление, стоимость текущего сопоставления равна сумме всех вычислено на данный момент, или .
Этот код адаптирован из e-maxx::algo. [7]
/**
* Solution to https://open.kattis.com/problems/cordonbleu using Hungarian
* algorithm.
*/
#include <cassert>
#include <iostream>
#include <limits>
#include <vector>
using namespace std;
/**
* Sets a = min(a, b)
* @return true if b < a
*/
template <class T> bool ckmin(T &a, const T &b) { return b < a ? a = b, 1 : 0; }
/**
* Given J jobs and W workers (J <= W), computes the minimum cost to assign each
* prefix of jobs to distinct workers.
*
* @tparam T a type large enough to represent integers on the order of J *
* max(|C|)
* @param C a matrix of dimensions JxW such that C[j][w] = cost to assign j-th
* job to w-th worker (possibly negative)
*
* @return a vector of length J, with the j-th entry equaling the minimum cost
* to assign the first (j+1) jobs to distinct workers
*/
template <class T> vector<T> hungarian(const vector<vector<T>> &C) {
const int J = (int)size(C), W = (int)size(C[0]);
assert(J <= W);
// job[w] = job assigned to w-th worker, or -1 if no job assigned
// note: a W-th worker was added for convenience
vector<int> job(W + 1, -1);
vector<T> ys(J), yt(W + 1); // potentials
// -yt[W] will equal the sum of all deltas
vector<T> answers;
const T inf = numeric_limits<T>::max();
for (int j_cur = 0; j_cur < J; ++j_cur) { // assign j_cur-th job
int w_cur = W;
job[w_cur] = j_cur;
// min reduced cost over edges from Z to worker w
vector<T> min_to(W + 1, inf);
vector<int> prv(W + 1, -1); // previous worker on alternating path
vector<bool> in_Z(W + 1); // whether worker is in Z
while (job[w_cur] != -1) { // runs at most j_cur + 1 times
in_Z[w_cur] = true;
const int j = job[w_cur];
T delta = inf;
int w_next;
for (int w = 0; w < W; ++w) {
if (!in_Z[w]) {
if (ckmin(min_to[w], C[j][w] - ys[j] - yt[w]))
prv[w] = w_cur;
if (ckmin(delta, min_to[w])) w_next = w;
}
}
// delta will always be nonnegative,
// except possibly during the first time this loop runs
// if any entries of C[j_cur] are negative
for (int w = 0; w <= W; ++w) {
if (in_Z[w]) ys[job[w]] += delta, yt[w] -= delta;
else min_to[w] -= delta;
}
w_cur = w_next;
}
// update assignments along alternating path
for (int w; w_cur != W; w_cur = w) job[w_cur] = job[w = prv[w_cur]];
answers.push_back(-yt[W]);
}
return answers;
}
/**
* Sanity check: https://en.wikipedia.org/wiki/Hungarian_algorithm#Example
* First job (5):
* clean bathroom: Bob -> 5
* First + second jobs (9):
* clean bathroom: Bob -> 5
* sweep floors: Alice -> 4
* First + second + third jobs (15):
* clean bathroom: Alice -> 8
* sweep floors: Carol -> 4
* wash windows: Bob -> 3
*/
void sanity_check_hungarian() {
vector<vector<int>> costs{{8, 5, 9}, {4, 2, 4}, {7, 3, 8}};
assert((hungarian(costs) == vector<int>{5, 9, 15}));
cerr << "Sanity check passed.\n";
}
// solves https://open.kattis.com/problems/cordonbleu
void cordon_bleu() {
int N, M;
cin >> N >> M;
vector<pair<int, int>> B(N), C(M);
vector<pair<int, int>> bottles(N), couriers(M);
for (auto &b : bottles) cin >> b.first >> b.second;
for (auto &c : couriers) cin >> c.first >> c.second;
pair<int, int> rest;
cin >> rest.first >> rest.second;
vector<vector<int>> costs(N, vector<int>(N + M - 1));
auto dist = [&](pair<int, int> x, pair<int, int> y) {
return abs(x.first - y.first) + abs(x.second - y.second);
};
for (int b = 0; b < N; ++b) {
for (int c = 0; c < M; ++c) { // courier -> bottle -> restaurant
costs[b][c] =
dist(couriers[c], bottles[b]) + dist(bottles[b], rest);
}
for (int _ = 0; _ < N - 1; ++_) { // restaurant -> bottle -> restaurant
costs[b][_ + M] = 2 * dist(bottles[b], rest);
}
}
cout << hungarian(costs).back() << "\n";
}
int main() {
sanity_check_hungarian();
cordon_bleu();
}
Соединение с последовательными кратчайшими путями
[ редактировать ]Венгерский алгоритм можно рассматривать как эквивалент последовательного алгоритма кратчайшего пути для потока с минимальной стоимостью: [8] [9] метод повторного взвешивания из алгоритма Джонсона где для поиска кратчайших путей используется . Реализация из предыдущего раздела ниже переписана таким образом, чтобы подчеркнуть это. связь; можно проверить, что потенциалы для рабочих равны потенциалам от предыдущего решения до постоянного смещения. Когда граф разреженный (есть только разрешенная работа, рабочие пары), возможно оптимизировать этот алгоритм для запуска время, используя кучу Фибоначчи для определения вместо того, чтобы перебирать все работники должны найти тот, который находится на минимальном расстоянии (упоминается здесь ).
template <class T> vector<T> hungarian(const vector<vector<T>> &C) {
const int J = (int)size(C), W = (int)size(C[0]);
assert(J <= W);
// job[w] = job assigned to w-th worker, or -1 if no job assigned
// note: a W-th worker was added for convenience
vector<int> job(W + 1, -1);
vector<T> h(W); // Johnson potentials
vector<T> answers;
T ans_cur = 0;
const T inf = numeric_limits<T>::max();
// assign j_cur-th job using Dijkstra with potentials
for (int j_cur = 0; j_cur < J; ++j_cur) {
int w_cur = W; // unvisited worker with minimum distance
job[w_cur] = j_cur;
vector<T> dist(W + 1, inf); // Johnson-reduced distances
dist[W] = 0;
vector<bool> vis(W + 1); // whether visited yet
vector<int> prv(W + 1, -1); // previous worker on shortest path
while (job[w_cur] != -1) { // Dijkstra step: pop min worker from heap
T min_dist = inf;
vis[w_cur] = true;
int w_next = -1; // next unvisited worker with minimum distance
// consider extending shortest path by w_cur -> job[w_cur] -> w
for (int w = 0; w < W; ++w) {
if (!vis[w]) {
// sum of reduced edge weights w_cur -> job[w_cur] -> w
T edge = C[job[w_cur]][w] - h[w];
if (w_cur != W) {
edge -= C[job[w_cur]][w_cur] - h[w_cur];
assert(edge >= 0); // consequence of Johnson potentials
}
if (ckmin(dist[w], dist[w_cur] + edge)) prv[w] = w_cur;
if (ckmin(min_dist, dist[w])) w_next = w;
}
}
w_cur = w_next;
}
for (int w = 0; w < W; ++w) { // update potentials
ckmin(dist[w], dist[w_cur]);
h[w] += dist[w];
}
ans_cur += h[w_cur];
for (int w; w_cur != W; w_cur = w) job[w_cur] = job[w = prv[w_cur]];
answers.push_back(ans_cur);
}
return answers;
}
Матричная интерпретация
[ редактировать ]Этот вариант алгоритма соответствует формулировке Флуда: [10] и позже описан более подробно Мункресом, который доказал, что это происходит в время. [4] Вместо отслеживания потенциалов вершин алгоритм работает только с матрицей:
где - исходная матрица затрат и – потенциалы из интерпретации графа. Изменение потенциалов соответствует добавлению или вычитанию строк или столбцов этой матрицы. Алгоритм начинается с . Таким образом, это можно рассматривать как взятие исходной матрицы затрат и ее модификацию.
Для заданных n работников и задач задача записывается в виде n × n. матрицы затрат размера
1 a а2 a а3 a 4 б 1 б 2 б 3 б 4 с 1 с 2 с 3 с 4 д 1 д 2 д 3 д 4
где a, b, c и d — рабочие, которые должны выполнять задачи 1, 2, 3 и 4. a 1 , a 2 , a 3 и a 4 обозначают штрафы, понесенные, когда работник «a» выполняет задачи 1, 2, 3 и 4 соответственно.
Проблема эквивалентна назначению каждому работнику уникальной задачи, при которой общий штраф минимизируется. Обратите внимание, что над каждой задачей может работать только один работник.
Шаг 1
[ редактировать ]Для каждой строки ее минимальный элемент вычитается из каждого элемента в этой строке. Это приводит к тому, что все элементы имеют неотрицательные значения. Следовательно, задание с общим штрафом 0 по определению является минимальным заданием.
Это также приводит к появлению хотя бы одного нуля в каждой строке. Таким образом, наивный жадный алгоритм может попытаться назначить всем работникам задачу с нулевым штрафом. Это показано ниже.
0 a а2 a а3 a 4 б 1 б 2 б 3 0 с 1 0 с 3 с 4 д 1 д 2 0 д 4
Нули выше будут назначенными задачами.
В худшем случае их n ! комбинации, которые стоит попробовать, поскольку несколько нулей могут появиться в строке, если несколько элементов являются минимальными. Так что в какой-то момент этот наивный алгоритм должен разрушиться.
Шаг 2
[ редактировать ]Иногда может оказаться, что матрица на этом этапе не может быть использована для присвоения, как в случае с матрицей ниже.
0 a а2 0 a 4 б 1 0 б 3 0 0 с 2 с 3 с 4 0 д 2 д 3 д 4
Чтобы преодолеть эту проблему, мы повторяем описанную выше процедуру для всех столбцов (т. е. минимальный элемент в каждом столбце вычитается из всех элементов в этом столбце ), а затем проверяем, возможно ли присвоение со штрафом 0.
В большинстве ситуаций это даст результат, но если это все же невозможно, то нужно продолжать.
Шаг 3
[ редактировать ]Все нули в матрице должны быть закрыты путем выделения как можно меньшего количества строк и/или столбцов. Шаги 3 и 4 представляют собой один из способов достижения этой цели.
Для каждой строки попробуйте присвоить произвольный ноль. Назначенные задачи обозначаются нулем, отмеченным звездочкой. Обратите внимание, что назначения не могут находиться в одной строке или столбце.
- Мы присваиваем первый ноль строки 1. Второй ноль строки 1 не может быть присвоен.
- Мы присваиваем первый ноль строки 2. Второй ноль строки 2 не может быть присвоен.
- Нули в строке 3 и строке 4 назначить невозможно, поскольку они находятся в том же столбце, что и ноль, назначенный в строке 1.
Мы могли бы закончить другим заданием, если выберем другой порядок строк и столбцов.
0* a а2 0 a 4 б 1 0* б 3 0 0 с 2 с 3 с 4 0 д 2 д 3 д 4
Шаг 4
[ редактировать ]Закройте все столбцы, содержащие ноль (помеченный звездочкой).
× × 0* aа2 0 a 4 б 1 0* б 3 0 0 с 2 с 3 с 4 0 д 2 д 3 д 4
Найдите непокрытый ноль и заштрихуйте его (отметьте его символом штриха ). Если такой ноль не найден, то есть все нули покрыты, перейдите к шагу 5.
- Если ноль находится в той же строке, что и ноль со звездочкой, закройте соответствующую строку и откройте столбец нуля со звездочкой.
- Затем ПЕРЕЙДИТЕ к «Найдите непокрытый ноль и заштрихуйте его».
- Здесь раскрыт второй ноль строки 1. Поскольку в строке 1 отмечен еще один ноль, мы закрываем строку 1 и открываем столбец 1.
- Затем открывается второй ноль второй строки. Закрываем вторую строку и раскрываем второй столбец.
× 0* aа2 0' a 4 × б 1 0* б 3 0 0 с 2 с 3 с 4 0 д 2 д 3 д 4
0* aа2 0' a 4 × б 1 0* б 3 0' × 0 с 2 с 3 с 4 0 д 2 д 3 д 4
- В противном случае непокрытый ноль не имеет назначенного нуля в своей строке. Мы прокладываем путь, начиная с нуля, выполнив следующие шаги:
- Подэтап 1: Найдите ноль со звездочкой в соответствующем столбце. Если он есть, перейдите к подэтапу 2, иначе остановитесь.
- Подшаг 2: Найдите нуль со штрихом в соответствующей строке (один всегда должен быть). Перейдите к подэтапу 1.
Ноль в строке 3 открыт. Добавляем к пути первый ноль строки 1, затем второй ноль строки 1, и все готово.
0* aа2 0' a 4 × б 1 0* б 3 0' × 0' с 2 с 3 с 4 0 д 2 д 3 д 4
- (Продолжение другой ветки) Для всех нулей, встречающихся на пути, нули со звездочкой и нули со звездочкой.
- Поскольку путь начинается и заканчивается нулем со штрихом при замене нулей со звездочкой, мы присваиваем еще один ноль.
0 aа2 0* a 4 б 1 0* б 3 0 0* с 2 с 3 с 4 0 д 2 д 3 д 4
- (Продолжение остальной ветки) Снимите все штриховые нули и откройте все строки.
- Повторите предыдущие шаги (продолжайте цикл до тех пор, пока не будет достигнут вышеуказанный «перейти к шагу 5»).
- Закрываем столбцы 1, 2 и 3. Второй ноль в строке 2 открыт, поэтому закрываем строку 2 и раскрываем столбец 2:
× × 0 aа2 0* a 4 б 1 0* б 3 0' × 0* с 2 с 3 с 4 0 д 2 д 3 д 4
Все нули теперь покрываются минимальным количеством строк и столбцов.
Вышеупомянутое подробное описание — это всего лишь один из способов нарисовать минимальное количество строк, охватывающих все нули. Другие методы также работают.
Шаг 5
[ редактировать ]Если количество звездчатых нулей равно n (или в общем случае , где n — количество людей, а m — количество рабочих мест), алгоритм завершает работу. См. подраздел « Результат» ниже, чтобы узнать, как интерпретировать результаты.
В противном случае найдите наименьшее непокрытое значение. Вычтите это значение из каждого немаркированного элемента и добавьте его к каждому элементу, покрытому двумя линиями. Вернитесь к шагу 4.
Это эквивалентно вычитанию числа из всех незакрытых строк и добавлению того же числа ко всем покрытым столбцам. Эти операции не меняют оптимальные назначения.
Результат
[ редактировать ]Если следовать этой конкретной версии алгоритма, нули со звездочками образуют минимальное назначение.
По теореме Кенига [11] минимальное количество строк (минимальное покрытие вершин [12] ) будет n (размер максимального соответствия [13] ). Таким образом, когда требуется n строк, минимальное назначение стоимости можно найти, рассматривая только нули в матрице.
Библиография
[ редактировать ]- Р. Е. Буркард, М. Делл'Амико, С. Мартелло: Проблемы с заданием (пересмотренное переиздание). СИАМ, Филадельфия (Пенсильвания), 2012 г. ISBN 978-1-61197-222-1
- М. Фишетти, «Уроки операционного исследования», Edizioni Libreria Progetto Padova, Италия, 1995.
- Р. Ахуджа , Т. Маньянти , Дж. Орлин , «Сетевые потоки», Prentice Hall, 1993.
- С. Мартелло, «Йено Эгервари: от истоков венгерского алгоритма до спутниковой связи». Центральноевропейский журнал операционных исследований 18: 47–58, 2010 г.
Ссылки
[ редактировать ]- ^ Гарольд В. Кун, «Венгерский метод решения задачи назначения», Naval Research Logistics Quarterly , 2 : 83–97, 1955. Оригинальная публикация Куна.
- ^ Гарольд В. Кун, «Варианты венгерского метода решения задач назначения», Naval Research Logistics Quarterly , 3 : 253–258, 1956.
- ^ «Презентация» . Архивировано из оригинала 16 октября 2015 года.
- ^ Jump up to: а б Дж. Манкрес, «Алгоритмы для задач назначения и транспортировки», Журнал Общества промышленной и прикладной математики , 5 (1): 32–38, март 1957 г.
- ^ Эдмондс, Джек; Карп, Ричард М. (1 апреля 1972 г.). «Теоретические улучшения алгоритмической эффективности решения задач сетевого потока» . Журнал АКМ . 19 (2): 248–264. дои : 10.1145/321694.321699 . S2CID 6375478 .
- ^ Томизава, Н. (1971). «О некоторых методах, полезных для решения проблем транспортной сети». Сети . 1 (2): 173–194. дои : 10.1002/net.3230010206 . ISSN 1097-0037 .
- ^ «Венгерский алгоритм решения задачи о назначениях» . e-maxx:: что-то . 23 августа 2012 года . Проверено 13 мая 2023 г.
- ^ Джейкоб Коглер (20 декабря 2022 г.). «Поток с минимальной стоимостью — последовательный алгоритм кратчайшего пути» . Алгоритмы соревновательного программирования . Проверено 14 мая 2023 г.
- ^ «Решение задачи назначения с использованием минимального потока затрат» . Алгоритмы соревновательного программирования . 17 июля 2022 г. Проверено 14 мая 2023 г.
- ^ Флуд, Меррилл М. (1956). «Задача коммивояжера». Исследование операций . 4 (1): 61–75. дои : 10.1287/opre.4.1.61 . ISSN 0030-364X .
- ^ Теорема Кенига (теория графов) Теорема Кенига
- ^ вершин Минимальное покрытие вершин
- ^ Сопоставление (теория графов) сопоставление
Внешние ссылки
[ редактировать ]- Брафф, Дерек, Задача о назначениях и венгерский метод (матричный формализм).
- Мордехай Дж. Голин, Двустороннее сопоставление и венгерский метод (биграфный формализм), Конспект курса, Гонконгский университет науки и технологий .
- Венгерский алгоритм максимального соответствия (оба формализма) на веб-сайте Brilliant.
- Р. А. Пилигрим , Алгоритм назначения Мункреса. Модифицировано для прямоугольных матриц , Конспект курса, Государственный университет Мюррея .
- Майк Доус , Задача оптимального назначения , Конспект курса, Университет Западного Онтарио .
- О венгерском методе Куна – дань уважения из Венгрии , Андраш Франк , Исследовательская группа Эгервари, Пазмани П. setany 1/C, H1117, Будапешт, Венгрия.
- Лекция: «Основы исследования операций – Задача о назначениях – Венгерский алгоритм» , профессор Г. Сринивасан, факультет исследований управления, ИИТ Мадрас.
- Расширение: анализ чувствительности присваивания (с временной сложностью O(n^4)) , Лю, Шелл.
- Решите любую задачу о заданиях онлайн , содержащее пошаговое объяснение венгерского алгоритма.
Реализации
[ редактировать ]Обратите внимание, что не все из них удовлетворяют временная сложность, даже если они так заявляют. Некоторые могут содержать ошибки, реализуйте более медленные алгоритм или иметь другие недостатки. В худшем случае пример кода, ссылка на который содержится в Википедии, впоследствии может быть изменен и включен в него код эксплойта. Проверка и бенчмаркинг необходимы при использовании таких примеров кода от неизвестных авторов.
- Версии кода RA Pilgrim на Lua и Python (заявляющие, что временная сложность)
- Реализация Юлии
- Реализация C, утверждающая временная сложность
- Реализация Java утверждает временная сложность
- Реализация Python
- Реализация Ruby с модульными тестами
- Реализация C# утверждает временная сложность
- Реализация D с модульными тестами (порт версии Java, утверждающей )
- Онлайн-интерактивная реализация
- Последовательная и параллельная реализации.
- Матлаб и Си
- Perl-реализация
- реализация на С++
- Реализация C++ утверждает временная сложность (лицензия с открытым исходным кодом в стиле BSD)
- Реализация MATLAB
- C-реализация
- Реализация JavaScript с модульными тестами (порт версии Java, утверждающей временная сложность)
- Пакет Clue R предлагает реализациюsolve_LSAP.
- Реализация Node.js на GitHub
- Реализация Python в пакете scipy