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)
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
connected network single points whose failure would split the
network into 2 or more disconnected components. They are useful for

View File

@ -23,10 +23,9 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexC,
vertexB,
]);
expect(articulationPointsSet.length).toBe(2);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
expect(articulationPointsSet[1].getKey()).toBe(vertexB.getKey());
});
it('should find articulation points in simple graph with back edge', () => {
@ -50,9 +49,8 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexC,
]);
expect(articulationPointsSet.length).toBe(1);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
});
it('should find articulation points in simple graph with back edge #2', () => {
@ -79,9 +77,8 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexC,
]);
expect(articulationPointsSet.length).toBe(1);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
});
it('should find articulation points in graph', () => {
@ -119,12 +116,11 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexF,
vertexE,
vertexD,
vertexC,
]);
expect(articulationPointsSet.length).toBe(4);
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
expect(articulationPointsSet[1].getKey()).toBe(vertexE.getKey());
expect(articulationPointsSet[2].getKey()).toBe(vertexD.getKey());
expect(articulationPointsSet[3].getKey()).toBe(vertexC.getKey());
});
it('should find articulation points in graph starting with articulation root vertex', () => {
@ -162,12 +158,11 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexF,
vertexE,
vertexC,
vertexD,
]);
expect(articulationPointsSet.length).toBe(4);
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
expect(articulationPointsSet[1].getKey()).toBe(vertexE.getKey());
expect(articulationPointsSet[2].getKey()).toBe(vertexC.getKey());
expect(articulationPointsSet[3].getKey()).toBe(vertexD.getKey());
});
it('should find articulation points in yet another graph #1', () => {
@ -194,10 +189,9 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexD,
vertexC,
]);
expect(articulationPointsSet.length).toBe(2);
expect(articulationPointsSet[0].getKey()).toBe(vertexD.getKey());
expect(articulationPointsSet[1].getKey()).toBe(vertexC.getKey());
});
it('should find articulation points in yet another graph #2', () => {
@ -232,8 +226,7 @@ describe('articulationPoints', () => {
const articulationPointsSet = articulationPoints(graph);
expect(articulationPointsSet).toEqual([
vertexC,
]);
expect(articulationPointsSet.length).toBe(1);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
});
});

View File

@ -7,11 +7,6 @@ class VisitMetadata {
constructor({ discoveryTime, lowDiscoveryTime }) {
this.discoveryTime = discoveryTime;
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
// disconnected children or not.
this.independantChildrenCount = 0;
@ -28,10 +23,10 @@ export default function articulationPoints(graph) {
// Set of vertices we've already visited during DFS.
const visitedSet = {};
// Set of articulation points found so far.
const articulationPointsSet = [];
// Set of articulation points.
const articulationPointsSet = {};
// Time needed to get to the current vertex.
// Time needed to discover to the current vertex.
let discoveryTime = 0;
// Peek the start vertex for DFS traversal.
@ -53,9 +48,6 @@ export default function articulationPoints(graph) {
discoveryTime += 1;
if (previousVertex) {
// Update child vertex information for previous vertex.
visitedSet[previousVertex.getKey()].childVertex = currentVertex;
// Update children counter for previous vertex.
visitedSet[previousVertex.getKey()].independantChildrenCount += 1;
}
@ -64,44 +56,43 @@ export default function articulationPoints(graph) {
* @param {GraphVertex} currentVertex
* @param {GraphVertex} previousVertex
*/
leaveVertex: ({ currentVertex }) => {
// Detect whether current vertex is articulation point or not.
// To do so we need to check two (OR) conditions:
leaveVertex: ({ currentVertex, previousVertex }) => {
if (previousVertex === null) {
// 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.
// 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);
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 child vertex low discovery time.
let childVertexLowDiscoveryTime = null;
if (visitedSet[currentVertex.getKey()].childVertex) {
const childVertexKey = visitedSet[currentVertex.getKey()].childVertex.getKey();
childVertexLowDiscoveryTime = visitedSet[childVertexKey].lowDiscoveryTime;
// 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;
}
// 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 }) => {
@ -112,5 +103,5 @@ export default function articulationPoints(graph) {
// Do Depth First Search traversal over submitted graph.
depthFirstSearch(graph, startVertex, dfsCallbacks);
return articulationPointsSet;
return Object.values(articulationPointsSet);
}