package com.vitco.low.triangulate.util; import gnu.trove.iterator.TIntIntIterator; import gnu.trove.iterator.TIntIterator; import gnu.trove.map.hash.TIntIntHashMap; import java.util.ArrayList; import java.util.HashMap; /** * Implementation of Hopcroft-Karp algorithm * * Reference: http://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm * * Adapted from * https://github.com/pierre-dejoue/kart-match/blob/master/src/fr/neuf/perso/pdejoue/kart_match/HopcroftKarp.java */ // todo: make faster! public class HopcroftKarp { // The Hopcroft-Karp algorithm public static HashMap<Integer, Integer> findMaximumMatching(HashMap<Integer, ArrayList<Integer>> graph) { // Local variables: // The first step of the Hopcroft-Karp algorithm consists in building a list alternating // U-layers and V-layers. The current U/V-layer being processed by the algorithm is stored in // hash maps current_layer_u and current_layer_v. All U-layers (respectively V-layers) shall // be disjoint from each other. Yet there is no need to store all the layers as they are built, // so the algorithm only keeps track of the union of the previous U-layers and V-layers in hash // maps all_layers_u and all_layers_v. // Finally, hash map matched_v contains the temporary matching built by the algorithm. Upon // completion of the algorithm, it is a maximum matching. TIntIntHashMap current_layer_u = new TIntIntHashMap(); // u --> v HashMap<Integer, ArrayList<Integer>> current_layer_v = new HashMap<Integer, ArrayList<Integer>>(); // v --> list of u TIntIntHashMap all_layers_u = new TIntIntHashMap(); // u --> v HashMap<Integer, ArrayList<Integer>> all_layers_v = new HashMap<Integer, ArrayList<Integer>>(); // v --> list of u TIntIntHashMap matched_v = new TIntIntHashMap(); // v --> u ArrayList<Integer> unmatched_v = new ArrayList<Integer>(); // list of v // Loop as long as we can find at least one minimal augmenting path while (true) { int k = 0; // U-layers have indexes n = 2*k ; V-layers have indexes n = 2*k+1. // The initial layer of vertices of U is equal to the set of u not in the current matching all_layers_u.clear(); current_layer_u.clear(); for(Integer u : graph.keySet()) { if(!matched_v.containsValue(u)) { current_layer_u.put(u, 0); all_layers_u.put(u, 0); } } all_layers_v.clear(); unmatched_v.clear(); // Use BFS to build alternating U and V layers, in which: // - The edges between U-layer 2*k and V-layer 2*k+1 are unmatched ones. // - The edges between V-layer 2*k+1 and U-layer 2*k+2 are matched ones. // While the current layer U is not empty and no unmatched V is encountered while(!current_layer_u.isEmpty() && unmatched_v.isEmpty()) { //Log.d("HopcroftKarp.Algo", "current_layer_u: " + current_layer_u.toString()); // Build the layer of vertices of V with index n = 2*k+1 current_layer_v.clear(); for (TIntIterator it = current_layer_u.keySet().iterator(); it.hasNext();) { Integer u = it.next(); for(Integer v : graph.get(u)) { if(!all_layers_v.containsKey(v)) { // If not already in the previous partitions for V ArrayList<Integer> entry = current_layer_v.get(v); if (entry == null) { entry = new ArrayList<Integer>(); current_layer_v.put(v, entry); } entry.add(u); // Expand of all_layers_v is done in the next step, building the U-layer } } } k++; // Build the layer of vertices of U with index n = 2*k current_layer_u.clear(); for(Integer v : current_layer_v.keySet()) { all_layers_v.put(v, current_layer_v.get(v)); // Expand the union of all V-layers to include current_v_layer // Is it a matched vertex in V? if(matched_v.containsKey(v)) { Integer u = matched_v.get(v); current_layer_u.put(u, v); all_layers_u.put(u, v); // Expand the union of all U-layers to include current_u_layer } else { // Found one unmatched vertex v. The algorithm will finish the current layer, // then exit the while loop since it has found at least one augmenting path. unmatched_v.add(v); } } } // After the inner while loop has completed, either we found at least one augmenting path... if(!unmatched_v.isEmpty()) { for(Integer v : unmatched_v) { // Use DFS to find one augmenting path ending with vertex V. The vertices from that path, if it // exists, are removed from the all_layers_u and all_layers_v maps. if(k >= 1) { recFindAugmentingPath(v, all_layers_u, all_layers_v, matched_v, (k-1)); // Ignore return status } else { throw new ArithmeticException("k should not be equal to zero here."); } } } else { // ... or we didn't, in which case we already got a maximum matching for that graph break; } } // end while(true) // compute the result (reversed) HashMap<Integer, Integer> result = new HashMap<Integer, Integer>(); for (TIntIntIterator it = matched_v.iterator(); it.hasNext();) { it.advance(); result.put(it.value(), it.key()); } return result; } // Recursive function used to build an augmenting path starting from the end node v. // It relies on a DFS on the U and V layers built during the first phase of the algorithm. // This is by the way this function which is responsible for most of the randomization // of the output. // Returns true if an augmenting path is found. private static boolean recFindAugmentingPath(Integer v, TIntIntHashMap all_layers_u, HashMap<Integer, ArrayList<Integer>> all_layers_v, TIntIntHashMap matched_v, int k) { if (all_layers_v.containsKey(v)) { ArrayList<Integer> list_u = all_layers_v.get(v); for(Integer u: list_u) { if(all_layers_u.containsKey(u)) { Integer prev_v = all_layers_u.get(u); // If the path ending with "prev_v -> u -> v" is an augmenting path if(k == 0 || recFindAugmentingPath(prev_v, all_layers_u, all_layers_v, matched_v, (k-1))) { matched_v.put(v, u); // Edge u -> v replaces the previous matched edge connected to v. all_layers_v.remove(v); // Remove vertex v from all_layers_v all_layers_u.remove(u); // Remove vertex u from all_layers_u return true; } } } } return false; // No augmenting path found } }