This commit is contained in:
Oleksii Trekhleb 2018-05-14 07:13:07 +03:00
parent 0c2561197a
commit 20d642b402
5 changed files with 253 additions and 2 deletions

View File

@ -75,7 +75,7 @@
* [Articulation Points](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/articulation-points) - Tarjan's algorithm (DFS based)
* [Bridges](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bridges) - DFS based algorithm
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm
* Strongly Connected Component algorithm
* [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm
* Shortest Path Faster Algorithm (SPFA)
* **Uncategorized**
* Union-Find

View File

@ -0,0 +1,16 @@
# Strongly Connected Component
A directed graph is called **strongly connected** if there is a path
in each direction between each pair of vertices of the graph.
In a directed graph G that may not itself be strongly connected,
a pair of vertices `u` and `v` are said to be strongly connected
to each other if there is a path in each direction between them.
![Strongly Connected](https://upload.wikimedia.org/wikipedia/commons/5/5c/Scc.png)
Graph with strongly connected components marked
## References
- [Wikipedia](https://en.wikipedia.org/wiki/Strongly_connected_component)
- [YouTube](https://www.youtube.com/watch?v=RpgcYiky7uw)

View File

@ -0,0 +1,102 @@
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
import Graph from '../../../../data-structures/graph/Graph';
import stronglyConnectedComponents from '../stronglyConnectedComponents';
describe('stronglyConnectedComponents', () => {
it('should detect strongly connected components in simple graph', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const vertexD = new GraphVertex('D');
const edgeAB = new GraphEdge(vertexA, vertexB);
const edgeBC = new GraphEdge(vertexB, vertexC);
const edgeCA = new GraphEdge(vertexC, vertexA);
const edgeCD = new GraphEdge(vertexC, vertexD);
const graph = new Graph(true);
graph
.addEdge(edgeAB)
.addEdge(edgeBC)
.addEdge(edgeCA)
.addEdge(edgeCD);
const components = stronglyConnectedComponents(graph);
expect(components).toBeDefined();
expect(components.length).toBe(2);
expect(components[0][0].getKey()).toBe(vertexA.getKey());
expect(components[0][1].getKey()).toBe(vertexC.getKey());
expect(components[0][2].getKey()).toBe(vertexB.getKey());
expect(components[1][0].getKey()).toBe(vertexD.getKey());
});
it('should detect strongly connected components in graph', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const vertexD = new GraphVertex('D');
const vertexE = new GraphVertex('E');
const vertexF = new GraphVertex('F');
const vertexG = new GraphVertex('G');
const vertexH = new GraphVertex('H');
const vertexI = new GraphVertex('I');
const vertexJ = new GraphVertex('J');
const vertexK = new GraphVertex('K');
const edgeAB = new GraphEdge(vertexA, vertexB);
const edgeBC = new GraphEdge(vertexB, vertexC);
const edgeCA = new GraphEdge(vertexC, vertexA);
const edgeBD = new GraphEdge(vertexB, vertexD);
const edgeDE = new GraphEdge(vertexD, vertexE);
const edgeEF = new GraphEdge(vertexE, vertexF);
const edgeFD = new GraphEdge(vertexF, vertexD);
const edgeGF = new GraphEdge(vertexG, vertexF);
const edgeGH = new GraphEdge(vertexG, vertexH);
const edgeHI = new GraphEdge(vertexH, vertexI);
const edgeIJ = new GraphEdge(vertexI, vertexJ);
const edgeJG = new GraphEdge(vertexJ, vertexG);
const edgeJK = new GraphEdge(vertexJ, vertexK);
const graph = new Graph(true);
graph
.addEdge(edgeAB)
.addEdge(edgeBC)
.addEdge(edgeCA)
.addEdge(edgeBD)
.addEdge(edgeDE)
.addEdge(edgeEF)
.addEdge(edgeFD)
.addEdge(edgeGF)
.addEdge(edgeGH)
.addEdge(edgeHI)
.addEdge(edgeIJ)
.addEdge(edgeJG)
.addEdge(edgeJK);
const components = stronglyConnectedComponents(graph);
expect(components).toBeDefined();
expect(components.length).toBe(4);
expect(components[0][0].getKey()).toBe(vertexG.getKey());
expect(components[0][1].getKey()).toBe(vertexJ.getKey());
expect(components[0][2].getKey()).toBe(vertexI.getKey());
expect(components[0][3].getKey()).toBe(vertexH.getKey());
expect(components[1][0].getKey()).toBe(vertexK.getKey());
expect(components[2][0].getKey()).toBe(vertexA.getKey());
expect(components[2][1].getKey()).toBe(vertexC.getKey());
expect(components[2][2].getKey()).toBe(vertexB.getKey());
expect(components[3][0].getKey()).toBe(vertexD.getKey());
expect(components[3][1].getKey()).toBe(vertexF.getKey());
expect(components[3][2].getKey()).toBe(vertexE.getKey());
});
});

View File

@ -0,0 +1,133 @@
import Stack from '../../../data-structures/stack/Stack';
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
/**
* @param {Graph} graph
* @return {Stack}
*/
function getVerticesSortedByDfsFinishTime(graph) {
// Set of all visited vertices during DFS pass.
const visitedVerticesSet = {};
// Stack of vertices by finish time.
// All vertices in this stack are ordered by finished time in decreasing order.
// Vertex that has been finished first will be at the bottom of the stack and
// vertex that has been finished last will be at the top of the stack.
const verticesByDfsFinishTime = new Stack();
// Set of all vertices we're going to visit.
const notVisitedVerticesSet = {};
graph.getAllVertices().forEach((vertex) => {
notVisitedVerticesSet[vertex.getKey()] = vertex;
});
// Specify DFS traversal callbacks.
const dfsCallbacks = {
enterVertex: ({ currentVertex }) => {
// Add current vertex to visited set.
visitedVerticesSet[currentVertex.getKey()] = currentVertex;
// Delete current vertex from not visited set.
delete notVisitedVerticesSet[currentVertex.getKey()];
},
leaveVertex: ({ currentVertex }) => {
// Push vertex to the stack when leaving it.
// This will make stack to be ordered by finish time in decreasing order.
verticesByDfsFinishTime.push(currentVertex);
},
allowTraversal: ({ nextVertex }) => {
// Don't allow to traverse the nodes that have been already visited.
return !visitedVerticesSet[nextVertex.getKey()];
},
};
// Do FIRST DFS PASS traversal for all graph vertices to fill the verticesByFinishTime stack.
while (Object.values(notVisitedVerticesSet).length) {
// Peek any vertex to start DFS traversal from.
const startVertexKey = Object.keys(notVisitedVerticesSet)[0];
const startVertex = notVisitedVerticesSet[startVertexKey];
delete notVisitedVerticesSet[startVertexKey];
depthFirstSearch(graph, startVertex, dfsCallbacks);
}
return verticesByDfsFinishTime;
}
/**
* @param {Graph} graph
* @param {Stack} verticesByFinishTime
* @return {*[]}
*/
function getSCCSets(graph, verticesByFinishTime) {
// Array of arrays of strongly connected vertices.
const stronglyConnectedComponentsSets = [];
// Array that will hold all vertices that are being visited during one DFS run.
let stronglyConnectedComponentsSet = [];
// Visited vertices set.
const visitedVerticesSet = {};
// Callbacks for DFS traversal.
const dfsCallbacks = {
enterVertex: ({ currentVertex }) => {
// Add current vertex to SCC set of current DFS round.
stronglyConnectedComponentsSet.push(currentVertex);
// Add current vertex to visited set.
visitedVerticesSet[currentVertex.getKey()] = currentVertex;
},
leaveVertex: ({ previousVertex }) => {
// Once DFS traversal is finished push the set of found strongly connected
// components during current DFS round to overall strongly connected components set.
// The sign that traversal is about to be finished is that we came back to start vertex
// which doesn't have parent.
if (previousVertex === null) {
stronglyConnectedComponentsSets.push([...stronglyConnectedComponentsSet]);
}
},
allowTraversal: ({ nextVertex }) => {
// Don't allow traversal of already visited vertices.
return !visitedVerticesSet[nextVertex.getKey()];
},
};
while (!verticesByFinishTime.isEmpty()) {
/** @var {GraphVertex} startVertex */
const startVertex = verticesByFinishTime.pop();
// Reset the set of strongly connected vertices.
stronglyConnectedComponentsSet = [];
// Don't do DFS on already visited vertices.
if (!visitedVerticesSet[startVertex.getKey()]) {
// Do DFS traversal.
depthFirstSearch(graph, startVertex, dfsCallbacks);
}
}
return stronglyConnectedComponentsSets;
}
/**
* Kosaraju's algorithm.
*
* @param {Graph} graph
* @return {*[]}
*/
export default function stronglyConnectedComponents(graph) {
// In this algorithm we will need to do TWO DFS PASSES overt the graph.
// Get stack of vertices ordered by DFS finish time.
// All vertices in this stack are ordered by finished time in decreasing order:
// Vertex that has been finished first will be at the bottom of the stack and
// vertex that has been finished last will be at the top of the stack.
const verticesByFinishTime = getVerticesSortedByDfsFinishTime(graph);
// Reverse the graph.
graph.reverse();
// Do DFS once again on reversed graph.
return getSCCSets(graph, verticesByFinishTime);
}

View File

@ -31,7 +31,7 @@ export default class Stack {
}
/**
* @return {LinkedListNode}
* @return {*}
*/
pop() {
const removedTail = this.linkedList.deleteTail();