mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-26 23:21:18 +08:00
Add Tarjan's algorithm.
This commit is contained in:
parent
ff9877cf6b
commit
21d4144e5a
@ -1,7 +1,7 @@
|
|||||||
# Articulation Points (or Cut Vertices)
|
# Articulation Points (or Cut Vertices)
|
||||||
|
|
||||||
A vertex in an undirected connected graph is an articulation point
|
A vertex in an undirected connected graph is an articulation point
|
||||||
(or cut vertex) iff removing it (and edges through it) disconnects
|
(or cut vertex) if removing it (and edges through it) disconnects
|
||||||
the graph. Articulation points represent vulnerabilities in a
|
the graph. Articulation points represent vulnerabilities in a
|
||||||
connected network – single points whose failure would split the
|
connected network – single points whose failure would split the
|
||||||
network into 2 or more disconnected components. They are useful for
|
network into 2 or more disconnected components. They are useful for
|
||||||
|
@ -23,10 +23,9 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(2);
|
||||||
vertexC,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
|
||||||
vertexB,
|
expect(articulationPointsSet[1].getKey()).toBe(vertexB.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find articulation points in simple graph with back edge', () => {
|
it('should find articulation points in simple graph with back edge', () => {
|
||||||
@ -50,9 +49,8 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(1);
|
||||||
vertexC,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find articulation points in simple graph with back edge #2', () => {
|
it('should find articulation points in simple graph with back edge #2', () => {
|
||||||
@ -79,9 +77,8 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(1);
|
||||||
vertexC,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find articulation points in graph', () => {
|
it('should find articulation points in graph', () => {
|
||||||
@ -119,12 +116,11 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(4);
|
||||||
vertexF,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
|
||||||
vertexE,
|
expect(articulationPointsSet[1].getKey()).toBe(vertexE.getKey());
|
||||||
vertexD,
|
expect(articulationPointsSet[2].getKey()).toBe(vertexD.getKey());
|
||||||
vertexC,
|
expect(articulationPointsSet[3].getKey()).toBe(vertexC.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find articulation points in graph starting with articulation root vertex', () => {
|
it('should find articulation points in graph starting with articulation root vertex', () => {
|
||||||
@ -162,12 +158,11 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(4);
|
||||||
vertexF,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
|
||||||
vertexE,
|
expect(articulationPointsSet[1].getKey()).toBe(vertexE.getKey());
|
||||||
vertexC,
|
expect(articulationPointsSet[2].getKey()).toBe(vertexC.getKey());
|
||||||
vertexD,
|
expect(articulationPointsSet[3].getKey()).toBe(vertexD.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find articulation points in yet another graph #1', () => {
|
it('should find articulation points in yet another graph #1', () => {
|
||||||
@ -194,10 +189,9 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(2);
|
||||||
vertexD,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexD.getKey());
|
||||||
vertexC,
|
expect(articulationPointsSet[1].getKey()).toBe(vertexC.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find articulation points in yet another graph #2', () => {
|
it('should find articulation points in yet another graph #2', () => {
|
||||||
@ -232,8 +226,7 @@ describe('articulationPoints', () => {
|
|||||||
|
|
||||||
const articulationPointsSet = articulationPoints(graph);
|
const articulationPointsSet = articulationPoints(graph);
|
||||||
|
|
||||||
expect(articulationPointsSet).toEqual([
|
expect(articulationPointsSet.length).toBe(1);
|
||||||
vertexC,
|
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,11 +7,6 @@ class VisitMetadata {
|
|||||||
constructor({ discoveryTime, lowDiscoveryTime }) {
|
constructor({ discoveryTime, lowDiscoveryTime }) {
|
||||||
this.discoveryTime = discoveryTime;
|
this.discoveryTime = discoveryTime;
|
||||||
this.lowDiscoveryTime = lowDiscoveryTime;
|
this.lowDiscoveryTime = lowDiscoveryTime;
|
||||||
|
|
||||||
// We need this to know to which vertex we need to compare discovery time
|
|
||||||
// when leaving the vertex.
|
|
||||||
this.childVertex = null;
|
|
||||||
|
|
||||||
// We need this in order to check graph root node, whether it has two
|
// We need this in order to check graph root node, whether it has two
|
||||||
// disconnected children or not.
|
// disconnected children or not.
|
||||||
this.independantChildrenCount = 0;
|
this.independantChildrenCount = 0;
|
||||||
@ -28,10 +23,10 @@ export default function articulationPoints(graph) {
|
|||||||
// Set of vertices we've already visited during DFS.
|
// Set of vertices we've already visited during DFS.
|
||||||
const visitedSet = {};
|
const visitedSet = {};
|
||||||
|
|
||||||
// Set of articulation points found so far.
|
// Set of articulation points.
|
||||||
const articulationPointsSet = [];
|
const articulationPointsSet = {};
|
||||||
|
|
||||||
// Time needed to get to the current vertex.
|
// Time needed to discover to the current vertex.
|
||||||
let discoveryTime = 0;
|
let discoveryTime = 0;
|
||||||
|
|
||||||
// Peek the start vertex for DFS traversal.
|
// Peek the start vertex for DFS traversal.
|
||||||
@ -53,9 +48,6 @@ export default function articulationPoints(graph) {
|
|||||||
discoveryTime += 1;
|
discoveryTime += 1;
|
||||||
|
|
||||||
if (previousVertex) {
|
if (previousVertex) {
|
||||||
// Update child vertex information for previous vertex.
|
|
||||||
visitedSet[previousVertex.getKey()].childVertex = currentVertex;
|
|
||||||
|
|
||||||
// Update children counter for previous vertex.
|
// Update children counter for previous vertex.
|
||||||
visitedSet[previousVertex.getKey()].independantChildrenCount += 1;
|
visitedSet[previousVertex.getKey()].independantChildrenCount += 1;
|
||||||
}
|
}
|
||||||
@ -64,35 +56,13 @@ export default function articulationPoints(graph) {
|
|||||||
* @param {GraphVertex} currentVertex
|
* @param {GraphVertex} currentVertex
|
||||||
* @param {GraphVertex} previousVertex
|
* @param {GraphVertex} previousVertex
|
||||||
*/
|
*/
|
||||||
leaveVertex: ({ currentVertex }) => {
|
leaveVertex: ({ currentVertex, previousVertex }) => {
|
||||||
// Detect whether current vertex is articulation point or not.
|
if (previousVertex === null) {
|
||||||
// To do so we need to check two (OR) conditions:
|
// Don't do anything for the root vertex if it is already current (not previous one)
|
||||||
// 1. Is it a root vertex with at least two independent children.
|
return;
|
||||||
// 2. If its visited time is <= low time of adjacent vertex.
|
|
||||||
if (currentVertex === startVertex) {
|
|
||||||
// Check that it has at least two independent children.
|
|
||||||
if (visitedSet[currentVertex.getKey()].independantChildrenCount >= 2) {
|
|
||||||
articulationPointsSet.push(currentVertex);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Get child vertex low discovery time.
|
|
||||||
let childVertexLowDiscoveryTime = null;
|
|
||||||
if (visitedSet[currentVertex.getKey()].childVertex) {
|
|
||||||
const childVertexKey = visitedSet[currentVertex.getKey()].childVertex.getKey();
|
|
||||||
childVertexLowDiscoveryTime = visitedSet[childVertexKey].lowDiscoveryTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare child vertex low discovery time with current discovery time to if there
|
|
||||||
// are any short path (back edge) exists. If we can get to child vertex faster then
|
|
||||||
// to current one it means that there is a back edge exists (short path) and current
|
|
||||||
// vertex isn't articulation point.
|
|
||||||
const currentDiscoveryTime = visitedSet[currentVertex.getKey()].discoveryTime;
|
|
||||||
if (currentDiscoveryTime <= childVertexLowDiscoveryTime) {
|
|
||||||
articulationPointsSet.push(currentVertex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the low time with the smallest time of adjacent vertices.
|
// Update the low time with the smallest time of adjacent vertices.
|
||||||
|
|
||||||
// Get minimum low discovery time from all neighbors.
|
// Get minimum low discovery time from all neighbors.
|
||||||
/** @param {GraphVertex} neighbor */
|
/** @param {GraphVertex} neighbor */
|
||||||
visitedSet[currentVertex.getKey()].lowDiscoveryTime = currentVertex.getNeighbors().reduce(
|
visitedSet[currentVertex.getKey()].lowDiscoveryTime = currentVertex.getNeighbors().reduce(
|
||||||
@ -102,6 +72,27 @@ export default function articulationPoints(graph) {
|
|||||||
},
|
},
|
||||||
visitedSet[currentVertex.getKey()].lowDiscoveryTime,
|
visitedSet[currentVertex.getKey()].lowDiscoveryTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Detect whether previous vertex is articulation point or not.
|
||||||
|
// To do so we need to check two [OR] conditions:
|
||||||
|
// 1. Is it a root vertex with at least two independent children.
|
||||||
|
// 2. If its visited time is <= low time of adjacent vertex.
|
||||||
|
if (previousVertex === startVertex) {
|
||||||
|
// Check that root vertex has at least two independent children.
|
||||||
|
if (visitedSet[previousVertex.getKey()].independantChildrenCount >= 2) {
|
||||||
|
articulationPointsSet[previousVertex.getKey()] = previousVertex;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get current vertex low discovery time.
|
||||||
|
const currentLowDiscoveryTime = visitedSet[currentVertex.getKey()].lowDiscoveryTime;
|
||||||
|
|
||||||
|
// Compare current vertex low discovery time with parent discovery time. Check if there
|
||||||
|
// are any short path (back edge) exists. If we can't get to current vertex other then
|
||||||
|
// via parent then the parent vertex is articulation point for current one.
|
||||||
|
const parentDiscoveryTime = visitedSet[previousVertex.getKey()].discoveryTime;
|
||||||
|
if (parentDiscoveryTime <= currentLowDiscoveryTime) {
|
||||||
|
articulationPointsSet[previousVertex.getKey()] = previousVertex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allowTraversal: ({ nextVertex }) => {
|
allowTraversal: ({ nextVertex }) => {
|
||||||
@ -112,5 +103,5 @@ export default function articulationPoints(graph) {
|
|||||||
// Do Depth First Search traversal over submitted graph.
|
// Do Depth First Search traversal over submitted graph.
|
||||||
depthFirstSearch(graph, startVertex, dfsCallbacks);
|
depthFirstSearch(graph, startVertex, dfsCallbacks);
|
||||||
|
|
||||||
return articulationPointsSet;
|
return Object.values(articulationPointsSet);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user