All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.matching.HopcroftKarpMaximumCardinalityBipartiteMatching Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2017-2023, by Joris Kinable and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.alg.matching;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.alg.util.*;

import java.util.*;

/**
 * Implementation of the well-known Hopcroft Karp algorithm to compute a matching of maximum
 * cardinality in a bipartite graph. The algorithm runs in $O(|E| \cdot \sqrt{|V|})$ time. This
 * implementation accepts undirected graphs which may contain self-loops and multiple edges. To
 * compute a maximum cardinality matching in general (non-bipartite) graphs, use
 * {@link SparseEdmondsMaximumCardinalityMatching} instead.
 *
 * 

* The Hopcroft Karp matching algorithm computes augmenting paths of increasing length, until no * augmenting path exists in the graph. At each iteration, the algorithm runs a Breadth First Search * from the exposed (free) vertices, until an augmenting path is found. Next, a Depth First Search * is performed to find all (vertex disjoint) augmenting paths of the same length. The matching is * augmented along all discovered augmenting paths simultaneously. * *

* The original algorithm is described in: Hopcroft, John E.; Karp, Richard M. (1973), "An n5/2 * algorithm for maximum matchings in bipartite graphs", SIAM Journal on Computing 2 (4): 225–231, * doi:10.1137/0202019 A coarse overview of the algorithm is given in: http://en.wikipedia.org/wiki/Hopcroft-Karp_algorithm * * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class HopcroftKarpMaximumCardinalityBipartiteMatching implements MatchingAlgorithm { private final Graph graph; private final Set partition1; private final Set partition2; /* Ordered list of vertices */ private List vertices; /* Mapping of a vertex to their unique position in the ordered list of vertices */ private Map vertexIndexMap; /* Number of matched vertices i partition 1. */ private int matchedVertices; /* Dummy vertex. All vertices are initially matched against this dummy vertex */ private static final int DUMMY = 0; /* Infinity */ private static final int INF = Integer.MAX_VALUE; /* Array keeping track of the matching. */ private int[] matching; /* Distance array. Used to compute shoretest augmenting paths */ private int[] dist; /* queue used for breadth first search */ private FixedSizeIntegerQueue queue; /** * Constructs a new instance of the Hopcroft Karp bipartite matching algorithm. The input graph * must be bipartite. For efficiency reasons, this class does not check whether the input graph * is bipartite. Invoking this class on a non-bipartite graph results in undefined behavior. To * test whether a graph is bipartite, use {@link GraphTests#isBipartite(Graph)}. * * @param graph bipartite graph * @param partition1 the first partition of vertices in the bipartite graph * @param partition2 the second partition of vertices in the bipartite graph */ public HopcroftKarpMaximumCardinalityBipartiteMatching( Graph graph, Set partition1, Set partition2) { this.graph = GraphTests.requireUndirected(graph); // Ensure that partition1 is smaller or equal in size compared to partition 2 if (partition1.size() <= partition2.size()) { this.partition1 = partition1; this.partition2 = partition2; } else { // else, swap this.partition1 = partition2; this.partition2 = partition1; } } /** * Initialize data structures */ private void init() { vertices = new ArrayList<>(); vertices.add(null); vertices.addAll(partition1); vertices.addAll(partition2); vertexIndexMap = new HashMap<>(); for (int i = 0; i < vertices.size(); i++) vertexIndexMap.put(vertices.get(i), i); matching = new int[vertices.size() + 1]; dist = new int[partition1.size() + 1]; queue = new FixedSizeIntegerQueue(vertices.size()); } /** * Greedily compute an initial feasible matching */ private void warmStart() { for (V uOrig : partition1) { int u = vertexIndexMap.get(uOrig); for (V vOrig : Graphs.neighborListOf(graph, uOrig)) { int v = vertexIndexMap.get(vOrig); if (matching[v] == DUMMY) { matching[v] = u; matching[u] = v; matchedVertices++; break; } } } } /** * BFS function which finds the shortest augmenting path. The length of the shortest augmenting * path is stored in dist[DUMMY]. * * @return true if an augmenting path was found, false otherwise */ private boolean bfs() { queue.clear(); for (int u = 1; u <= partition1.size(); u++) if (matching[u] == DUMMY) { // Add all unmatched vertices to the queue and set their // distance to 0 dist[u] = 0; queue.enqueue(u); } else // Set distance of all matched vertices to INF dist[u] = INF; dist[DUMMY] = INF; while (!queue.isEmpty()) { int u = queue.poll(); if (dist[u] < dist[DUMMY]) for (V vOrig : Graphs.neighborListOf(graph, vertices.get(u))) { int v = vertexIndexMap.get(vOrig); if (dist[matching[v]] == INF) { dist[matching[v]] = dist[u] + 1; queue.enqueue(matching[v]); } } } return dist[DUMMY] != INF; // Return true if an augmenting path is found } /** * Find all vertex disjoint augmenting paths of length dist[DUMMY]. To find paths of dist[DUMMY] * length, we simply follow nodes that are 1 distance increments away from each other. * * @param u vertex from which the DFS is started * @return true if an augmenting path from vertex u was found, false otherwise */ private boolean dfs(int u) { if (u != DUMMY) { for (V vOrig : Graphs.neighborListOf(graph, vertices.get(u))) { int v = vertexIndexMap.get(vOrig); if (dist[matching[v]] == dist[u] + 1) if (dfs(matching[v])) { matching[v] = u; matching[u] = v; return true; } } // No augmenting path has been found. Set distance of u to INF to ensure that u isn't // visited again. dist[u] = INF; return false; } return true; } @Override public Matching getMatching() { this.init(); this.warmStart(); while (matchedVertices < partition1.size() && bfs()) { // Greedily search for vertex disjoint augmenting paths for (int v = 1; v <= partition1.size() && matchedVertices < partition1.size(); v++) if (matching[v] == DUMMY) // v is unmatched if (dfs(v)) matchedVertices++; } assert matchedVertices <= partition1.size(); Set edges = new HashSet<>(); for (int i = 0; i < vertices.size(); i++) { if (matching[i] != DUMMY) { edges.add(graph.getEdge(vertices.get(i), vertices.get(matching[i]))); } } return new MatchingImpl<>(graph, edges, edges.size()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy