![JAR search and dependency download from the Maven repository](/logo.png)
edu.princeton.cs.algs4.HopcroftKarp Maven / Gradle / Ivy
Show all versions of algorithm Show documentation
/******************************************************************************
* Compilation: javac HopcroftKarp.java
* Execution: java HopcroftKarp V1 V2 E
* Dependencies: FordFulkerson.java FlowNetwork.java FlowEdge.java
* BipartiteX.java
*
* Find a maximum cardinality matching (and minimum cardinality vertex cover)
* in a bipartite graph using Hopcroft-Karp algorithm.
*
******************************************************************************/
package edu.princeton.cs.algs4;
import java.util.Iterator;
/**
* The {@code HopcroftKarp} class represents a data type for computing a
* maximum (cardinality) matching and a
* minimum (cardinality) vertex cover in a bipartite graph.
* A bipartite graph in a graph whose vertices can be partitioned
* into two disjoint sets such that every edge has one endpoint in either set.
* A matching in a graph is a subset of its edges with no common
* vertices. A maximum matching is a matching with the maximum number
* of edges.
* A perfect matching is a matching which matches all vertices in the graph.
* A vertex cover in a graph is a subset of its vertices such that
* every edge is incident to at least one vertex. A minimum vertex cover
* is a vertex cover with the minimum number of vertices.
* By Konig's theorem, in any biparite
* graph, the maximum number of edges in matching equals the minimum number
* of vertices in a vertex cover.
* The maximum matching problem in nonbipartite graphs is
* also important, but all known algorithms for this more general problem
* are substantially more complicated.
*
* This implementation uses the Hopcroft-Karp algorithm.
* The order of growth of the running time in the worst case is
* (E + V) sqrt(V),
* where E is the number of edges and V is the number
* of vertices in the graph. It uses extra space (not including the graph)
* proportional to V.
*
* See also {@link BipartiteMatching}, which solves the problem in
* O(E V) time using the alternating path algorithm
* and BipartiteMatchingToMaxflow,
* which solves the problem in O(E V) time via a reduction
* to the maxflow problem.
*
* For additional documentation, see
* Section 6.5
* Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
public class HopcroftKarp {
private static final int UNMATCHED = -1;
private final int V; // number of vertices in the graph
private BipartiteX bipartition; // the bipartition
private int cardinality; // cardinality of current matching
private int[] mate; // mate[v] = w if v-w is an edge in current matching
// = -1 if v is not in current matching
private boolean[] inMinVertexCover; // inMinVertexCover[v] = true iff v is in min vertex cover
private boolean[] marked; // marked[v] = true iff v is reachable via alternating path
private int[] distTo; // distTo[v] = number of edges on shortest path to v
/**
* Determines a maximum matching (and a minimum vertex cover)
* in a bipartite graph.
*
* @param G the bipartite graph
* @throws IllegalArgumentException if {@code G} is not bipartite
*/
public HopcroftKarp(Graph G) {
bipartition = new BipartiteX(G);
if (!bipartition.isBipartite()) {
throw new IllegalArgumentException("graph is not bipartite");
}
// initialize empty matching
this.V = G.V();
mate = new int[V];
for (int v = 0; v < V; v++)
mate[v] = UNMATCHED;
// the call to hasAugmentingPath() provides enough info to reconstruct level graph
while (hasAugmentingPath(G)) {
// to be able to iterate over each adjacency list, keeping track of which
// vertex in each adjacency list needs to be explored next
Iterator[] adj = (Iterator[]) new Iterator[G.V()];
for (int v = 0; v < G.V(); v++)
adj[v] = G.adj(v).iterator();
// for each unmatched vertex s on one side of bipartition
for (int s = 0; s < V; s++) {
if (isMatched(s) || !bipartition.color(s)) continue; // or use distTo[s] == 0
// find augmenting path from s using nonrecursive DFS
Stack path = new Stack();
path.push(s);
while (!path.isEmpty()) {
int v = path.peek();
// retreat, no more edges in level graph leaving v
if (!adj[v].hasNext())
path.pop();
// advance
else {
// process edge v-w only if it is an edge in level graph
int w = adj[v].next();
if (!isLevelGraphEdge(v, w)) continue;
// add w to augmenting path
path.push(w);
// augmenting path found: update the matching
if (!isMatched(w)) {
// StdOut.println("augmenting path: " + toString(path));
while (!path.isEmpty()) {
int x = path.pop();
int y = path.pop();
mate[x] = y;
mate[y] = x;
}
cardinality++;
}
}
}
}
}
// also find a min vertex cover
inMinVertexCover = new boolean[V];
for (int v = 0; v < V; v++) {
if (bipartition.color(v) && !marked[v]) inMinVertexCover[v] = true;
if (!bipartition.color(v) && marked[v]) inMinVertexCover[v] = true;
}
assert certifySolution(G);
}
// string representation of augmenting path (chop off last vertex)
private static String toString(Iterable path) {
StringBuilder sb = new StringBuilder();
for (int v : path)
sb.append(v + "-");
String s = sb.toString();
s = s.substring(0, s.lastIndexOf('-'));
return s;
}
// is the edge v-w in the level graph?
private boolean isLevelGraphEdge(int v, int w) {
return (distTo[w] == distTo[v] + 1) && isResidualGraphEdge(v, w);
}
// is the edge v-w a forward edge not in the matching or a reverse edge in the matching?
private boolean isResidualGraphEdge(int v, int w) {
if ((mate[v] != w) && bipartition.color(v)) return true;
if ((mate[v] == w) && !bipartition.color(v)) return true;
return false;
}
/*
* is there an augmenting path?
* - if so, upon termination adj[] contains the level graph;
* - if not, upon termination marked[] specifies those vertices reachable via an alternating
* path from one side of the bipartition
*
* an alternating path is a path whose edges belong alternately to the matching and not
* to the matching
*
* an augmenting path is an alternating path that starts and ends at unmatched vertices
*/
private boolean hasAugmentingPath(Graph G) {
// shortest path distances
marked = new boolean[V];
distTo = new int[V];
for (int v = 0; v < V; v++)
distTo[v] = Integer.MAX_VALUE;
// breadth-first search (starting from all unmatched vertices on one side of bipartition)
Queue queue = new Queue();
for (int v = 0; v < V; v++) {
if (bipartition.color(v) && !isMatched(v)) {
queue.enqueue(v);
marked[v] = true;
distTo[v] = 0;
}
}
// run BFS until an augmenting path is found
// (and keep going until all vertices at that distance are explored)
boolean hasAugmentingPath = false;
while (!queue.isEmpty()) {
int v = queue.dequeue();
for (int w : G.adj(v)) {
// forward edge not in matching or backwards edge in matching
if (isResidualGraphEdge(v, w)) {
if (!marked[w]) {
distTo[w] = distTo[v] + 1;
marked[w] = true;
if (!isMatched(w))
hasAugmentingPath = true;
// stop enqueuing vertices once an alternating path has been discovered
// (no vertex on same side will be marked if its shortest path distance longer)
if (!hasAugmentingPath) queue.enqueue(w);
}
}
}
}
return hasAugmentingPath;
}
/**
* Returns the vertex to which the specified vertex is matched in
* the maximum matching computed by the algorithm.
*
* @param v the vertex
* @return the vertex to which vertex {@code v} is matched in the
* maximum matching; {@code -1} if the vertex is not matched
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*
*/
public int mate(int v) {
validate(v);
return mate[v];
}
/**
* Returns true if the specified vertex is matched in the maximum matching
* computed by the algorithm.
*
* @param v the vertex
* @return {@code true} if vertex {@code v} is matched in maximum matching;
* {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*
*/
public boolean isMatched(int v) {
validate(v);
return mate[v] != UNMATCHED;
}
/**
* Returns the number of edges in any maximum matching.
*
* @return the number of edges in any maximum matching
*/
public int size() {
return cardinality;
}
/**
* Returns true if the graph contains a perfect matching.
* That is, the number of edges in a maximum matching is equal to one half
* of the number of vertices in the graph (so that every vertex is matched).
*
* @return {@code true} if the graph contains a perfect matching;
* {@code false} otherwise
*/
public boolean isPerfect() {
return cardinality * 2 == V;
}
/**
* Returns true if the specified vertex is in the minimum vertex cover
* computed by the algorithm.
*
* @param v the vertex
* @return {@code true} if vertex {@code v} is in the minimum vertex cover;
* {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public boolean inMinVertexCover(int v) {
validate(v);
return inMinVertexCover[v];
}
// throw an exception if vertex is invalid
private void validate(int v) {
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**************************************************************************
*
* The code below is solely for testing correctness of the data type.
*
**************************************************************************/
// check that mate[] and inVertexCover[] define a max matching and min vertex cover, respectively
private boolean certifySolution(Graph G) {
// check that mate(v) = w iff mate(w) = v
for (int v = 0; v < V; v++) {
if (mate(v) == -1) continue;
if (mate(mate(v)) != v) return false;
}
// check that size() is consistent with mate()
int matchedVertices = 0;
for (int v = 0; v < V; v++) {
if (mate(v) != -1) matchedVertices++;
}
if (2*size() != matchedVertices) return false;
// check that size() is consistent with minVertexCover()
int sizeOfMinVertexCover = 0;
for (int v = 0; v < V; v++)
if (inMinVertexCover(v)) sizeOfMinVertexCover++;
if (size() != sizeOfMinVertexCover) return false;
// check that mate() uses each vertex at most once
boolean[] isMatched = new boolean[V];
for (int v = 0; v < V; v++) {
int w = mate[v];
if (w == -1) continue;
if (v == w) return false;
if (v >= w) continue;
if (isMatched[v] || isMatched[w]) return false;
isMatched[v] = true;
isMatched[w] = true;
}
// check that mate() uses only edges that appear in the graph
for (int v = 0; v < V; v++) {
if (mate(v) == -1) continue;
boolean isEdge = false;
for (int w : G.adj(v)) {
if (mate(v) == w) isEdge = true;
}
if (!isEdge) return false;
}
// check that inMinVertexCover() is a vertex cover
for (int v = 0; v < V; v++)
for (int w : G.adj(v))
if (!inMinVertexCover(v) && !inMinVertexCover(w)) return false;
return true;
}
/**
* Unit tests the {@code HopcroftKarp} data type.
* Takes three command-line arguments {@code V1}, {@code V2}, and {@code E};
* creates a random bipartite graph with {@code V1} + {@code V2} vertices
* and {@code E} edges; computes a maximum matching and minimum vertex cover;
* and prints the results.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
int V1 = Integer.parseInt(args[0]);
int V2 = Integer.parseInt(args[1]);
int E = Integer.parseInt(args[2]);
Graph G = GraphGenerator.bipartite(V1, V2, E);
if (G.V() < 1000) StdOut.println(G);
HopcroftKarp matching = new HopcroftKarp(G);
// print maximum matching
StdOut.printf("Number of edges in max matching = %d\n", matching.size());
StdOut.printf("Number of vertices in min vertex cover = %d\n", matching.size());
StdOut.printf("Graph has a perfect matching = %b\n", matching.isPerfect());
StdOut.println();
if (G.V() >= 1000) return;
StdOut.print("Max matching: ");
for (int v = 0; v < G.V(); v++) {
int w = matching.mate(v);
if (matching.isMatched(v) && v < w) // print each edge only once
StdOut.print(v + "-" + w + " ");
}
StdOut.println();
// print minimum vertex cover
StdOut.print("Min vertex cover: ");
for (int v = 0; v < G.V(); v++)
if (matching.inMinVertexCover(v))
StdOut.print(v + " ");
StdOut.println();
}
}
/******************************************************************************
* Copyright 2002-2018, Robert Sedgewick and Kevin Wayne.
*
* This file is part of algs4.jar, which accompanies the textbook
*
* Algorithms, 4th edition by Robert Sedgewick and Kevin Wayne,
* Addison-Wesley Professional, 2011, ISBN 0-321-57351-X.
* http://algs4.cs.princeton.edu
*
*
* algs4.jar is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* algs4.jar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with algs4.jar. If not, see http://www.gnu.org/licenses.
******************************************************************************/