Content deleted Content added
Acknowledge that the algorithm works even with initially negative costs |
|||
(19 intermediate revisions by 13 users not shown) | |||
Line 2:
The '''Hungarian method''' is a [[combinatorial optimization]] [[algorithm]] that solves the [[assignment problem]] in [[polynomial time]] and which anticipated later [[Duality (optimization)|primal–dual methods]]. It was developed and published in 1955 by [[Harold Kuhn]], who gave it the name "Hungarian method" because the algorithm was largely based on the earlier works of two Hungarian mathematicians, [[Dénes Kőnig]] and [[Jenő Egerváry]].<ref name="kuhn1955">Harold W. Kuhn, "The Hungarian Method for the assignment problem", ''[[Naval Research Logistics Quarterly]]'', '''2''': 83–97, 1955. Kuhn's original publication.</ref><ref name="kuhn1956">Harold W. Kuhn, "Variants of the Hungarian method for assignment problems", ''Naval Research Logistics Quarterly'', '''3''': 253–258, 1956.</ref> However, in 2006 it was discovered that [[Carl Gustav Jacobi]] had solved the assignment problem in the 19th century, and the solution had been published posthumously in 1890 in Latin.<ref>{{Cite web|url=http://www.lix.polytechnique.fr/~ollivier/JACOBI/presentationlEngl.htm|archive-url = https://web.archive.org/web/20151016182619/http://www.lix.polytechnique.fr/~ollivier/JACOBI/presentationlEngl.htm|archive-date = 16 October 2015|title = Presentation}}</ref>
[[James Munkres]] reviewed the algorithm in 1957 and observed that it is [[
==The problem==
Line 8:
=== Example ===
In this simple example, there are three workers: Alice, Bob and
:{| class="wikitable" style="text-align:center;"
! {{diagonal split header|Worker |Task}}
Line 25:
| '''$3'''
|-
!
| $9
| '''$4'''
Line 31:
|}
The Hungarian method, when applied to the above table, would give the minimum cost: this is $15, achieved by having Alice clean the bathroom,
:{| class="wikitable" style="text-align:center;"
! {{diagonal split header|Sweep|Clean}}
! Alice
! Bob
!
|-
! Alice
Line 48:
| $18
|-
!
| '''$15'''
| $16
Line 71:
==The algorithm in terms of bipartite graphs==
Let us call a function <math>y: (S\cup T) \to \mathbb{R}</math> a '''potential''' if <math>y(i)+y(j) \leq c(i, j)</math> for each <math>i \in S, j \in T</math>.
The ''value'' of potential {{mvar|y}} is the sum of the potential over all vertices:
:<math>\sum_{v\in S\cup T} y(v)</math>. The cost of each perfect matching is at least the value of each potential: the total cost of the matching is the sum of costs of all edges it contains; the cost of each edge is at least the sum of potentials of its endpoints; since the matching is perfect, each vertex is an endpoint of exactly one edge; hence the total cost is at least the total potential. The Hungarian method finds a perfect matching and a potential such that the matching cost equals the potential value. This proves that both of them are optimal. In fact, the Hungarian method finds a perfect matching of '''tight edges''': an edge <math>ij</math> is called tight for a potential {{mvar|y}} if <math>y(i)+y(j) = c(i, j)</math>. Let us denote the [[Glossary of graph theory#Subgraphs|subgraph]] of '''tight''' edges by <math>G_y</math>. The cost of a perfect matching in <math>G_y</math> (if there is one) equals the value of {{mvar|y}}.
Line 87 ⟶ 91:
:<math>\Delta := \min \{c(i,j)-y(i)-y(j): i \in Z \cap S, j \in T \setminus Z\}.</math>
{{math|Δ}} is well defined because at least one such edge <math>ij</math> must exist whenever the matching is not yet of maximum possible size (see the following section); it is positive because there are no tight edges between <math>Z \cap S</math> and <math>T \setminus Z</math>. Increase {{mvar|y}} by {{math|Δ}} on the vertices of <math>Z \cap S</math> and decrease {{mvar|y}} by {{math|Δ}} on the vertices of <math>Z \cap T</math>. The resulting {{mvar|y}} is still a potential, and although the graph <math>G_y</math> changes, it still contains {{mvar|M}} (see the next subsections). We orient the new edges from {{mvar|S}} to {{mvar|T}}. By the definition of {{math|Δ}} the set {{mvar|Z}} of vertices reachable from <math>R_S</math> increases (note that the number of tight edges does not necessarily increase). If the vertex added to <math>Z \cap T</math> is unmatched (that is, it is also in <math>R_T</math>), then at the next iteration the graph will have an augmenting path.
We repeat these steps until {{mvar|M}} is a perfect matching, in which case it gives a minimum cost assignment. The running time of this version of the method is <math>O(n^4)</math>: {{mvar|M}} is augmented {{mvar|n}} times, and in a phase where {{mvar|M}} is unchanged, there are at most {{mvar|n}} potential changes (since {{mvar|Z}} increases every time). The time sufficient for a potential change is <math>O(n^2)</math>.
Line 144 ⟶ 148:
*/
import std;
template <typename T, typename U>
using Pair = std::pair<T, U>;
template <typename T>
using Vector = std::vector<T>;
template <typename T>
using NumericLimits = std::numeric_limits<T>;
/**
* @brief Checks if b < a
*
* Sets a = min(a, b)
* @param a The first parameter to check
* @param b The second parameter to check
* @tparam The type to perform the check on
* @return true if b < a
*/
template <
constexpr bool ckmin(T return b < a ? a = b, } /**
* @brief Performs the Hungarian algorithm.
*
* Given J jobs and W workers (J <= W), computes the minimum cost to assign each
* prefix of jobs to distinct workers.
Line 168 ⟶ 188:
* to assign the first (j+1) jobs to distinct workers
*/
template <typename T>
const int J = (int)size(C), W = (int)size(C[0]);▼
const int J = static_cast<int>(C.size());
const int W = static_cast<int>(C[0].size());
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<T> yt(W + 1); // potentials // -yt[W] will equal the sum of all deltas
const T inf =
for (int
int
job[
// min reduced cost over edges from Z to worker w
while (job[
const int j = job[
T delta = inf;
int
for (int w = 0; w < W; ++w) {
if (!
if (ckmin(
if (ckmin(delta,
wNext = w;
}
}
// delta will always be nonnegative,
// except possibly during the first time this loop runs
// if any entries of C[
for (int w = 0; w <= W; ++w) {
if (
yt[w] -= delta;
} else {
minTo[w] -= delta;
}▼
}
}
// update assignments along alternating path
for (int w;
job[wCur] = job[w = prev[wCur]];
answers.push_back(-yt[W]);
}
Line 214 ⟶ 243:
/**
* @brief Performs a sanity check for the Hungarian algorithm.
*
* Sanity check: https://en.wikipedia.org/wiki/Hungarian_algorithm#Example
* First job (5):
Line 222 ⟶ 253:
* First + second + third jobs (15):
* clean bathroom: Alice -> 8
* sweep floors:
* wash windows: Bob -> 3
*/
void
assert((hungarian(costs) ==
}
/**
void cordon_bleu() {▼
*/
int N, M;▼
vector<pair<int, int>> bottles(N), couriers(M);▼
Vector<Pair<int, int>> B(N);
for (auto &c : couriers) cin >> c.first >> c.second;▼
cin >> rest.first >> rest.second;▼
for (const Pair<int, int>& c: couriers)
Pair<int, int> rest;
▲ std::cin >> rest.first >> rest.second;
Vector<Vector<int>> costs(N, Vector<int>(N + M - 1));
auto dist = [&](const Pair<int, int>& x, const Pair<int, int>& y) -> int {
return std::abs(x.first - y.first) + std::abs(x.second - y.second);
};
for (int b = 0; b < N; ++b) {
for (int c = 0; c < M; ++c)
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);
}
}
/**
* @brief Entry point into the program.
*
* @return The return code of the program.
*/
int main() {
}
</syntaxhighlight>
Line 286 ⟶ 326:
<syntaxhighlight lang="cpp" line="1">
template <typename T>
▲ const int J = (int)size(C), W = (int)size(C[0]);
const int W = static_cast<int>(C[0].size());
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
T
const T inf =
// assign
for (int
int
job[
dist[W] = 0;
while (job[
T
vis[
int
// consider extending shortest path by
for (int w = 0; w < W; ++w) {
if (!vis[w]) {
// sum of reduced edge weights
T edge = C[job[
if (
edge -= C[job[
assert(edge >= 0); // consequence of Johnson potentials
}
if (ckmin(dist[w], dist[
if (ckmin(minDist, dist[w]))
wNext = w;
}
}
}
for (int w = 0; w < W; ++w) { // update potentials
ckmin(dist[w], dist[
h[w] += dist[w];
}
ans_cur += h[
for (int w;
answers.push_back(ansCur);
}
return answers;
Line 525 ⟶ 570:
If the number of starred zeros is {{mvar|n}} (or in the general case <math>min(n,m)</math>, where {{mvar|n}} is the number of people and {{mvar|m}} is the number of jobs), the algorithm terminates. See the [[#Result|Result]] subsection below on how to interpret the results.
This is equivalent to subtracting a number from all rows which are not covered and adding the same number to all columns which are covered. These operations do not change optimal assignments.
Line 564 ⟶ 609:
* [https://github.com/evansenter/gene/blob/f515fd73cb9d6a22b4d4b146d70b6c2ec6a5125b/objects/extensions/hungarian.rb Ruby implementation with unit tests]
* [https://github.com/antifriz/hungarian-algorithm-n3 C# implementation claiming <math>O(n^3)</math> time complexity]
* [http://www.fantascienza.net/leonardo/so/hungarian.d D implementation with unit tests (port of a Java version claiming <math>O(n^3)</math>)] {{Webarchive|url=https://web.archive.org/web/20191230174110/http://www.fantascienza.net/leonardo/so/hungarian.d |date=30 December 2019 }}
* [http://www.ifors.ms.unimelb.edu.au/tutorial/hungarian/welcome_frame.html Online interactive implementation]<!--Please note that this implements a variant of the algorithm as described above. -->
* [http://www.netlib.org/utk/lsi/pcwLSI/text/node220.html Serial and parallel implementations.]
* [http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=6543 Matlab and C] {{Webarchive|url=https://web.archive.org/web/20080503063209/http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=6543 |date=3 May 2008 }}
* [https://metacpan.org/module/Algorithm::Munkres Perl implementation]
* [https://github.com/saebyn/munkres-cpp C++ implementation]
|