package com.interview.graph;
import java.util.*;
/**
* Date 11/16/2015
* @author Tushar Roy
*
* Find all cycles in directed graph using Johnson's algorithm
*
* Time complexity - O(E + V).(c+1) where c is number of cycles found
* Space complexity - O(E + V + s) where s is sum of length of all cycles.
*
* Link to youtube video - https://youtu.be/johyrWospv0
*
* References
* https://github.com/jgrapht/jgrapht/blob/master/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/JohnsonSimpleCycles.java
*/
public class AllCyclesInDirectedGraphJohnson {
Set<Vertex<Integer>> blockedSet;
Map<Vertex<Integer>, Set<Vertex<Integer>>> blockedMap;
Deque<Vertex<Integer>> stack;
List<List<Vertex<Integer>>> allCycles;
/**
* Main function to find all cycles
*/
public List<List<Vertex<Integer>>> simpleCyles(Graph<Integer> graph) {
blockedSet = new HashSet<>();
blockedMap = new HashMap<>();
stack = new LinkedList<>();
allCycles = new ArrayList<>();
long startIndex = 1;
TarjanStronglyConnectedComponent tarjan = new TarjanStronglyConnectedComponent();
while(startIndex <= graph.getAllVertex().size()) {
Graph<Integer> subGraph = createSubGraph(startIndex, graph);
List<Set<Vertex<Integer>>> sccs = tarjan.scc(subGraph);
//this creates graph consisting of strongly connected components only and then returns the
//least indexed vertex among all the strongly connected component graph.
//it also ignore one vertex graph since it wont have any cycle.
Optional<Vertex<Integer>> maybeLeastVertex = leastIndexSCC(sccs, subGraph);
if(maybeLeastVertex.isPresent()) {
Vertex<Integer> leastVertex = maybeLeastVertex.get();
blockedSet.clear();
blockedMap.clear();
findCyclesInSCG(leastVertex, leastVertex);
startIndex = leastVertex.getId() + 1;
} else {
break;
}
}
return allCycles;
}
private Optional<Vertex<Integer>> leastIndexSCC(List<Set<Vertex<Integer>>> sccs, Graph<Integer> subGraph) {
long min = Integer.MAX_VALUE;
Vertex<Integer> minVertex = null;
Set<Vertex<Integer>> minScc = null;
for(Set<Vertex<Integer>> scc : sccs) {
if(scc.size() == 1) {
continue;
}
for(Vertex<Integer> vertex : scc) {
if(vertex.getId() < min) {
min = vertex.getId();
minVertex = vertex;
minScc = scc;
}
}
}
if(minVertex == null) {
return Optional.empty();
}
Graph<Integer> graphScc = new Graph<>(true);
for(Edge<Integer> edge : subGraph.getAllEdges()) {
if(minScc.contains(edge.getVertex1()) && minScc.contains(edge.getVertex2())) {
graphScc.addEdge(edge.getVertex1().getId(), edge.getVertex2().getId());
}
}
return Optional.of(graphScc.getVertex(minVertex.getId()));
}
private void unblock(Vertex<Integer> u) {
blockedSet.remove(u);
if(blockedMap.get(u) != null) {
blockedMap.get(u).forEach( v -> {
if(blockedSet.contains(v)) {
unblock(v);
}
});
blockedMap.remove(u);
}
}
private boolean findCyclesInSCG(
Vertex<Integer> startVertex,
Vertex<Integer> currentVertex) {
boolean foundCycle = false;
stack.push(currentVertex);
blockedSet.add(currentVertex);
for (Edge<Integer> e : currentVertex.getEdges()) {
Vertex<Integer> neighbor = e.getVertex2();
//if neighbor is same as start vertex means cycle is found.
//Store contents of stack in final result.
if (neighbor == startVertex) {
List<Vertex<Integer>> cycle = new ArrayList<>();
stack.push(startVertex);
cycle.addAll(stack);
Collections.reverse(cycle);
stack.pop();
allCycles.add(cycle);
foundCycle = true;
} //explore this neighbor only if it is not in blockedSet.
else if (!blockedSet.contains(neighbor)) {
boolean gotCycle =
findCyclesInSCG(startVertex, neighbor);
foundCycle = foundCycle || gotCycle;
}
}
//if cycle is found with current vertex then recursively unblock vertex and all vertices which are dependent on this vertex.
if (foundCycle) {
//remove from blockedSet and then remove all the other vertices dependent on this vertex from blockedSet
unblock(currentVertex);
} else {
//if no cycle is found with current vertex then don't unblock it. But find all its neighbors and add this
//vertex to their blockedMap. If any of those neighbors ever get unblocked then unblock current vertex as well.
for (Edge<Integer> e : currentVertex.getEdges()) {
Vertex<Integer> w = e.getVertex2();
Set<Vertex<Integer>> bSet = getBSet(w);
bSet.add(currentVertex);
}
}
//remove vertex from the stack.
stack.pop();
return foundCycle;
}
private Set<Vertex<Integer>> getBSet(Vertex<Integer> v) {
return blockedMap.computeIfAbsent(v, (key) ->
new HashSet<>() );
}
private Graph createSubGraph(long startVertex, Graph<Integer> graph) {
Graph<Integer> subGraph = new Graph<>(true);
for(Edge<Integer> edge : graph.getAllEdges()) {
if(edge.getVertex1().getId() >= startVertex && edge.getVertex2().getId() >= startVertex) {
subGraph.addEdge(edge.getVertex1().getId(), edge.getVertex2().getId());
}
}
return subGraph;
}
public static void main(String args[]) {
AllCyclesInDirectedGraphJohnson johnson = new AllCyclesInDirectedGraphJohnson();
Graph<Integer> graph = new Graph<>(true);
graph.addEdge(1, 2);
graph.addEdge(1, 8);
graph.addEdge(1, 5);
graph.addEdge(2, 9);
graph.addEdge(2, 7);
graph.addEdge(2, 3);
graph.addEdge(3, 1);
graph.addEdge(3, 2);
graph.addEdge(3, 6);
graph.addEdge(3, 4);
graph.addEdge(6, 4);
graph.addEdge(4, 5);
graph.addEdge(5, 2);
graph.addEdge(8, 9);
graph.addEdge(9, 8);
List<List<Vertex<Integer>>> allCycles = johnson.simpleCyles(graph);
allCycles.forEach(cycle -> {
StringJoiner joiner = new StringJoiner("->");
cycle.forEach(vertex -> joiner.add(String.valueOf(vertex.getId())));
System.out.println(joiner);
});
}
}