forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
253 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
src/algorithms/graph/strongly-connected-components/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
 | ||
|
||
Graph with strongly connected components marked | ||
|
||
## References | ||
|
||
- [Wikipedia](https://en.wikipedia.org/wiki/Strongly_connected_component) | ||
- [YouTube](https://www.youtube.com/watch?v=RpgcYiky7uw) |
102 changes: 102 additions & 0 deletions
102
...gorithms/graph/strongly-connected-components/__test__/stronglyConnectedComponents.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); | ||
}); |
133 changes: 133 additions & 0 deletions
133
src/algorithms/graph/strongly-connected-components/stronglyConnectedComponents.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters