mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-09-20 07:43:04 +08:00
Add SCC.
This commit is contained in:
parent
0c2561197a
commit
20d642b402
@ -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
|
||||
|
16
src/algorithms/graph/strongly-connected-components/README.md
Normal file
16
src/algorithms/graph/strongly-connected-components/README.md
Normal 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)
|
@ -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());
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
@ -31,7 +31,7 @@ export default class Stack {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {LinkedListNode}
|
||||
* @return {*}
|
||||
*/
|
||||
pop() {
|
||||
const removedTail = this.linkedList.deleteTail();
|
||||
|
Loading…
Reference in New Issue
Block a user