/**
     * 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);
    }
  }