Kuhn's Algorithm for Maximum Bipartite Matching¶
Problem¶
You are given a bipartite graph
Algorithm Description¶
Required Definitions¶
-
A matching
-
A maximal matching is a matching
-
A maximum matching (also known as maximum-cardinality matching) is a matching that contains the largest possible number of edges. Every maximum matching is a maximal matching.
-
A path of length
-
An alternating path (in a bipartite graph, with respect to some matching) is a path in which the edges alternately belong / do not belong to the matching.
-
An augmenting path (in a bipartite graph, with respect to some matching) is an alternating path whose initial and final vertices are unsaturated, i.e., they do not belong in the matching.
-
The symmetric difference (also known as the disjunctive union) of sets
Berge's lemma¶
This lemma was proven by the French mathematician Claude Berge in 1957, although it already was observed by the Danish mathematician Julius Petersen in 1891 and the Hungarian mathematician Denés Kőnig in 1931.
Formulation¶
A matching
Proof¶
Both sides of the bi-implication will be proven by contradiction.
-
A matching
Let there be an augmenting path
Formally, given an augmenting path
-
A matching
Let there be a matching
- an isolated vertex
- a (simple) path whose edges are alternately from
- a cycle of even length whose edges are alternately from
Since
Kuhn's algorithm¶
Kuhn's algorithm is a direct application of Berge's lemma. It is essentially described as follows:
First, we take an empty matching. Then, while the algorithm is able to find an augmenting path, we update the matching by alternating it along this path and repeat the process of finding the augmenting path. As soon as it is not possible to find such a path, we stop the process - the current matching is the maximum.
It remains to detail the way to find augmenting paths. Kuhn's algorithm simply searches for any of these paths using depth-first or breadth-first traversal. The algorithm looks through all the vertices of the graph in turn, starting each traversal from it, trying to find an augmenting path starting at this vertex.
The algorithm is more convenient to describe if we assume that the input graph is already split into two parts (although, in fact, the algorithm can be implemented in such a way that the input graph is not explicitly split into two parts).
The algorithm looks at all the vertices
The search for an augmenting path is carried out using a special depth-first or breadth-first traversal (usually depth-first traversal is used for ease of implementation).
Initially, the depth-first traversal is at the current unsaturated vertex
So, this traversal, launched from the vertex
After all the vertices
Running time¶
Kuhn's algorithm can be thought of as a series of
However, this estimate can be improved slightly. It turns out that for Kuhn's algorithm, it is important which part of the graph is chosen as the first and which as the second.
Indeed, in the implementation described above, the depth/breadth-first traversal starts only from the vertices of the first part, so the entire algorithm is executed in
time
Implementation¶
Standard implementation¶
Let us present here an implementation of the above algorithm based on depth-first traversal and accepting a bipartite graph in the form of a graph explicitly split into two parts. This implementation is very concise, and perhaps it should be remembered in this form.
Here
Then there are two auxiliary arrays:
A function
Inside the function, all the edges outgoing from the vertex
The main program first indicates that the current matching is empty (the list
It is worth noting that the size of the matching is easy to get as the number of calls
int n, k;
vector<vector<int>> g;
vector<int> mt;
vector<bool> used;
bool try_kuhn(int v) {
if (used[v])
return false;
used[v] = true;
for (int to : g[v]) {
if (mt[to] == -1 || try_kuhn(mt[to])) {
mt[to] = v;
return true;
}
}
return false;
}
int main() {
//... reading the graph ...
mt.assign(k, -1);
for (int v = 0; v < n; ++v) {
used.assign(n, false);
try_kuhn(v);
}
for (int i = 0; i < k; ++i)
if (mt[i] != -1)
printf("%d %d\n", mt[i] + 1, i + 1);
}
We repeat once again that Kuhn's algorithm is easy to implement in such a way that it works on graphs that are known to be bipartite, but their explicit splitting into two parts
has not been given. In this case, it will be necessary to abandon the convenient division into two parts, and store all the information for all vertices of the graph. For this,
an array of lists
Improved implementation¶
Let us modify the algorithm as follows. Before the main loop of the algorithm, we will find an arbitrary matching by some simple algorithm (a simple heuristic algorithm),
and only then we will execute a loop with calls to the
For example, you can simply iterate over all the vertices of the first part, and for each of them, find an arbitrary edge that can be added to the matching, and add it. Even such a simple heuristic can speed up Kuhn's algorithm several times.
Please note that the main loop will have to be slightly modified. Since when calling the function
In the implementation, only the code in the
int main() {
// ... reading the graph ...
mt.assign(k, -1);
vector<bool> used1(n, false);
for (int v = 0; v < n; ++v) {
for (int to : g[v]) {
if (mt[to] == -1) {
mt[to] = v;
used1[v] = true;
break;
}
}
}
for (int v = 0; v < n; ++v) {
if (used1[v])
continue;
used.assign(n, false);
try_kuhn(v);
}
for (int i = 0; i < k; ++i)
if (mt[i] != -1)
printf("%d %d\n", mt[i] + 1, i + 1);
}
Another good heuristic is as follows. At each step, it will search for the vertex of the smallest degree (but not isolated), select any edge from it and add it to the matching, then remove both these vertices with all incident edges from the graph. Such greed works very well on random graphs; in many cases it even builds the maximum matching (although there is a test case against it, on which it will find a matching that is much smaller than the maximum).
Notes¶
- Kuhn's algorithm is a subroutine in the Hungarian algorithm, also known as the Kuhn-Munkres algorithm.
- Kuhn's algorithm runs in
- The minimum vertex cover problem is NP-hard for general graphs. However, Kőnig's theorem gives that, for bipartite graphs, the cardinality of the maximum matching equals the cardinality of the minimum vertex cover. Hence, we can use maximum bipartite matching algorithms to solve the minimum vertex cover problem in polynomial time for bipartite graphs.