diff --git a/src/algorithms/graph/articulation-points/README.md b/src/algorithms/graph/articulation-points/README.md index d9a7eff0..4c64d81e 100644 --- a/src/algorithms/graph/articulation-points/README.md +++ b/src/algorithms/graph/articulation-points/README.md @@ -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 diff --git a/src/algorithms/graph/articulation-points/__test__/articulationPoints.test.js b/src/algorithms/graph/articulation-points/__test__/articulationPoints.test.js index c0241241..03b12ed9 100644 --- a/src/algorithms/graph/articulation-points/__test__/articulationPoints.test.js +++ b/src/algorithms/graph/articulation-points/__test__/articulationPoints.test.js @@ -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()); }); }); diff --git a/src/algorithms/graph/articulation-points/articulationPoints.js b/src/algorithms/graph/articulation-points/articulationPoints.js index dd653167..d4bd9e04 100644 --- a/src/algorithms/graph/articulation-points/articulationPoints.js +++ b/src/algorithms/graph/articulation-points/articulationPoints.js @@ -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); }