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

org.psjava.algo.graph.matching.HopcroftKarpAlgorithm Maven / Gradle / Ivy

The newest version!
package org.psjava.algo.graph.matching;

import org.psjava.algo.graph.bfs.BFS;
import org.psjava.algo.graph.bfs.BFSVisitor;
import org.psjava.algo.graph.dfs.DFSVisitorBase;
import org.psjava.algo.graph.dfs.MultiSourceDFS;
import org.psjava.ds.Collection;
import org.psjava.ds.array.DynamicArray;
import org.psjava.ds.array.FirstInArray;
import org.psjava.ds.array.LastInArray;
import org.psjava.ds.graph.EdgeFilteredSubNewGraph;
import org.psjava.ds.graph.Graph;
import org.psjava.ds.graph.MutableDirectedGraph;
import org.psjava.ds.graph.BipartiteGraph;
import org.psjava.ds.graph.BipartiteGraphEdge;
import org.psjava.ds.graph.DirectedEdge;
import org.psjava.ds.map.MutableMap;
import org.psjava.ds.map.ValuesInMap;
import org.psjava.goods.GoodMutableMapFactory;
import org.psjava.util.DataFilter;
import org.psjava.util.FilteredIterable;
import org.psjava.util.VisitorStopper;
import org.psjava.util.ZeroTo;

/**
 * O(V*root(E))
 */

public class HopcroftKarpAlgorithm {

	public static MaximumBipartiteMatchingAlgorithm getInstance() {
		return new MaximumBipartiteMatchingAlgorithm() {
			@Override
			public  MaximumBipartiteMatchingResult calc(BipartiteGraph bg) {
				Graph, Edge> adj = wrapAsGraph(bg);
				while (true) {
					Object bfsMark = new Object();
					Collection> bfsFinishes = bfs(adj, bfsMark);
					if (bfsFinishes.isEmpty())
						break;
					dfs(adj, bfsFinishes, bfsMark);
				}
				return createResult(adj);
			}

		};
	}

	private static enum Side {
		LEFT, RIGHT
	}

	private static class Vertex {
		V original;
		Side side;
		boolean free = true;

		Vertex(V original, Side side) {
			this.original = original;
			this.side = side;
		}
	}

	private static class EdgeStatus {
		boolean inMatch = false;
		Object bfsMark = null;
	}

	private static class Edge implements DirectedEdge> {
		final Vertex from, to;
		final EdgeStatus status;

		Edge(Vertex from, Vertex to, EdgeStatus status) {
			this.from = from;
			this.to = to;
			this.status = status;
		}

		@Override
		public Vertex from() {
			return from;
		}

		@Override
		public Vertex to() {
			return to;
		}

	}

	private static  Graph, Edge> wrapAsGraph(BipartiteGraph bg) {
		MutableMap> vertex = GoodMutableMapFactory.getInstance().create();
		for (V v : bg.getLeftVertices())
			vertex.add(v, new Vertex(v, Side.LEFT));
		for (V v : bg.getRightVertices())
			vertex.add(v, new Vertex(v, Side.RIGHT));
		MutableDirectedGraph, Edge> graph = MutableDirectedGraph.create();
		for (Vertex v : ValuesInMap.get(vertex))
			graph.insertVertex(v);
		for (BipartiteGraphEdge e : bg.getEdges()) {
			EdgeStatus status = new EdgeStatus();
			graph.addEdge(new Edge(vertex.get(e.left()), vertex.get(e.right()), status));
			graph.addEdge(new Edge(vertex.get(e.right()), vertex.get(e.left()), status));
		}
		return graph;
	}

	private static  Collection> bfs(final Graph, Edge> adj, final Object mark) {
		final DynamicArray> finishes = DynamicArray.create();

		BFS.traverse(EdgeFilteredSubNewGraph.wrap(adj, new DataFilter>() {
			@Override
			public boolean isAccepted(Edge edge) {
				// to alternate matched and non-matched edges.
				if (edge.from.side == Side.LEFT)
					return !edge.status.inMatch;
				else
					return edge.status.inMatch;
			}
		}), FilteredIterable.create(adj.getVertices(), new DataFilter>() {
			@Override
			public boolean isAccepted(Vertex v) {
				return (v.side == Side.LEFT) && v.free;
			}
		}), new BFSVisitor, Edge>() {
			int finishDepth = -1;

			@Override
			public void onDiscover(Vertex vertex, int depth, VisitorStopper stopper) {
				if (finishDepth == -1 || depth <= finishDepth) {
					if (vertex.side == Side.RIGHT && vertex.free) {
						finishDepth = depth;
						finishes.addToLast(vertex);
					}
				} else {
					stopper.stop();
				}
			}

			@Override
			public void onWalk(Edge e) {
				e.status.bfsMark = mark;
			}

		});
		return finishes;
	}

	private static  void dfs(final Graph, Edge> adj, final Collection> bfsFinishes, final Object bfsMark) {
		MultiSourceDFS.traverse(EdgeFilteredSubNewGraph.wrap(adj, new DataFilter>() {
			@Override
			public boolean isAccepted(Edge edge) {
				return edge.status.bfsMark == bfsMark; // uses only edges discovered in bfs step.
			}
		}), bfsFinishes, new DFSVisitorBase, Edge>() {
			DynamicArray> path = DynamicArray.create();

			@Override
			public void onWalkDown(Edge edge) {
				path.addToLast(edge);
			}

			@Override
			public void onWalkUp(Edge downedEdge) {
				path.removeLast();
			}

			@Override
			public void onDiscovered(Vertex v, int depth, VisitorStopper stopper) {
				if (wasBfsStart(v)) {
					for (int index : ZeroTo.get(path.size()))
						path.get(index).status.inMatch = (index % 2 == 0);
					FirstInArray.getFirst(path).from.free = false;
					LastInArray.getLast(path).to.free = false;
				}
			}

			private boolean wasBfsStart(Vertex v) {
				return v.side == Side.LEFT && v.free;
			}

		});
	}

	private static  MaximumBipartiteMatchingResult createResult(Graph, Edge> adj) {
		final MutableMap match = GoodMutableMapFactory.getInstance().create();
		for (Vertex v : adj.getVertices())
			for (Edge e : adj.getEdges(v))
				if (e.status.inMatch)
					match.add(e.from().original, e.to().original);

		return new MaximumBipartiteMatchingResult() {
			@Override
			public V getMatchedVertex(V v) {
				return match.get(v);
			}

			@Override
			public int getMaxMatchCount() {
				return match.size() / 2;
			}

			@Override
			public boolean hasMatch(V v) {
				return match.containsKey(v);
			}
		};
	}

	private HopcroftKarpAlgorithm() {
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy