Add Tarjan's algorithm.

This commit is contained in:
Oleksii Trekhleb 2018-05-11 07:42:02 +03:00
parent ff9877cf6b
commit 21d4144e5a
3 changed files with 59 additions and 75 deletions

View File

@ -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

View File

@ -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());
]);
}); });
}); });

View File

@ -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,44 +56,43 @@ 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)
return;
}
// Update the low time with the smallest time of adjacent vertices.
// Get minimum low discovery time from all neighbors.
/** @param {GraphVertex} neighbor */
visitedSet[currentVertex.getKey()].lowDiscoveryTime = currentVertex.getNeighbors().reduce(
(lowestDiscoveryTime, neighbor) => {
const neighborLowTime = visitedSet[neighbor.getKey()].lowDiscoveryTime;
return neighborLowTime < lowestDiscoveryTime ? neighborLowTime : lowestDiscoveryTime;
},
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. // 1. Is it a root vertex with at least two independent children.
// 2. If its visited time is <= low time of adjacent vertex. // 2. If its visited time is <= low time of adjacent vertex.
if (currentVertex === startVertex) { if (previousVertex === startVertex) {
// Check that it has at least two independent children. // Check that root vertex has at least two independent children.
if (visitedSet[currentVertex.getKey()].independantChildrenCount >= 2) { if (visitedSet[previousVertex.getKey()].independantChildrenCount >= 2) {
articulationPointsSet.push(currentVertex); articulationPointsSet[previousVertex.getKey()] = previousVertex;
} }
} else { } else {
// Get child vertex low discovery time. // Get current vertex low discovery time.
let childVertexLowDiscoveryTime = null; const currentLowDiscoveryTime = visitedSet[currentVertex.getKey()].lowDiscoveryTime;
if (visitedSet[currentVertex.getKey()].childVertex) {
const childVertexKey = visitedSet[currentVertex.getKey()].childVertex.getKey(); // Compare current vertex low discovery time with parent discovery time. Check if there
childVertexLowDiscoveryTime = visitedSet[childVertexKey].lowDiscoveryTime; // 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;
} }
// 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.
// Get minimum low discovery time from all neighbors.
/** @param {GraphVertex} neighbor */
visitedSet[currentVertex.getKey()].lowDiscoveryTime = currentVertex.getNeighbors().reduce(
(lowestDiscoveryTime, neighbor) => {
const neighborLowTime = visitedSet[neighbor.getKey()].lowDiscoveryTime;
return neighborLowTime < lowestDiscoveryTime ? neighborLowTime : lowestDiscoveryTime;
},
visitedSet[currentVertex.getKey()].lowDiscoveryTime,
);
} }
}, },
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);
} }