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