/** * Find the maximum weight matching of a path using dynamic programming. * * @param path a list of edges. The code assumes that the list of edges is a valid simple path, * and that is not a cycle. * @return a maximum weight matching of the path */ public Pair<Double, Set<E>> getMaximumWeightMatching(Graph<V, E> g, LinkedList<E> path) { int pathLength = path.size(); // special cases switch (pathLength) { case 0: // special case, empty path return Pair.of(Double.valueOf(0d), Collections.emptySet()); case 1: // special case, one edge E e = path.getFirst(); double eWeight = g.getEdgeWeight(e); if (comparator.compare(eWeight, 0d) > 0) { return Pair.of(eWeight, Collections.singleton(e)); } else { return Pair.of(Double.valueOf(0d), Collections.emptySet()); } } // make sure work array has enough space if (a.length < pathLength + 1) { a = new double[pathLength + 1]; } // first pass to find solution Iterator<E> it = path.iterator(); E e = it.next(); double eWeight = g.getEdgeWeight(e); a[0] = 0d; a[1] = (comparator.compare(eWeight, 0d) > 0) ? eWeight : 0d; for (int i = 2; i <= pathLength; i++) { e = it.next(); eWeight = g.getEdgeWeight(e); if (comparator.compare(a[i - 1], a[i - 2] + eWeight) > 0) { a[i] = a[i - 1]; } else { a[i] = a[i - 2] + eWeight; } } // reverse second pass to build solution Set<E> matching = new HashSet<>(); it = path.descendingIterator(); int i = pathLength; while (i >= 1) { e = it.next(); if (comparator.compare(a[i], a[i - 1]) > 0) { matching.add(e); // skip next edge if (i > 1) { e = it.next(); } i--; } i--; } // return solution return Pair.of(a[pathLength], matching); }
// the algorithm (improved with additional heuristics) private Pair<Double, Set<E>> runWithHeuristics(Graph<V, E> graph) { // lookup all relevant vertices Set<V> visibleVertex = initVisibleVertices(graph); // create solver for paths DynamicProgrammingPathSolver pathSolver = new DynamicProgrammingPathSolver(); Set<E> matching = new HashSet<>(); double matchingWeight = 0d; Set<V> matchedVertices = new HashSet<>(); // run algorithm while (!visibleVertex.isEmpty()) { // find vertex arbitrarily V x = visibleVertex.stream().findAny().get(); // grow path from x LinkedList<E> path = new LinkedList<>(); while (x != null) { // first heaviest edge incident to vertex x (among visible neighbors) double maxWeight = 0d; E maxWeightedEdge = null; V maxWeightedNeighbor = null; for (E e : graph.edgesOf(x)) { V other = Graphs.getOppositeVertex(graph, e, x); if (visibleVertex.contains(other) && !other.equals(x)) { double curWeight = graph.getEdgeWeight(e); if (comparator.compare(curWeight, 0d) > 0 && (maxWeightedEdge == null || comparator.compare(curWeight, maxWeight) > 0)) { maxWeight = curWeight; maxWeightedEdge = e; maxWeightedNeighbor = other; } } } // add edge to path and remove x if (maxWeightedEdge != null) { path.add(maxWeightedEdge); } visibleVertex.remove(x); // go to next vertex x = maxWeightedNeighbor; } // find maximum weight matching of path using dynamic programming Pair<Double, Set<E>> pathMatching = pathSolver.getMaximumWeightMatching(graph, path); // add it to result while keeping track of matched vertices matchingWeight += pathMatching.getFirst(); for (E e : pathMatching.getSecond()) { V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); if (!matchedVertices.add(s)) { throw new RuntimeException("Set is not a valid matching, please submit a bug report"); } if (!matchedVertices.add(t)) { throw new RuntimeException("Set is not a valid matching, please submit a bug report"); } matching.add(e); } } // extend matching to maximal cardinality (out of edges with positive weight) for (E e : graph.edgeSet()) { double edgeWeight = graph.getEdgeWeight(e); if (comparator.compare(edgeWeight, 0d) <= 0) { // ignore zero or negative weight continue; } V s = graph.getEdgeSource(e); if (matchedVertices.contains(s)) { // matched vertex, ignore continue; } V t = graph.getEdgeTarget(e); if (matchedVertices.contains(t)) { // matched vertex, ignore continue; } // add edge to matching matching.add(e); matchingWeight += edgeWeight; } // return extended matching return Pair.of(matchingWeight, matching); }
// the algorithm (no heuristics) private Pair<Double, Set<E>> run(Graph<V, E> graph) { // lookup all relevant vertices Set<V> visibleVertex = initVisibleVertices(graph); // run algorithm Set<E> m1 = new HashSet<>(); Set<E> m2 = new HashSet<>(); double m1Weight = 0d, m2Weight = 0d; int i = 1; while (!visibleVertex.isEmpty()) { // find vertex arbitrarily V x = visibleVertex.stream().findAny().get(); // grow path from x while (x != null) { // first heaviest edge incident to vertex x (among visible neighbors) double maxWeight = 0d; E maxWeightedEdge = null; V maxWeightedNeighbor = null; for (E e : graph.edgesOf(x)) { V other = Graphs.getOppositeVertex(graph, e, x); if (visibleVertex.contains(other) && !other.equals(x)) { double curWeight = graph.getEdgeWeight(e); if (comparator.compare(curWeight, 0d) > 0 && (maxWeightedEdge == null || comparator.compare(curWeight, maxWeight) > 0)) { maxWeight = curWeight; maxWeightedEdge = e; maxWeightedNeighbor = other; } } } // add it to either m1 or m2, alternating between them if (maxWeightedEdge != null) { switch (i) { case 1: m1.add(maxWeightedEdge); m1Weight += maxWeight; break; case 2: m2.add(maxWeightedEdge); m2Weight += maxWeight; break; default: throw new RuntimeException("Failed to figure out matching, seems to be a bug"); } i = 3 - i; } // remove x and incident edges visibleVertex.remove(x); // go to next vertex x = maxWeightedNeighbor; } } // return best matching if (comparator.compare(m1Weight, m2Weight) > 0) { return Pair.of(m1Weight, m1); } else { return Pair.of(m2Weight, m2); } }