From 62e55b84bd915c8e27ced3c39f51c4d9adb5dbb0 Mon Sep 17 00:00:00 2001 From: Revone Date: Sat, 4 Nov 2023 21:23:02 +0800 Subject: [PATCH] [graph] The getAllPathsBetween method in the [graph] provides a 'limit' parameter to control this exponential time complexity method from entering into a deadlock state. The getMinPathsBetween method, by default, calls the Dijkstra method to avoid invoking the excessively high time complexity method, getAllPathsBetween. --- CHANGELOG.md | 2 +- README.md | 24 ++--- package-lock.json | 50 +++++----- package.json | 10 +- src/data-structures/graph/abstract-graph.ts | 93 +++++++++++-------- .../graph/undirected-graph.test.ts | 20 ++-- 6 files changed, 109 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf94394..a6c4d84 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.41.7](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) +## [v1.41.8](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) ### Changes diff --git a/README.md b/README.md index 6f78b55..1efbc38 100644 --- a/README.md +++ b/README.md @@ -658,40 +658,40 @@ optimal approach to data structure design. [//]: # (Start of Replace Section)
avl-tree
-
test nametime taken (ms)executions per secsample deviation
10,000 add randomly32.7530.539.04e-4
10,000 add & delete randomly69.5914.370.00
10,000 addMany42.0723.778.58e-4
10,000 get27.8735.885.71e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 add randomly30.2933.013.64e-4
10,000 add & delete randomly68.1014.680.00
10,000 addMany39.5425.294.07e-4
10,000 get26.7237.423.77e-4
binary-tree
-
test nametime taken (ms)executions per secsample deviation
1,000 add randomly12.8477.861.92e-4
1,000 add & delete randomly15.9462.723.07e-4
1,000 addMany10.5994.451.67e-4
1,000 get17.9955.592.35e-4
1,000 dfs70.1714.258.83e-4
1,000 bfs54.5218.347.43e-4
1,000 morris37.2626.845.27e-4
+
test nametime taken (ms)executions per secsample deviation
1,000 add randomly12.8877.631.02e-4
1,000 add & delete randomly15.9062.881.08e-4
1,000 addMany10.5994.418.39e-5
1,000 get18.0155.531.95e-4
1,000 dfs69.1114.476.47e-4
1,000 bfs54.4218.384.20e-4
1,000 morris37.1426.922.27e-4
bst
-
test nametime taken (ms)executions per secsample deviation
10,000 add randomly33.2530.074.20e-4
10,000 add & delete randomly73.7613.568.39e-4
10,000 addMany29.9633.384.40e-4
10,000 get28.5335.053.89e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 add randomly28.4835.112.29e-4
10,000 add & delete randomly64.8215.430.01
10,000 addMany28.7434.809.06e-4
10,000 get27.3836.521.82e-4
rb-tree
-
test nametime taken (ms)executions per secsample deviation
100,000 add randomly74.4113.440.00
100,000 add & 1000 delete randomly80.6312.400.01
100,000 getNode60.0016.670.01
+
test nametime taken (ms)executions per secsample deviation
100,000 add randomly72.3013.830.00
100,000 add & 1000 delete randomly81.3712.290.01
100,000 getNode59.4816.819.29e-4
directed-graph
-
test nametime taken (ms)executions per secsample deviation
1,000 addVertex0.109856.401.79e-6
1,000 addEdge6.21160.968.40e-4
1,000 getVertex0.052.16e+43.85e-7
1,000 getEdge22.4344.570.00
tarjan219.404.560.03
tarjan all214.214.670.00
topologicalSort172.465.800.00
+
test nametime taken (ms)executions per secsample deviation
1,000 addVertex0.109786.775.56e-7
1,000 addEdge6.02166.041.27e-4
1,000 getVertex0.052.18e+43.02e-7
1,000 getEdge23.4142.710.00
tarjan223.514.470.01
tarjan all224.894.450.00
topologicalSort181.905.500.00
heap
-
test nametime taken (ms)executions per secsample deviation
10,000 add & pop4.63215.754.05e-5
10,000 fib add & pop355.102.820.00
+
test nametime taken (ms)executions per secsample deviation
10,000 add & pop4.62216.333.06e-5
10,000 fib add & pop351.412.850.00
doubly-linked-list
-
test nametime taken (ms)executions per secsample deviation
1,000,000 unshift210.624.750.05
1,000,000 unshift & shift176.635.660.04
1,000,000 insertBefore288.553.470.05
+
test nametime taken (ms)executions per secsample deviation
1,000,000 unshift214.844.650.04
1,000,000 unshift & shift167.115.980.04
1,000,000 insertBefore335.782.980.07
singly-linked-list
-
test nametime taken (ms)executions per secsample deviation
10,000 push & pop232.244.310.03
10,000 insertBefore245.584.070.00
+
test nametime taken (ms)executions per secsample deviation
10,000 push & pop212.534.710.01
10,000 insertBefore243.944.100.00
max-priority-queue
-
test nametime taken (ms)executions per secsample deviation
10,000 refill & poll11.6286.092.24e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 refill & poll11.4387.501.84e-4
deque
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push223.784.470.05
1,000,000 shift25.7938.780.00
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push216.074.630.05
1,000,000 shift24.9740.050.00
queue
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push44.2722.590.01
1,000,000 push & shift79.6312.560.00
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push42.5723.490.01
1,000,000 push & shift79.9412.519.99e-4
trie
-
test nametime taken (ms)executions per secsample deviation
100,000 push49.2920.290.00
100,000 getWords95.5510.470.01
+
test nametime taken (ms)executions per secsample deviation
100,000 push54.0218.510.00
100,000 getWords82.8312.070.00
[//]: # (End of Replace Section) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d8cdac0..77cca0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "data-structure-typed", - "version": "1.41.6", + "version": "1.41.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "data-structure-typed", - "version": "1.41.6", + "version": "1.41.8", "license": "MIT", "devDependencies": { "@types/benchmark": "^2.1.3", @@ -16,10 +16,10 @@ "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "auto-changelog": "^2.4.0", - "avl-tree-typed": "^1.41.6", + "avl-tree-typed": "^1.41.8", "benchmark": "^2.1.4", - "binary-tree-typed": "^1.41.6", - "bst-typed": "^1.41.6", + "binary-tree-typed": "^1.41.8", + "bst-typed": "^1.41.8", "dependency-cruiser": "^14.1.0", "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", @@ -27,7 +27,7 @@ "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.28.1", "fast-glob": "^3.3.1", - "heap-typed": "^1.41.6", + "heap-typed": "^1.41.8", "istanbul-badges-readme": "^1.8.5", "jest": "^29.7.0", "prettier": "^3.0.3", @@ -2789,12 +2789,12 @@ } }, "node_modules/avl-tree-typed": { - "version": "1.41.6", - "resolved": "https://registry.npmjs.org/avl-tree-typed/-/avl-tree-typed-1.41.6.tgz", - "integrity": "sha512-r7XvaFjgHKR1P/7YD8vC11p4MHgiDVzMU1vmIcQ44q0wxXkQ/YIncJhUSa7oM6PZpSoPRLk33wPpY3+1AUrnKQ==", + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/avl-tree-typed/-/avl-tree-typed-1.41.8.tgz", + "integrity": "sha512-tC0yAkPKS3TYj9q/PbEd+I/AgI/TZIcrueDXsKZMlpLwLe87oj2xfks50CRpmvF89iPHBWCgrtGO8YS1oF2/aw==", "dev": true, "dependencies": { - "data-structure-typed": "^1.41.6" + "data-structure-typed": "^1.41.8" } }, "node_modules/babel-jest": { @@ -2988,12 +2988,12 @@ } }, "node_modules/binary-tree-typed": { - "version": "1.41.6", - "resolved": "https://registry.npmjs.org/binary-tree-typed/-/binary-tree-typed-1.41.6.tgz", - "integrity": "sha512-B9FnRYxDIibvnKFnkm+zNwI62AwOPXMv0gQtAI6mvxuBYPUhy8tGGc5bZnQCisT/KFfrWwUj/1T72kgmyl4h1Q==", + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/binary-tree-typed/-/binary-tree-typed-1.41.8.tgz", + "integrity": "sha512-kViP86Bx1RGKz59KbKXZlRQL9KNsxX4rhPpR9Y1bQz0Tpz5JIuOBJk4f19YK+FdGQpIK3TzUPmFRDIJoHJLnxQ==", "dev": true, "dependencies": { - "data-structure-typed": "^1.41.6" + "data-structure-typed": "^1.41.8" } }, "node_modules/brace-expansion": { @@ -3072,12 +3072,12 @@ } }, "node_modules/bst-typed": { - "version": "1.41.6", - "resolved": "https://registry.npmjs.org/bst-typed/-/bst-typed-1.41.6.tgz", - "integrity": "sha512-GSiJ4JbszYhthcfPpu7+jyXNGzs94Il2111v94s288scZvvRCNjw7cBNUPScf2yAHJ6t4GMlz+ikPu4apoyA5g==", + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/bst-typed/-/bst-typed-1.41.8.tgz", + "integrity": "sha512-pOee++rWrVkub9+PSWMeyNNRae17iCVmu4tRy05ttGNCmb88aWgtCZgw+uNrsDj2WDUcwRbSO+usqvvc2kAqOg==", "dev": true, "dependencies": { - "data-structure-typed": "^1.41.6" + "data-structure-typed": "^1.41.8" } }, "node_modules/buffer-from": { @@ -3480,9 +3480,9 @@ } }, "node_modules/data-structure-typed": { - "version": "1.41.6", - "resolved": "https://registry.npmjs.org/data-structure-typed/-/data-structure-typed-1.41.6.tgz", - "integrity": "sha512-Yi7Kj5wQtjo/h6vE40dMOOma7BHc5ob3/Y6z0N3+zQjUpTUkYBN5jKl24RhxYVfWBCvWrK434prrxVMonwo0BA==", + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/data-structure-typed/-/data-structure-typed-1.41.8.tgz", + "integrity": "sha512-g4gwL7oJH6aMC8uD5Fgwgc/yRWDrVsnLpYtwpVk5A3FFWyIP/FXriQ0kNOdpo4TydSbefyjo/QzapUXHcZ7ZOA==", "dev": true }, "node_modules/debug": { @@ -4847,12 +4847,12 @@ } }, "node_modules/heap-typed": { - "version": "1.41.6", - "resolved": "https://registry.npmjs.org/heap-typed/-/heap-typed-1.41.6.tgz", - "integrity": "sha512-rrquP8vWOgEGWmF8aUmpYKKwFFu3+mk0eH0kwvlQr/dUybuHXf+JwzjBY9kvtaW1IuP8zhK9kK31mTPTjGs/Zw==", + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/heap-typed/-/heap-typed-1.41.8.tgz", + "integrity": "sha512-eegrUhG3a9EP9QHLCtjKmuWG3Fx6/wCSu4wGBIVgJP2OSSWdoGhp1Lj9BRqhCc6hYCLUlOEa271z458Y0vZmDw==", "dev": true, "dependencies": { - "data-structure-typed": "^1.41.6" + "data-structure-typed": "^1.41.8" } }, "node_modules/html-escaper": { diff --git a/package.json b/package.json index 76c5c31..4907829 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-structure-typed", - "version": "1.41.7", + "version": "1.41.9", "description": "Data Structures of Javascript & TypeScript. Binary Tree, BST, Graph, Heap, Priority Queue, Linked List, Queue, Deque, Stack, AVL Tree, Tree Multiset, Trie, Directed Graph, Undirected Graph, Singly Linked List, Doubly Linked List, Max Heap, Max Priority Queue, Min Heap, Min Priority Queue.", "main": "dist/cjs/src/index.js", "module": "dist/mjs/src/index.js", @@ -64,10 +64,10 @@ "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "auto-changelog": "^2.4.0", - "avl-tree-typed": "^1.41.6", + "avl-tree-typed": "^1.41.8", "benchmark": "^2.1.4", - "binary-tree-typed": "^1.41.6", - "bst-typed": "^1.41.6", + "binary-tree-typed": "^1.41.8", + "bst-typed": "^1.41.8", "dependency-cruiser": "^14.1.0", "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", @@ -75,7 +75,7 @@ "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.28.1", "fast-glob": "^3.3.1", - "heap-typed": "^1.41.6", + "heap-typed": "^1.41.8", "istanbul-badges-readme": "^1.8.5", "jest": "^29.7.0", "prettier": "^3.0.3", diff --git a/src/data-structures/graph/abstract-graph.ts b/src/data-structures/graph/abstract-graph.ts index 1600844..044a127 100644 --- a/src/data-structures/graph/abstract-graph.ts +++ b/src/data-structures/graph/abstract-graph.ts @@ -1,15 +1,16 @@ /** * data-structure-typed * - * @author Kirk Qi - * @copyright Copyright (c) 2022 Kirk Qi + * @author Tyler Zeng + * @copyright Copyright (c) 2022 Tyler Zeng * @license MIT License */ -import {arrayRemove, uuidV4} from '../../utils'; +import {uuidV4} from '../../utils'; import {PriorityQueue} from '../priority-queue'; import type {DijkstraResult, VertexKey} from '../../types'; import {IGraph} from '../../interfaces'; import {Queue} from '../queue'; +import * as console from "console"; export abstract class AbstractVertex { key: VertexKey; @@ -223,39 +224,44 @@ export abstract class AbstractGraph< * @param {VO | VertexKey} v1 - The parameter `v1` represents either a vertex object (`VO`) or a vertex ID (`VertexKey`). * It is the starting vertex for finding paths. * @param {VO | VertexKey} v2 - The parameter `v2` represents either a vertex object (`VO`) or a vertex ID (`VertexKey`). + * @param limit - The count of limitation of result array. * @returns The function `getAllPathsBetween` returns an array of arrays of vertices (`VO[][]`). */ - getAllPathsBetween(v1: VO | VertexKey, v2: VO | VertexKey): VO[][] { + getAllPathsBetween(v1: VO | VertexKey, v2: VO | VertexKey, limit = 1000): VO[][] { const paths: VO[][] = []; const vertex1 = this._getVertex(v1); const vertex2 = this._getVertex(v2); + if (!(vertex1 && vertex2)) { return []; } - const dfs = (cur: VO, dest: VO, visiting: Set, path: VO[]) => { - visiting.add(cur); + const stack: { vertex: VO, path: VO[] }[] = []; + stack.push({ vertex: vertex1, path: [vertex1] }); - if (cur === dest) { - paths.push([vertex1, ...path]); + while (stack.length > 0) { + const { vertex, path } = stack.pop()!; + + if (vertex === vertex2) { + paths.push(path); + if (paths.length >= limit) return paths; } - const neighbors = this.getNeighbors(cur); + const neighbors = this.getNeighbors(vertex); for (const neighbor of neighbors) { - if (!visiting.has(neighbor)) { - path.push(neighbor); - dfs(neighbor, dest, visiting, path); - path.pop(); + if (!path.includes(neighbor)) { + const newPath = [...path, neighbor]; + stack.push({ vertex: neighbor, path: newPath }); } } - - visiting.delete(cur); - }; - - dfs(vertex1, vertex2, new Set(), []); + } + console.log('999999', JSON.stringify(paths)); return paths; } + + + /** * 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. @@ -338,38 +344,43 @@ export abstract class AbstractGraph< * @param {boolean} [isWeight] - A boolean flag indicating whether to consider the weight of edges in finding the * minimum path. If set to true, the function will use Dijkstra's algorithm to find the minimum weighted path. If set * to false, the function will use breadth-first search (BFS) to find the minimum path. + * @param isDFS - If set to true, it enforces the use of getAllPathsBetween to first obtain all possible paths, + * followed by iterative computation of the shortest path. This approach may result in exponential time complexity, + * so the default method is to use the Dijkstra algorithm to obtain the shortest weighted path. * @returns The function `getMinPathBetween` returns an array of vertices (`VO[]`) representing the minimum path between * two vertices (`v1` and `v2`). If there is no path between the vertices, it returns `null`. */ - getMinPathBetween(v1: VO | VertexKey, v2: VO | VertexKey, isWeight?: boolean): VO[] | null { + getMinPathBetween(v1: VO | VertexKey, v2: VO | VertexKey, isWeight?: boolean, isDFS = false): VO[] | null { if (isWeight === undefined) isWeight = false; if (isWeight) { - const allPaths = this.getAllPathsBetween(v1, v2); - let min = Infinity; - let minIndex = -1; - let index = 0; - for (const path of allPaths) { - const pathSumWeight = this.getPathSumWeight(path); - if (pathSumWeight < min) { - min = pathSumWeight; - minIndex = index; + if (isDFS) { + const allPaths = this.getAllPathsBetween(v1, v2, 10000); + let min = Infinity; + let minIndex = -1; + let index = 0; + for (const path of allPaths) { + const pathSumWeight = this.getPathSumWeight(path); + if (pathSumWeight < min) { + min = pathSumWeight; + minIndex = index; + } + index++; } - index++; + return allPaths[minIndex] || null; + } else { + return this.dijkstra(v1, v2, true, true)?.minPath ?? []; } - return allPaths[minIndex] || null; + } else { - // BFS + // DFS let minPath: VO[] = []; const vertex1 = this._getVertex(v1); const vertex2 = this._getVertex(v2); - if (!(vertex1 && vertex2)) { - return []; - } - - const dfs = (cur: VO, dest: VO, visiting: Map, path: VO[]) => { - visiting.set(cur, true); + if (!(vertex1 && vertex2)) return []; + const dfs = (cur: VO, dest: VO, visiting: Set, path: VO[]) => { + visiting.add(cur); if (cur === dest) { minPath = [vertex1, ...path]; return; @@ -377,17 +388,17 @@ export abstract class AbstractGraph< const neighbors = this.getNeighbors(cur); for (const neighbor of neighbors) { - if (!visiting.get(neighbor)) { + if (!visiting.has(neighbor)) { path.push(neighbor); dfs(neighbor, dest, visiting, path); - arrayRemove(path, (vertex: VO) => vertex === neighbor); + path.pop(); } } - visiting.set(cur, false); + visiting.delete(cur); }; - dfs(vertex1, vertex2, new Map(), []); + dfs(vertex1, vertex2, new Set(), []); return minPath; } } diff --git a/test/unit/data-structures/graph/undirected-graph.test.ts b/test/unit/data-structures/graph/undirected-graph.test.ts index fce010f..1ad0c6e 100644 --- a/test/unit/data-structures/graph/undirected-graph.test.ts +++ b/test/unit/data-structures/graph/undirected-graph.test.ts @@ -149,8 +149,7 @@ describe('UndirectedGraph', () => { expect(degreeOfC).toBe(1); }); - it('xxx', () => { - // const start = performance.now(); + it('should getAllPathsBetween work well in 66 vertexes 97 edges graph', () => { const graph = new UndirectedGraph<{name: string}, number>(); for (const v of saltyVertexes) { graph.addVertex(v.name, v); @@ -159,9 +158,18 @@ describe('UndirectedGraph', () => { const [s, d] = e; graph.addEdge(s.name, d.name, d.weight); } - // const result = graph.getAllPathsBetween('Intersection_1','Intersection_5'); - // console.log('---xxx', performance.now() - start, result) - // const result = graph.dijkstra('Intersection_1','Intersection_5', true, true); - // console.log('---xxx', performance.now() - start, result) + const allPaths = graph.getAllPathsBetween('Intersection_1','Intersection_5'); + expect(allPaths.length).toBe(1000); + const minWeightedPathDFS = graph.getMinPathBetween('Intersection_1','Intersection_5', true, true); + const minWeightedPath = graph.dijkstra('Intersection_1','Intersection_5', true, true); + expect(minWeightedPathDFS?.[0].key).toBe('Intersection_1'); + expect(minWeightedPathDFS?.[5].key).toBe('Intersection_42'); + expect(minWeightedPathDFS?.[8].key).toBe('Intersection_18'); + expect(minWeightedPathDFS?.[31].key).toBe('Intersection_5'); + expect(minWeightedPath?.minPath?.[0].key).toBe('Intersection_1') + expect(minWeightedPath?.minPath?.[1].key).toBe('Intersection_2') + expect(minWeightedPath?.minPath?.[2].key).toBe('Intersection_3') + expect(minWeightedPath?.minPath?.[3].key).toBe('Intersection_4') + expect(minWeightedPath?.minPath?.[4].key).toBe('Intersection_5') }); });