From 25b9e1e9e4b5a2e674f281e3e8c13741052d3683 Mon Sep 17 00:00:00 2001 From: Revone Date: Mon, 6 Nov 2023 14:47:00 +0800 Subject: [PATCH] [graph] To facilitate users who are not familiar with the Tarjan algorithm, provide the following methods: getCycles, getSCCs, getBridges, getCutVertexes, getDFNMap, and getLowMap. --- CHANGELOG.md | 2 +- README.md | 24 ++--- src/data-structures/graph/abstract-graph.ts | 102 +++++++++++++----- .../binary-tree/binary-tree.test.ts | 5 +- .../graph/directed-graph.test.ts | 36 ++++++- .../graph/undirected-graph.test.ts | 34 ++++++ 6 files changed, 157 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c657b..fef9aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. - [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - [`auto-changelog`](https://github.com/CookPete/auto-changelog) -## [v1.42.0](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) +## [v1.42.1](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) ### Changes diff --git a/README.md b/README.md index 0b3571b..f26e3de 100644 --- a/README.md +++ b/README.md @@ -636,40 +636,40 @@ optimal approach to data structure design. [//]: # (Start of Replace Section)
avl-tree
-
test nametime taken (ms)executions per secsample deviation
10,000 add randomly30.6532.633.52e-4
10,000 add & delete randomly66.8414.960.00
10,000 addMany41.5224.080.01
10,000 get26.8537.244.74e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 add randomly30.7432.537.33e-4
10,000 add & delete randomly67.9914.710.00
10,000 addMany39.8225.114.59e-4
10,000 get26.7037.462.61e-4
binary-tree
-
test nametime taken (ms)executions per secsample deviation
1,000 add randomly12.9976.993.06e-4
1,000 add & delete randomly16.1262.044.54e-4
1,000 addMany10.9591.298.27e-4
1,000 get18.1055.242.56e-4
1,000 dfs69.3314.426.64e-4
1,000 bfs57.0417.534.09e-4
1,000 morris37.1426.933.56e-4
+
test nametime taken (ms)executions per secsample deviation
1,000 add randomly12.9177.445.52e-4
1,000 add & delete randomly15.9062.881.24e-4
1,000 addMany10.7293.271.52e-4
1,000 get18.3954.392.28e-4
1,000 dfs71.5813.970.00
1,000 bfs57.6217.350.00
1,000 morris37.1726.902.13e-4
bst
-
test nametime taken (ms)executions per secsample deviation
10,000 add randomly29.6033.793.76e-4
10,000 add & delete randomly67.4514.830.00
10,000 addMany29.0934.388.10e-4
10,000 get27.8135.968.19e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 add randomly30.5032.790.00
10,000 add & delete randomly68.3514.638.92e-4
10,000 addMany29.4134.009.96e-4
10,000 get28.2735.375.46e-4
rb-tree
-
test nametime taken (ms)executions per secsample deviation
100,000 add randomly73.4613.610.01
100,000 add & 1000 delete randomly81.3512.290.01
100,000 getNode65.2315.330.01
+
test nametime taken (ms)executions per secsample deviation
100,000 add randomly82.1912.170.01
100,000 add & 1000 delete randomly99.7410.030.03
100,000 getNode78.2712.780.04
directed-graph
-
test nametime taken (ms)executions per secsample deviation
1,000 addVertex0.109826.704.27e-6
1,000 addEdge6.37156.883.98e-4
1,000 getVertex0.052.14e+49.10e-7
1,000 getEdge22.9343.620.00
tarjan213.014.690.01
tarjan all213.984.670.00
topologicalSort174.065.740.01
+
test nametime taken (ms)executions per secsample deviation
1,000 addVertex0.109687.711.14e-5
1,000 addEdge7.42134.680.00
1,000 getVertex0.052.05e+49.33e-6
1,000 getEdge20.2949.290.00
tarjan190.485.250.01
tarjan all190.725.240.00
topologicalSort161.216.200.02
heap
-
test nametime taken (ms)executions per secsample deviation
10,000 add & pop4.65214.971.07e-4
10,000 fib add & pop354.242.820.00
+
test nametime taken (ms)executions per secsample deviation
10,000 add & pop4.70212.555.89e-4
10,000 fib add & pop366.962.730.02
doubly-linked-list
-
test nametime taken (ms)executions per secsample deviation
1,000,000 unshift226.924.410.05
1,000,000 unshift & shift169.365.900.04
1,000,000 insertBefore319.443.130.06
+
test nametime taken (ms)executions per secsample deviation
1,000,000 unshift226.424.420.04
1,000,000 unshift & shift163.146.130.01
1,000,000 insertBefore343.182.910.08
singly-linked-list
-
test nametime taken (ms)executions per secsample deviation
10,000 push & pop226.294.420.02
10,000 insertBefore252.653.960.01
+
test nametime taken (ms)executions per secsample deviation
10,000 push & pop217.344.600.01
10,000 insertBefore248.514.020.00
max-priority-queue
-
test nametime taken (ms)executions per secsample deviation
10,000 refill & poll11.6485.912.88e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 refill & poll11.6585.813.52e-4
deque
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push222.814.490.06
1,000,000 shift26.8037.310.00
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push224.574.450.04
1,000,000 shift25.1539.770.00
queue
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push45.9021.790.01
1,000,000 push & shift81.2412.310.00
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push43.5222.980.01
1,000,000 push & shift79.7112.550.00
trie
-
test nametime taken (ms)executions per secsample deviation
100,000 push59.0316.940.01
100,000 getWords89.1711.210.00
+
test nametime taken (ms)executions per secsample deviation
100,000 push59.2816.870.00
100,000 getWords106.799.360.00
[//]: # (End of Replace Section) \ No newline at end of file diff --git a/src/data-structures/graph/abstract-graph.ts b/src/data-structures/graph/abstract-graph.ts index 3147c5f..72a4560 100644 --- a/src/data-structures/graph/abstract-graph.ts +++ b/src/data-structures/graph/abstract-graph.ts @@ -64,8 +64,7 @@ export abstract class AbstractGraph< E = any, VO extends AbstractVertex = AbstractVertex, EO extends AbstractEdge = AbstractEdge -> implements IGraph -{ +> implements IGraph { protected _vertices: Map = new Map(); get vertices(): Map { @@ -236,10 +235,10 @@ export abstract class AbstractGraph< } const stack: { vertex: VO, path: VO[] }[] = []; - stack.push({ vertex: vertex1, path: [vertex1] }); + stack.push({vertex: vertex1, path: [vertex1]}); while (stack.length > 0) { - const { vertex, path } = stack.pop()!; + const {vertex, path} = stack.pop()!; if (vertex === vertex2) { paths.push(path); @@ -250,7 +249,7 @@ export abstract class AbstractGraph< for (const neighbor of neighbors) { if (!path.includes(neighbor)) { const newPath = [...path, neighbor]; - stack.push({ vertex: neighbor, path: newPath }); + stack.push({vertex: neighbor, path: newPath}); } } } @@ -258,8 +257,6 @@ export abstract class AbstractGraph< } - - /** * The function calculates the sum of weights along a given path. * @param {VO[]} path - An array of vertices (VO) representing a path in a graph. @@ -522,14 +519,14 @@ export abstract class AbstractGraph< } getMinDist && - distMap.forEach((d, v) => { - if (v !== srcVertex) { - if (d < minDist) { - minDist = d; - if (genPaths) minDest = v; - } + distMap.forEach((d, v) => { + if (v !== srcVertex) { + if (d < minDist) { + minDist = d; + if (genPaths) minDest = v; } - }); + } + }); genPaths && getPaths(minDest); @@ -591,7 +588,7 @@ export abstract class AbstractGraph< if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Infinity); } - const heap = new PriorityQueue<{key: number; value: VO}>({comparator: (a, b) => a.key - b.key}); + const heap = new PriorityQueue<{ key: number; value: VO }>({comparator: (a, b) => a.key - b.key}); heap.add({key: 0, value: srcVertex}); distMap.set(srcVertex, 0); @@ -802,11 +799,6 @@ export abstract class AbstractGraph< * Floyd algorithm time: O(VO^3) space: O(VO^2), not support graph with negative weight cycle * all pairs * The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of nodes in a graph. It employs dynamic programming to compute the shortest paths from any node to any other node. The Floyd-Warshall algorithm's advantage lies in its ability to handle graphs with negative-weight edges, and it can simultaneously compute shortest paths between any two nodes. - */ - - /** - * Floyd algorithm time: O(VO^3) space: O(VO^2), not support graph with negative weight cycle - * all pairs * / /** @@ -815,12 +807,12 @@ export abstract class AbstractGraph< * The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of nodes in a graph. It employs dynamic programming to compute the shortest paths from any node to any other node. The Floyd-Warshall algorithm's advantage lies in its ability to handle graphs with negative-weight edges, and it can simultaneously compute shortest paths between any two nodes. * The function implements the Floyd-Warshall algorithm to find the shortest path between all pairs of vertices in a * graph. - * @returns The function `floyd()` returns an object with two properties: `costs` and `predecessor`. The `costs` + * @returns The function `floydWarshall()` returns an object with two properties: `costs` and `predecessor`. The `costs` * property is a 2D array of numbers representing the shortest path costs between vertices in a graph. The * `predecessor` property is a 2D array of vertices (or `null`) representing the predecessor vertices in the shortest * path between vertices in the */ - floyd(): {costs: number[][]; predecessor: (VO | null)[][]} { + floydWarshall(): { costs: number[][]; predecessor: (VO | null)[][] } { const idAndVertices = [...this._vertices]; const n = idAndVertices.length; @@ -871,7 +863,7 @@ export abstract class AbstractGraph< * Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs. * The `tarjan` function is used to perform various graph analysis tasks such as finding articulation points, bridges, * strongly connected components (SCCs), and cycles in a graph. - * @param {boolean} [needArticulationPoints] - A boolean value indicating whether or not to calculate and return the + * @param {boolean} [needCutVertexes] - A boolean value indicating whether or not to calculate and return the * articulation points in the graph. Articulation points are the vertices in a graph whose removal would increase the * number of connected components in the graph. * @param {boolean} [needBridges] - A boolean flag indicating whether the algorithm should find and return the bridges @@ -884,13 +876,13 @@ export abstract class AbstractGraph< * are arrays of vertices that form cycles within the SCCs. * @returns The function `tarjan` returns an object with the following properties: */ - tarjan(needArticulationPoints?: boolean, needBridges?: boolean, needSCCs?: boolean, needCycles?: boolean) { + tarjan(needCutVertexes: boolean = false, needBridges: boolean = false, needSCCs: boolean = true, needCycles: boolean = false) { // !! in undirected graph we will not let child visit parent when dfs // !! articulation point(in dfs search tree not in graph): (cur !== root && cur.has(child)) && (low(child) >= dfn(cur)) || (cur === root && cur.children() >= 2) // !! bridge: low(child) > dfn(cur) const defaultConfig = false; - if (needArticulationPoints === undefined) needArticulationPoints = defaultConfig; + if (needCutVertexes === undefined) needCutVertexes = defaultConfig; if (needBridges === undefined) needBridges = defaultConfig; if (needSCCs === undefined) needSCCs = defaultConfig; if (needCycles === undefined) needCycles = defaultConfig; @@ -905,7 +897,7 @@ export abstract class AbstractGraph< const [root] = vertices.values(); - const articulationPoints: VO[] = []; + const cutVertexes: VO[] = []; const bridges: EO[] = []; let dfn = 0; const dfs = (cur: VO, parent: VO | null) => { @@ -929,10 +921,10 @@ export abstract class AbstractGraph< } const curFromMap = dfnMap.get(cur); if (childLow !== undefined && curFromMap !== undefined) { - if (needArticulationPoints) { + if (needCutVertexes) { if ((cur === root && childCount >= 2) || (cur !== root && childLow >= curFromMap)) { // todo not ensure the logic if (cur === root && childCount >= 2 || ((cur !== root) && (childLow >= curFromMap))) { - articulationPoints.push(cur); + cutVertexes.push(cur); } } @@ -983,7 +975,59 @@ export abstract class AbstractGraph< }); } - return {dfnMap, lowMap, bridges, articulationPoints, SCCs, cycles}; + return {dfnMap, lowMap, bridges, cutVertexes, SCCs, cycles}; + } + + /** + * The function returns a map that associates each vertex object with its corresponding depth-first + * number. + * @returns A Map object with keys of type VO and values of type number. + */ + getDFNMap(): Map { + return this.tarjan(false, false, false, false).dfnMap; + } + + /** + * The function returns a Map object that contains the low values of each vertex in a Tarjan + * algorithm. + * @returns The method `getLowMap()` is returning a `Map` object with keys of type `VO` and values of + * type `number`. + */ + getLowMap(): Map { + return this.tarjan(false, false, false, false).lowMap; + } + + /** + * The function `getCycles` returns a map of cycles found using the Tarjan algorithm. + * @returns The function `getCycles()` is returning a `Map`. + */ + getCycles(): Map { + return this.tarjan(false, false, false, true).cycles; + } + + /** + * The function "getCutVertexes" returns an array of cut vertexes using the Tarjan algorithm. + * @returns an array of VO objects, specifically the cut vertexes. + */ + getCutVertexes(): VO[] { + return this.tarjan(true, false, false, false).cutVertexes; + } + + /** + * The function "getSCCs" returns a map of strongly connected components (SCCs) using the Tarjan + * algorithm. + * @returns a map where the keys are numbers and the values are arrays of VO objects. + */ + getSCCs(): Map { + return this.tarjan(false, false, true, false).SCCs; + } + + /** + * The function "getBridges" returns an array of bridges using the Tarjan algorithm. + * @returns the bridges found using the Tarjan algorithm. + */ + getBridges() { + return this.tarjan(false, true, false, false).bridges; } protected abstract _addEdgeOnly(edge: EO): boolean; diff --git a/test/unit/data-structures/binary-tree/binary-tree.test.ts b/test/unit/data-structures/binary-tree/binary-tree.test.ts index e70a3f8..74e497d 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -1,8 +1,7 @@ import {AVLTree, AVLTreeNode, BinaryTree, BinaryTreeNode, IterationType} from '../../../../src'; -import {isDebugTest} from '../../../config'; +// import {isDebugTest} from '../../../config'; -const isDebug = isDebugTest; -// const isDebug = true; +// const isDebug = isDebugTest; describe('BinaryTreeNode', () => { it('should create an instance of BinaryTreeNode', () => { diff --git a/test/unit/data-structures/graph/directed-graph.test.ts b/test/unit/data-structures/graph/directed-graph.test.ts index ff8b2ad..ded2fff 100644 --- a/test/unit/data-structures/graph/directed-graph.test.ts +++ b/test/unit/data-structures/graph/directed-graph.test.ts @@ -340,7 +340,7 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => { expect(min).toBe(Infinity); expect(minPath).toBeInstanceOf(Array); - const floydResult = myGraph.floyd(); + const floydResult = myGraph.floydWarshall(); expect(floydResult).toBeTruthy(); if (floydResult) { const {costs, predecessor} = floydResult; @@ -562,3 +562,37 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => { } }); }); + + +describe('cycles, strongly connected components, bridges, articular points in DirectedGraph', () => { + const graph = new DirectedGraph(); + graph.addVertex('A'); + graph.addVertex('B'); + graph.addVertex('C'); + graph.addVertex('D'); + graph.addVertex('E'); + graph.addVertex('F'); + graph.addVertex('G'); + graph.addVertex('H'); + graph.addEdge('A', 'B'); + graph.addEdge('B', 'D'); + graph.addEdge('D', 'C'); + graph.addEdge('C', 'A'); + graph.addEdge('C', 'B'); + graph.addEdge('D', 'E'); + graph.addEdge('E', 'G'); + graph.addEdge('E', 'H'); + graph.addEdge('H', 'F'); + const cycles = graph.getCycles(); + const scCs = graph.getSCCs(); + const bridges = graph.getBridges(); + const cutVertexes = graph.getCutVertexes(); + const dfnMap = graph.getDFNMap(); + const lowMap = graph.getLowMap(); + expect(cycles.size).toBe(1) + expect(scCs.size).toBe(5) + expect(bridges.length).toBe(4) + expect(cutVertexes.length).toBe(4) + expect(dfnMap.size).toBe(8) + expect(lowMap.size).toBe(8) +}); \ No newline at end of file diff --git a/test/unit/data-structures/graph/undirected-graph.test.ts b/test/unit/data-structures/graph/undirected-graph.test.ts index a216e49..12855d5 100644 --- a/test/unit/data-structures/graph/undirected-graph.test.ts +++ b/test/unit/data-structures/graph/undirected-graph.test.ts @@ -174,3 +174,37 @@ describe('UndirectedGraph', () => { expect(minWeightedPath?.minPath?.[4]?.key).toBe('Intersection_5') }); }); + + +describe('cycles, strongly connected components, bridges, articular points in UndirectedGraph', () => { + const graph = new UndirectedGraph(); + graph.addVertex('A'); + graph.addVertex('B'); + graph.addVertex('C'); + graph.addVertex('D'); + graph.addVertex('E'); + graph.addVertex('F'); + graph.addVertex('G'); + graph.addVertex('H'); + graph.addEdge('A', 'B'); + graph.addEdge('B', 'D'); + graph.addEdge('D', 'C'); + graph.addEdge('C', 'A'); + graph.addEdge('C', 'B'); + graph.addEdge('D', 'E'); + graph.addEdge('E', 'G'); + graph.addEdge('E', 'H'); + graph.addEdge('H', 'F'); + const cycles = graph.getCycles(); + const scCs = graph.getSCCs(); + const bridges = graph.getBridges(); + const cutVertexes = graph.getCutVertexes(); + const dfnMap = graph.getDFNMap(); + const lowMap = graph.getLowMap(); + expect(cycles.size).toBe(1) + expect(scCs.size).toBe(5) + expect(bridges.length).toBe(4) + expect(cutVertexes.length).toBe(4) + expect(dfnMap.size).toBe(8) + expect(lowMap.size).toBe(8) +}); \ No newline at end of file