/*
   * The subroutine of DFS. NOTE: the set is used to distinguish between 1st
   * and 2nd round of DFS. set == null: finished vertices are stored (1st
   * round). set != null: all vertices found will be saved in the set (2nd
   * round)
   */
  private void dfsVisit(
      DirectedGraph<V, E> visitedGraph, VertexData<V> vertexData, Set<V> vertices) {
    Deque<VertexData<V>> stack = new ArrayDeque<VertexData<V>>();
    stack.add(vertexData);

    while (!stack.isEmpty()) {
      VertexData<V> data = stack.removeLast();

      if (!data.isDiscovered()) {
        data.setDiscovered(true);

        if (vertices != null) {
          vertices.add(data.getVertex());
        }

        stack.add(new VertexData1<V>(data, true, true));

        // follow all edges
        for (E edge : visitedGraph.outgoingEdgesOf(data.getVertex())) {
          VertexData<V> targetData = vertexToVertexData.get(visitedGraph.getEdgeTarget(edge));

          if (!targetData.isDiscovered()) {
            // the "recursion"
            stack.add(targetData);
          }
        }
      } else if (data.isFinished()) {
        if (vertices == null) {
          orderedVertices.addFirst(data.getFinishedData());
        }
      }
    }
  }
  /**
   * Computes a {@link List} of {@link Set}s, where each set contains vertices which together form a
   * strongly connected component within the given graph.
   *
   * @return <code>List</code> of <code>Set</code> s containing the strongly connected components
   */
  public List<Set<V>> stronglyConnectedSets() {
    if (stronglyConnectedSets == null) {
      orderedVertices = new LinkedList<VertexData<V>>();
      stronglyConnectedSets = new Vector<Set<V>>();

      // create VertexData objects for all vertices, store them
      createVertexData();

      // perform the first round of DFS, result is an ordering
      // of the vertices by decreasing finishing time
      for (VertexData<V> data : vertexToVertexData.values()) {
        if (!data.isDiscovered()) {
          dfsVisit(graph, data, null);
        }
      }

      // 'create' inverse graph (i.e. every edge is reversed)
      DirectedGraph<V, E> inverseGraph = new EdgeReversedGraph<V, E>(graph);

      // get ready for next dfs round
      resetVertexData();

      // second dfs round: vertices are considered in decreasing
      // finishing time order; every tree found is a strongly
      // connected set
      for (VertexData<V> data : orderedVertices) {
        if (!data.isDiscovered()) {
          // new strongly connected set
          Set<V> set = new HashSet<V>();
          stronglyConnectedSets.add(set);
          dfsVisit(inverseGraph, data, set);
        }
      }

      // clean up for garbage collection
      orderedVertices = null;
      vertexToVertexData = null;
    }

    return stronglyConnectedSets;
  }