@graphty/algorithms 1.3.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +358 -413
- package/dist/algorithms.js +564 -261
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +10 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +27 -6
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +11 -2
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +17 -3
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +7 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +9 -6
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +21 -3
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +25 -7
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +28 -2
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank.js +30 -4
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -3
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +11 -4
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +10 -3
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +11 -4
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/index.d.ts +1 -1
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +1 -1
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +10 -3
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +13 -8
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +14 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +16 -1
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +2 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +21 -4
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/index.d.ts.map +1 -1
- package/dist/src/algorithms/community/index.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +0 -3
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +13 -6
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +0 -1
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +37 -6
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +37 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.js +42 -7
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -1
- package/dist/src/algorithms/community/louvain.d.ts +2 -3
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +19 -6
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +0 -4
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.js +1 -5
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -1
- package/dist/src/algorithms/components/connected.d.ts +23 -0
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +29 -3
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +10 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +10 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -1
- package/dist/src/algorithms/matching/index.d.ts.map +1 -1
- package/dist/src/algorithms/matching/index.js.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.d.ts +8 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.js +36 -2
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -1
- package/dist/src/algorithms/mst/index.d.ts.map +1 -1
- package/dist/src/algorithms/mst/index.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts +14 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +17 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/mst/prim.d.ts +9 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -1
- package/dist/src/algorithms/mst/prim.js +9 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +11 -4
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +18 -16
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +15 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +15 -0
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +13 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +37 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +23 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +34 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +1 -1
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts +10 -0
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +48 -0
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/algorithms/traversal/index.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/index.js.map +1 -1
- package/dist/src/benchmark-all-algorithms.d.ts +2 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -1
- package/dist/src/benchmark-all-algorithms.js +32 -3
- package/dist/src/benchmark-all-algorithms.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +8 -1
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +22 -6
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +2 -5
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +14 -12
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl.d.ts +6 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +34 -3
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +4 -1
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +61 -28
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.d.ts +45 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +45 -1
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/index.d.ts.map +1 -1
- package/dist/src/data-structures/index.js.map +1 -1
- package/dist/src/data-structures/priority-queue.d.ts +18 -1
- package/dist/src/data-structures/priority-queue.d.ts.map +1 -1
- package/dist/src/data-structures/priority-queue.js +26 -5
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.d.ts +21 -0
- package/dist/src/data-structures/union-find.d.ts.map +1 -1
- package/dist/src/data-structures/union-find.js +21 -0
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +4 -2
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +31 -2
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +0 -3
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +17 -4
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +26 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +36 -10
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts +21 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +27 -6
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/link-prediction/index.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +103 -53
- package/dist/src/optimized/bit-packed.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.js +104 -54
- package/dist/src/optimized/bit-packed.js.map +1 -1
- package/dist/src/optimized/csr-graph.d.ts +87 -24
- package/dist/src/optimized/csr-graph.d.ts.map +1 -1
- package/dist/src/optimized/csr-graph.js +87 -24
- package/dist/src/optimized/csr-graph.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +37 -16
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +39 -20
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/optimized/graph-adapter.d.ts +58 -9
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -1
- package/dist/src/optimized/graph-adapter.js +58 -10
- package/dist/src/optimized/graph-adapter.js.map +1 -1
- package/dist/src/optimized/index.d.ts +1 -1
- package/dist/src/optimized/index.d.ts.map +1 -1
- package/dist/src/optimized/index.js +3 -1
- package/dist/src/optimized/index.js.map +1 -1
- package/dist/src/pathfinding/astar.d.ts +25 -9
- package/dist/src/pathfinding/astar.d.ts.map +1 -1
- package/dist/src/pathfinding/astar.js +25 -10
- package/dist/src/pathfinding/astar.js.map +1 -1
- package/dist/src/pathfinding/utils.d.ts +15 -6
- package/dist/src/pathfinding/utils.d.ts.map +1 -1
- package/dist/src/pathfinding/utils.js +19 -7
- package/dist/src/pathfinding/utils.js.map +1 -1
- package/dist/src/research/grsbm.d.ts +0 -1
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +22 -9
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/index.d.ts.map +1 -1
- package/dist/src/research/index.js.map +1 -1
- package/dist/src/research/sync.d.ts +0 -1
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +28 -7
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +0 -1
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +32 -4
- package/dist/src/research/terahac.js.map +1 -1
- package/dist/src/utils/graph-converters.d.ts +31 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -1
- package/dist/src/utils/graph-converters.js +31 -0
- package/dist/src/utils/graph-converters.js.map +1 -1
- package/dist/src/utils/graph-utilities.d.ts.map +1 -1
- package/dist/src/utils/graph-utilities.js.map +1 -1
- package/dist/src/utils/math-utilities.d.ts +11 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -1
- package/dist/src/utils/math-utilities.js +14 -5
- package/dist/src/utils/math-utilities.js.map +1 -1
- package/dist/src/utils/optimization-helpers.d.ts +9 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -1
- package/dist/src/utils/optimization-helpers.js +12 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -1
- package/dist/src/utils/priorityQueue.d.ts +24 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -1
- package/dist/src/utils/priorityQueue.js +34 -4
- package/dist/src/utils/priorityQueue.js.map +1 -1
- package/package.json +49 -45
- package/src/algorithms/centrality/betweenness.ts +34 -17
- package/src/algorithms/centrality/closeness.ts +34 -17
- package/src/algorithms/centrality/degree.ts +15 -19
- package/src/algorithms/centrality/delta-pagerank-simple.ts +36 -28
- package/src/algorithms/centrality/delta-pagerank.ts +34 -11
- package/src/algorithms/centrality/eigenvector.ts +14 -15
- package/src/algorithms/centrality/hits.ts +17 -17
- package/src/algorithms/centrality/index.ts +20 -15
- package/src/algorithms/centrality/katz.ts +17 -25
- package/src/algorithms/centrality/pagerank.ts +26 -17
- package/src/algorithms/community/girvan-newman.ts +39 -28
- package/src/algorithms/community/index.ts +6 -7
- package/src/algorithms/community/label-propagation.ts +20 -28
- package/src/algorithms/community/leiden.ts +61 -37
- package/src/algorithms/community/louvain-optimized.ts +49 -20
- package/src/algorithms/community/louvain.ts +35 -25
- package/src/algorithms/community/modularity-utils.ts +5 -17
- package/src/algorithms/components/connected.ts +40 -17
- package/src/algorithms/matching/bipartite.ts +17 -10
- package/src/algorithms/matching/index.ts +4 -4
- package/src/algorithms/matching/isomorphism.ts +53 -23
- package/src/algorithms/mst/index.ts +3 -4
- package/src/algorithms/mst/kruskal.ts +21 -6
- package/src/algorithms/mst/prim.ts +13 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +16 -15
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +28 -28
- package/src/algorithms/shortest-path/dijkstra.ts +23 -17
- package/src/algorithms/shortest-path/floyd-warshall.ts +21 -4
- package/src/algorithms/shortest-path/index.ts +5 -5
- package/src/algorithms/traversal/bfs-unified.ts +64 -60
- package/src/algorithms/traversal/bfs-variants.ts +61 -37
- package/src/algorithms/traversal/bfs.ts +1 -7
- package/src/algorithms/traversal/dfs.ts +61 -42
- package/src/algorithms/traversal/index.ts +3 -3
- package/src/benchmark-all-algorithms.ts +57 -25
- package/src/clustering/hierarchical.ts +40 -34
- package/src/clustering/index.ts +8 -8
- package/src/clustering/k-core.ts +26 -42
- package/src/clustering/mcl.ts +53 -23
- package/src/clustering/spectral.ts +91 -57
- package/src/core/graph.ts +54 -14
- package/src/data-structures/index.ts +2 -2
- package/src/data-structures/priority-queue.ts +36 -11
- package/src/data-structures/union-find.ts +22 -1
- package/src/flow/ford-fulkerson.ts +62 -35
- package/src/flow/min-cut.ts +43 -45
- package/src/index.ts +3 -2
- package/src/link-prediction/adamic-adar.ts +65 -51
- package/src/link-prediction/common-neighbors.ts +45 -36
- package/src/link-prediction/index.ts +1 -1
- package/src/optimized/bit-packed.ts +106 -56
- package/src/optimized/csr-graph.ts +93 -30
- package/src/optimized/direction-optimized-bfs.ts +44 -25
- package/src/optimized/graph-adapter.ts +64 -16
- package/src/optimized/index.ts +7 -9
- package/src/pathfinding/astar.ts +40 -25
- package/src/pathfinding/utils.ts +20 -13
- package/src/research/grsbm.ts +30 -24
- package/src/research/index.ts +4 -4
- package/src/research/sync.ts +39 -23
- package/src/research/terahac.ts +54 -20
- package/src/utils/graph-converters.ts +42 -14
- package/src/utils/graph-utilities.ts +11 -37
- package/src/utils/math-utilities.ts +17 -8
- package/src/utils/optimization-helpers.ts +27 -21
- package/src/utils/priorityQueue.ts +40 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {Graph} from "../../core/graph.js";
|
|
2
|
-
import type {NodeId, TraversalOptions, TraversalResult} from "../../types/index.js";
|
|
1
|
+
import { Graph } from "../../core/graph.js";
|
|
2
|
+
import type { NodeId, TraversalOptions, TraversalResult } from "../../types/index.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Depth-First Search (DFS) implementation
|
|
@@ -18,12 +18,12 @@ export interface DFSOptions extends TraversalOptions {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Perform depth-first search starting from a given node
|
|
21
|
+
* @param graph - The input graph to traverse
|
|
22
|
+
* @param startNode - The node to start the DFS from
|
|
23
|
+
* @param options - DFS options (recursive mode, pre/post order, etc.)
|
|
24
|
+
* @returns Traversal result with visited nodes, order, and tree structure
|
|
21
25
|
*/
|
|
22
|
-
export function depthFirstSearch(
|
|
23
|
-
graph: Graph,
|
|
24
|
-
startNode: NodeId,
|
|
25
|
-
options: DFSOptions = {},
|
|
26
|
-
): TraversalResult {
|
|
26
|
+
export function depthFirstSearch(graph: Graph, startNode: NodeId, options: DFSOptions = {}): TraversalResult {
|
|
27
27
|
if (!graph.hasNode(startNode)) {
|
|
28
28
|
throw new Error(`Start node ${String(startNode)} not found in graph`);
|
|
29
29
|
}
|
|
@@ -38,11 +38,17 @@ export function depthFirstSearch(
|
|
|
38
38
|
dfsIterative(graph, startNode, visited, order, tree, options);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
return {visited, order, tree};
|
|
41
|
+
return { visited, order, tree };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Iterative DFS implementation (safer for browsers)
|
|
46
|
+
* @param graph - The input graph to traverse
|
|
47
|
+
* @param startNode - The node to start the DFS from
|
|
48
|
+
* @param visited - Set to track visited nodes
|
|
49
|
+
* @param order - Array to store traversal order
|
|
50
|
+
* @param tree - Map to store parent relationships
|
|
51
|
+
* @param options - DFS options (recursive mode, pre/post order, etc.)
|
|
46
52
|
*/
|
|
47
53
|
function dfsIterative(
|
|
48
54
|
graph: Graph,
|
|
@@ -58,8 +64,8 @@ function dfsIterative(
|
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
const stack: {node: NodeId
|
|
62
|
-
stack.push({node: startNode, parent: null, depth: 0});
|
|
67
|
+
const stack: { node: NodeId; parent: NodeId | null; depth: number }[] = [];
|
|
68
|
+
stack.push({ node: startNode, parent: null, depth: 0 });
|
|
63
69
|
|
|
64
70
|
while (stack.length > 0) {
|
|
65
71
|
const current = stack.pop();
|
|
@@ -89,7 +95,7 @@ function dfsIterative(
|
|
|
89
95
|
for (let i = neighbors.length - 1; i >= 0; i--) {
|
|
90
96
|
const neighbor = neighbors[i];
|
|
91
97
|
if (neighbor !== undefined && !visited.has(neighbor)) {
|
|
92
|
-
stack.push({node: neighbor, parent: current.node, depth: current.depth + 1});
|
|
98
|
+
stack.push({ node: neighbor, parent: current.node, depth: current.depth + 1 });
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
}
|
|
@@ -98,6 +104,14 @@ function dfsIterative(
|
|
|
98
104
|
|
|
99
105
|
/**
|
|
100
106
|
* Recursive DFS implementation
|
|
107
|
+
* @param graph - The input graph to traverse
|
|
108
|
+
* @param node - Current node being visited
|
|
109
|
+
* @param visited - Set to track visited nodes
|
|
110
|
+
* @param order - Array to store traversal order
|
|
111
|
+
* @param tree - Map to store parent relationships
|
|
112
|
+
* @param options - DFS options (recursive mode, pre/post order, etc.)
|
|
113
|
+
* @param depth - Current depth in the traversal tree
|
|
114
|
+
* @param parent - Parent node of the current node
|
|
101
115
|
*/
|
|
102
116
|
function dfsRecursive(
|
|
103
117
|
graph: Graph,
|
|
@@ -145,6 +159,8 @@ function dfsRecursive(
|
|
|
145
159
|
|
|
146
160
|
/**
|
|
147
161
|
* Detect cycles in a graph using DFS
|
|
162
|
+
* @param graph - The input graph to check for cycles
|
|
163
|
+
* @returns True if the graph contains a cycle, false otherwise
|
|
148
164
|
*/
|
|
149
165
|
export function hasCycleDFS(graph: Graph): boolean {
|
|
150
166
|
const visited = new Set<NodeId>();
|
|
@@ -170,13 +186,13 @@ export function hasCycleDFS(graph: Graph): boolean {
|
|
|
170
186
|
|
|
171
187
|
/**
|
|
172
188
|
* Utility function for cycle detection in directed graphs
|
|
189
|
+
* @param graph - The directed graph to check
|
|
190
|
+
* @param node - Current node being visited
|
|
191
|
+
* @param visited - Set of all visited nodes
|
|
192
|
+
* @param recursionStack - Set of nodes in the current recursion path
|
|
193
|
+
* @returns True if a cycle is found, false otherwise
|
|
173
194
|
*/
|
|
174
|
-
function hasCycleUtilDirected(
|
|
175
|
-
graph: Graph,
|
|
176
|
-
node: NodeId,
|
|
177
|
-
visited: Set<NodeId>,
|
|
178
|
-
recursionStack: Set<NodeId>,
|
|
179
|
-
): boolean {
|
|
195
|
+
function hasCycleUtilDirected(graph: Graph, node: NodeId, visited: Set<NodeId>, recursionStack: Set<NodeId>): boolean {
|
|
180
196
|
visited.add(node);
|
|
181
197
|
recursionStack.add(node);
|
|
182
198
|
|
|
@@ -198,13 +214,13 @@ function hasCycleUtilDirected(
|
|
|
198
214
|
|
|
199
215
|
/**
|
|
200
216
|
* Utility function for cycle detection in undirected graphs
|
|
217
|
+
* @param graph - The undirected graph to check
|
|
218
|
+
* @param node - Current node being visited
|
|
219
|
+
* @param visited - Set of all visited nodes
|
|
220
|
+
* @param parent - Parent node of the current node
|
|
221
|
+
* @returns True if a cycle is found, false otherwise
|
|
201
222
|
*/
|
|
202
|
-
function hasCycleUtilUndirected(
|
|
203
|
-
graph: Graph,
|
|
204
|
-
node: NodeId,
|
|
205
|
-
visited: Set<NodeId>,
|
|
206
|
-
parent: NodeId | null,
|
|
207
|
-
): boolean {
|
|
223
|
+
function hasCycleUtilUndirected(graph: Graph, node: NodeId, visited: Set<NodeId>, parent: NodeId | null): boolean {
|
|
208
224
|
visited.add(node);
|
|
209
225
|
|
|
210
226
|
// Check all neighbors
|
|
@@ -224,6 +240,8 @@ function hasCycleUtilUndirected(
|
|
|
224
240
|
|
|
225
241
|
/**
|
|
226
242
|
* Topological sorting using DFS (for directed acyclic graphs)
|
|
243
|
+
* @param graph - The directed acyclic graph to sort
|
|
244
|
+
* @returns Array of node IDs in topological order, or null if graph has cycles
|
|
227
245
|
*/
|
|
228
246
|
export function topologicalSort(graph: Graph): NodeId[] | null {
|
|
229
247
|
if (!graph.isDirected) {
|
|
@@ -251,13 +269,12 @@ export function topologicalSort(graph: Graph): NodeId[] | null {
|
|
|
251
269
|
|
|
252
270
|
/**
|
|
253
271
|
* Utility function for topological sorting
|
|
272
|
+
* @param graph - The directed graph being sorted
|
|
273
|
+
* @param node - Current node being visited
|
|
274
|
+
* @param visited - Set of visited nodes
|
|
275
|
+
* @param stack - Stack to store nodes in reverse topological order
|
|
254
276
|
*/
|
|
255
|
-
function topologicalSortUtil(
|
|
256
|
-
graph: Graph,
|
|
257
|
-
node: NodeId,
|
|
258
|
-
visited: Set<NodeId>,
|
|
259
|
-
stack: NodeId[],
|
|
260
|
-
): void {
|
|
277
|
+
function topologicalSortUtil(graph: Graph, node: NodeId, visited: Set<NodeId>, stack: NodeId[]): void {
|
|
261
278
|
visited.add(node);
|
|
262
279
|
|
|
263
280
|
// Visit all neighbors first
|
|
@@ -273,6 +290,8 @@ function topologicalSortUtil(
|
|
|
273
290
|
|
|
274
291
|
/**
|
|
275
292
|
* Find strongly connected components using DFS (for directed graphs)
|
|
293
|
+
* @param graph - The directed graph to analyze
|
|
294
|
+
* @returns Array of strongly connected components (each is an array of node IDs)
|
|
276
295
|
*/
|
|
277
296
|
export function findStronglyConnectedComponents(graph: Graph): NodeId[][] {
|
|
278
297
|
if (!graph.isDirected) {
|
|
@@ -310,13 +329,12 @@ export function findStronglyConnectedComponents(graph: Graph): NodeId[][] {
|
|
|
310
329
|
|
|
311
330
|
/**
|
|
312
331
|
* DFS to record finish order
|
|
332
|
+
* @param graph - The graph being traversed
|
|
333
|
+
* @param node - Current node being visited
|
|
334
|
+
* @param visited - Set of visited nodes
|
|
335
|
+
* @param finishOrder - Array to store nodes in order of completion
|
|
313
336
|
*/
|
|
314
|
-
function dfsFinishOrder(
|
|
315
|
-
graph: Graph,
|
|
316
|
-
node: NodeId,
|
|
317
|
-
visited: Set<NodeId>,
|
|
318
|
-
finishOrder: NodeId[],
|
|
319
|
-
): void {
|
|
337
|
+
function dfsFinishOrder(graph: Graph, node: NodeId, visited: Set<NodeId>, finishOrder: NodeId[]): void {
|
|
320
338
|
visited.add(node);
|
|
321
339
|
|
|
322
340
|
for (const neighbor of Array.from(graph.neighbors(node))) {
|
|
@@ -330,13 +348,12 @@ function dfsFinishOrder(
|
|
|
330
348
|
|
|
331
349
|
/**
|
|
332
350
|
* DFS to collect nodes in a component
|
|
351
|
+
* @param graph - The graph being traversed
|
|
352
|
+
* @param node - Current node being visited
|
|
353
|
+
* @param visited - Set of visited nodes
|
|
354
|
+
* @param component - Array to collect nodes in the current component
|
|
333
355
|
*/
|
|
334
|
-
function dfsCollectComponent(
|
|
335
|
-
graph: Graph,
|
|
336
|
-
node: NodeId,
|
|
337
|
-
visited: Set<NodeId>,
|
|
338
|
-
component: NodeId[],
|
|
339
|
-
): void {
|
|
356
|
+
function dfsCollectComponent(graph: Graph, node: NodeId, visited: Set<NodeId>, component: NodeId[]): void {
|
|
340
357
|
visited.add(node);
|
|
341
358
|
component.push(node);
|
|
342
359
|
|
|
@@ -349,9 +366,11 @@ function dfsCollectComponent(
|
|
|
349
366
|
|
|
350
367
|
/**
|
|
351
368
|
* Create transpose (reverse) of a directed graph
|
|
369
|
+
* @param graph - The directed graph to transpose
|
|
370
|
+
* @returns A new graph with all edges reversed
|
|
352
371
|
*/
|
|
353
372
|
function createTransposeGraph(graph: Graph): Graph {
|
|
354
|
-
const transpose = new Graph({directed: true});
|
|
373
|
+
const transpose = new Graph({ directed: true });
|
|
355
374
|
|
|
356
375
|
// Add all nodes
|
|
357
376
|
for (const node of Array.from(graph.nodes())) {
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Graph traversal algorithms
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export {breadthFirstSearch, isBipartite, shortestPathBFS, singleSourceShortestPathBFS} from "./bfs.js";
|
|
6
|
-
export type {DFSOptions} from "./dfs.js";
|
|
7
|
-
export {depthFirstSearch, findStronglyConnectedComponents, hasCycleDFS, topologicalSort} from "./dfs.js";
|
|
5
|
+
export { breadthFirstSearch, isBipartite, shortestPathBFS, singleSourceShortestPathBFS } from "./bfs.js";
|
|
6
|
+
export type { DFSOptions } from "./dfs.js";
|
|
7
|
+
export { depthFirstSearch, findStronglyConnectedComponents, hasCycleDFS, topologicalSort } from "./dfs.js";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as algorithms from "./algorithms/index.js";
|
|
11
|
-
import {Graph} from "./core/graph.js";
|
|
11
|
+
import { Graph } from "./core/graph.js";
|
|
12
12
|
|
|
13
13
|
interface BenchmarkResult {
|
|
14
14
|
algorithm: string;
|
|
@@ -35,8 +35,8 @@ const ALGORITHM_CATEGORIES = {
|
|
|
35
35
|
],
|
|
36
36
|
config: {
|
|
37
37
|
name: "Traversal",
|
|
38
|
-
quick: {nodes: 1000, edges: 5000, runs: 3},
|
|
39
|
-
full: {nodes: 10000, edges: 50000, runs: 10},
|
|
38
|
+
quick: { nodes: 1000, edges: 5000, runs: 3 },
|
|
39
|
+
full: { nodes: 10000, edges: 50000, runs: 10 },
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
shortestPath: {
|
|
@@ -54,8 +54,8 @@ const ALGORITHM_CATEGORIES = {
|
|
|
54
54
|
],
|
|
55
55
|
config: {
|
|
56
56
|
name: "Shortest Path",
|
|
57
|
-
quick: {nodes: 100, edges: 500, runs: 1}, // Smaller for O(V³) algorithms
|
|
58
|
-
full: {nodes: 500, edges: 2500, runs: 3},
|
|
57
|
+
quick: { nodes: 100, edges: 500, runs: 1 }, // Smaller for O(V³) algorithms
|
|
58
|
+
full: { nodes: 500, edges: 2500, runs: 3 },
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
centrality: {
|
|
@@ -71,8 +71,8 @@ const ALGORITHM_CATEGORIES = {
|
|
|
71
71
|
],
|
|
72
72
|
config: {
|
|
73
73
|
name: "Centrality",
|
|
74
|
-
quick: {nodes: 500, edges: 2500, runs: 1},
|
|
75
|
-
full: {nodes: 2000, edges: 10000, runs: 3},
|
|
74
|
+
quick: { nodes: 500, edges: 2500, runs: 1 },
|
|
75
|
+
full: { nodes: 2000, edges: 10000, runs: 3 },
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
78
|
components: {
|
|
@@ -90,44 +90,48 @@ const ALGORITHM_CATEGORIES = {
|
|
|
90
90
|
],
|
|
91
91
|
config: {
|
|
92
92
|
name: "Components",
|
|
93
|
-
quick: {nodes: 2000, edges: 10000, runs: 3},
|
|
94
|
-
full: {nodes: 10000, edges: 50000, runs: 10},
|
|
93
|
+
quick: { nodes: 2000, edges: 10000, runs: 3 },
|
|
94
|
+
full: { nodes: 10000, edges: 50000, runs: 10 },
|
|
95
95
|
},
|
|
96
96
|
},
|
|
97
97
|
mst: {
|
|
98
98
|
algorithms: ["kruskalMST", "primMST", "minimumSpanningTree"],
|
|
99
99
|
config: {
|
|
100
100
|
name: "Minimum Spanning Tree",
|
|
101
|
-
quick: {nodes: 1000, edges: 5000, runs: 5},
|
|
102
|
-
full: {nodes: 5000, edges: 25000, runs: 10},
|
|
101
|
+
quick: { nodes: 1000, edges: 5000, runs: 5 },
|
|
102
|
+
full: { nodes: 5000, edges: 25000, runs: 10 },
|
|
103
103
|
},
|
|
104
104
|
},
|
|
105
105
|
community: {
|
|
106
106
|
algorithms: ["girvanNewman", "labelPropagation", "leiden", "louvain"],
|
|
107
107
|
config: {
|
|
108
108
|
name: "Community Detection",
|
|
109
|
-
quick: {nodes: 200, edges: 1000, runs: 1}, // Very expensive algorithms
|
|
110
|
-
full: {nodes: 1000, edges: 5000, runs: 2},
|
|
109
|
+
quick: { nodes: 200, edges: 1000, runs: 1 }, // Very expensive algorithms
|
|
110
|
+
full: { nodes: 1000, edges: 5000, runs: 2 },
|
|
111
111
|
},
|
|
112
112
|
},
|
|
113
113
|
matching: {
|
|
114
114
|
algorithms: ["bipartitePartition", "greedyBipartiteMatching", "maximumBipartiteMatching"],
|
|
115
115
|
config: {
|
|
116
116
|
name: "Matching",
|
|
117
|
-
quick: {nodes: 500, edges: 1000, runs: 3},
|
|
118
|
-
full: {nodes: 2000, edges: 4000, runs: 5},
|
|
117
|
+
quick: { nodes: 500, edges: 1000, runs: 3 },
|
|
118
|
+
full: { nodes: 2000, edges: 4000, runs: 5 },
|
|
119
119
|
},
|
|
120
120
|
},
|
|
121
121
|
} as const;
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
124
|
* Main benchmark runner
|
|
125
|
+
* @param mode - The benchmark mode: "quick" for fast tests or "full" for comprehensive tests
|
|
126
|
+
* @returns Array of benchmark results for all tested algorithms
|
|
125
127
|
*/
|
|
126
128
|
export function runAllAlgorithmsBenchmark(mode: "quick" | "full" = "full"): BenchmarkResult[] {
|
|
127
129
|
const startTime = Date.now();
|
|
128
130
|
const timeLimit = mode === "quick" ? 2 * 60 * 1000 : 60 * 60 * 1000; // 2 min or 1 hour
|
|
129
131
|
|
|
130
|
-
console.log(
|
|
132
|
+
console.log(
|
|
133
|
+
`🚀 ${mode === "quick" ? "Quick" : "Comprehensive"} Benchmark - All ${String(getTotalAlgorithmCount())} Algorithms`,
|
|
134
|
+
);
|
|
131
135
|
console.log(`=${"=".repeat(60)}`);
|
|
132
136
|
console.log(`Time limit: ${mode === "quick" ? "2 minutes" : "1 hour"}`);
|
|
133
137
|
console.log(`Start time: ${new Date().toLocaleTimeString()}\n`);
|
|
@@ -175,7 +179,9 @@ export function runAllAlgorithmsBenchmark(mode: "quick" | "full" = "full"): Benc
|
|
|
175
179
|
console.log("===================");
|
|
176
180
|
console.log(`Total time: ${totalTime.toFixed(1)}s`);
|
|
177
181
|
console.log(`Algorithms tested: ${String(totalTested)}`);
|
|
178
|
-
console.log(
|
|
182
|
+
console.log(
|
|
183
|
+
`Success rate: ${String(totalPassed)}/${String(totalTested)} (${((totalPassed / totalTested) * 100).toFixed(1)}%)`,
|
|
184
|
+
);
|
|
179
185
|
|
|
180
186
|
if (mode === "full") {
|
|
181
187
|
displayDetailedResults(results);
|
|
@@ -186,6 +192,12 @@ export function runAllAlgorithmsBenchmark(mode: "quick" | "full" = "full"): Benc
|
|
|
186
192
|
|
|
187
193
|
/**
|
|
188
194
|
* Benchmark a single algorithm
|
|
195
|
+
* @param algoName - The name of the algorithm to benchmark
|
|
196
|
+
* @param category - The category the algorithm belongs to
|
|
197
|
+
* @param graph - The test graph to run the algorithm on
|
|
198
|
+
* @param runs - Number of times to run the algorithm for averaging
|
|
199
|
+
* @param mode - The benchmark mode (quick or full)
|
|
200
|
+
* @returns The benchmark result containing timing and success information
|
|
189
201
|
*/
|
|
190
202
|
function benchmarkAlgorithm(
|
|
191
203
|
algoName: string,
|
|
@@ -216,7 +228,7 @@ function benchmarkAlgorithm(
|
|
|
216
228
|
const args = getAlgorithmArgs(algoName, graph);
|
|
217
229
|
const start = performance.now();
|
|
218
230
|
|
|
219
|
-
(algo as (...
|
|
231
|
+
(algo as (...args: unknown[]) => unknown)(...args);
|
|
220
232
|
|
|
221
233
|
const time = performance.now() - start;
|
|
222
234
|
times.push(time);
|
|
@@ -251,6 +263,9 @@ function benchmarkAlgorithm(
|
|
|
251
263
|
|
|
252
264
|
/**
|
|
253
265
|
* Get appropriate arguments for each algorithm
|
|
266
|
+
* @param algoName - The name of the algorithm to get arguments for
|
|
267
|
+
* @param graph - The base graph to use for generating arguments
|
|
268
|
+
* @returns Array of arguments to pass to the algorithm function
|
|
254
269
|
*/
|
|
255
270
|
function getAlgorithmArgs(algoName: string, graph: Graph): unknown[] {
|
|
256
271
|
// Get random nodes for algorithms that need them
|
|
@@ -326,6 +341,10 @@ function getAlgorithmArgs(algoName: string, graph: Graph): unknown[] {
|
|
|
326
341
|
|
|
327
342
|
/**
|
|
328
343
|
* Create test graph appropriate for each algorithm category
|
|
344
|
+
* @param nodes - Number of nodes in the test graph
|
|
345
|
+
* @param edges - Target number of edges in the test graph
|
|
346
|
+
* @param category - The algorithm category to optimize the graph structure for
|
|
347
|
+
* @returns A new test graph with the specified structure
|
|
329
348
|
*/
|
|
330
349
|
function createTestGraph(nodes: number, edges: number, category: string): Graph {
|
|
331
350
|
const graph = new Graph();
|
|
@@ -362,6 +381,8 @@ function createTestGraph(nodes: number, edges: number, category: string): Graph
|
|
|
362
381
|
|
|
363
382
|
/**
|
|
364
383
|
* Create a connected random graph
|
|
384
|
+
* @param graph - The graph to add edges to
|
|
385
|
+
* @param targetEdges - Target number of edges to create
|
|
365
386
|
*/
|
|
366
387
|
function createConnectedGraph(graph: Graph, targetEdges: number): void {
|
|
367
388
|
const nodes = graph.nodeCount;
|
|
@@ -387,6 +408,8 @@ function createConnectedGraph(graph: Graph, targetEdges: number): void {
|
|
|
387
408
|
|
|
388
409
|
/**
|
|
389
410
|
* Create small-world graph (Watts-Strogatz model)
|
|
411
|
+
* @param graph - The graph to add edges to
|
|
412
|
+
* @param avgDegree - Target average degree for each node
|
|
390
413
|
*/
|
|
391
414
|
function createSmallWorldGraph(graph: Graph, avgDegree: number): void {
|
|
392
415
|
const nodes = graph.nodeCount;
|
|
@@ -418,6 +441,8 @@ function createSmallWorldGraph(graph: Graph, avgDegree: number): void {
|
|
|
418
441
|
|
|
419
442
|
/**
|
|
420
443
|
* Create graph with community structure
|
|
444
|
+
* @param graph - The graph to add edges to
|
|
445
|
+
* @param targetEdges - Target number of edges to create
|
|
421
446
|
*/
|
|
422
447
|
function createCommunityGraph(graph: Graph, targetEdges: number): void {
|
|
423
448
|
const nodes = graph.nodeCount;
|
|
@@ -433,7 +458,8 @@ function createCommunityGraph(graph: Graph, targetEdges: number): void {
|
|
|
433
458
|
|
|
434
459
|
for (let i = start; i < end && edgesAdded < targetEdges; i++) {
|
|
435
460
|
for (let j = i + 1; j < end && edgesAdded < targetEdges; j++) {
|
|
436
|
-
if (Math.random() < 0.8) {
|
|
461
|
+
if (Math.random() < 0.8) {
|
|
462
|
+
// High probability within community
|
|
437
463
|
graph.addEdge(i, j);
|
|
438
464
|
edgesAdded++;
|
|
439
465
|
}
|
|
@@ -455,9 +481,11 @@ function createCommunityGraph(graph: Graph, targetEdges: number): void {
|
|
|
455
481
|
|
|
456
482
|
/**
|
|
457
483
|
* Create directed version of graph
|
|
484
|
+
* @param undirectedGraph - The undirected graph to convert
|
|
485
|
+
* @returns A new directed graph with the same nodes and edges
|
|
458
486
|
*/
|
|
459
487
|
function createDirectedGraph(undirectedGraph: Graph): Graph {
|
|
460
|
-
const directedGraph = new Graph({directed: true});
|
|
488
|
+
const directedGraph = new Graph({ directed: true });
|
|
461
489
|
|
|
462
490
|
// Copy nodes
|
|
463
491
|
for (const node of undirectedGraph.nodes()) {
|
|
@@ -474,6 +502,8 @@ function createDirectedGraph(undirectedGraph: Graph): Graph {
|
|
|
474
502
|
|
|
475
503
|
/**
|
|
476
504
|
* Create bipartite graph
|
|
505
|
+
* @param nodesPerPartition - Number of nodes in each partition
|
|
506
|
+
* @returns A new bipartite graph with sparse connections between partitions
|
|
477
507
|
*/
|
|
478
508
|
function createBipartiteGraph(nodesPerPartition: number): Graph {
|
|
479
509
|
const graph = new Graph();
|
|
@@ -486,7 +516,8 @@ function createBipartiteGraph(nodesPerPartition: number): Graph {
|
|
|
486
516
|
// Add edges only between partitions
|
|
487
517
|
for (let i = 0; i < nodesPerPartition; i++) {
|
|
488
518
|
for (let j = nodesPerPartition; j < nodesPerPartition * 2; j++) {
|
|
489
|
-
if (Math.random() < 0.3) {
|
|
519
|
+
if (Math.random() < 0.3) {
|
|
520
|
+
// Sparse bipartite graph
|
|
490
521
|
graph.addEdge(i, j);
|
|
491
522
|
}
|
|
492
523
|
}
|
|
@@ -497,14 +528,15 @@ function createBipartiteGraph(nodesPerPartition: number): Graph {
|
|
|
497
528
|
|
|
498
529
|
/**
|
|
499
530
|
* Get total algorithm count
|
|
531
|
+
* @returns The total number of algorithms across all categories
|
|
500
532
|
*/
|
|
501
533
|
function getTotalAlgorithmCount(): number {
|
|
502
|
-
return Object.values(ALGORITHM_CATEGORIES).reduce((total, category) =>
|
|
503
|
-
total + category.algorithms.length, 0);
|
|
534
|
+
return Object.values(ALGORITHM_CATEGORIES).reduce((total, category) => total + category.algorithms.length, 0);
|
|
504
535
|
}
|
|
505
536
|
|
|
506
537
|
/**
|
|
507
538
|
* Display detailed results for full benchmark
|
|
539
|
+
* @param results - Array of benchmark results to display
|
|
508
540
|
*/
|
|
509
541
|
function displayDetailedResults(results: BenchmarkResult[]): void {
|
|
510
542
|
console.log("\n📊 Detailed Results");
|
|
@@ -530,8 +562,8 @@ function displayDetailedResults(results: BenchmarkResult[]): void {
|
|
|
530
562
|
if (successful.length > 0) {
|
|
531
563
|
const avgTime = successful.reduce((sum, r) => sum + r.executionTime, 0) / successful.length;
|
|
532
564
|
console.log(` Average time: ${avgTime.toFixed(2)}ms`);
|
|
533
|
-
const minTime = Math.min(...
|
|
534
|
-
const maxTime = Math.max(...
|
|
565
|
+
const minTime = Math.min(...successful.map((r) => r.executionTime));
|
|
566
|
+
const maxTime = Math.max(...successful.map((r) => r.executionTime));
|
|
535
567
|
console.log(` Range: ${minTime.toFixed(2)}ms - ${maxTime.toFixed(2)}ms`);
|
|
536
568
|
}
|
|
537
569
|
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* structure in graphs.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {Graph} from "../core/graph.js";
|
|
9
|
+
import type { Graph } from "../core/graph.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Convert Graph to adjacency set representation for clustering
|
|
13
|
+
* @param graph - The input graph to convert
|
|
14
|
+
* @returns Map of node IDs to sets of neighbor node IDs
|
|
13
15
|
*/
|
|
14
16
|
function graphToAdjacencySet(graph: Graph): Map<string, Set<string>> {
|
|
15
17
|
const adjacency = new Map<string, Set<string>>();
|
|
@@ -62,11 +64,10 @@ export type LinkageMethod = "single" | "complete" | "average" | "ward";
|
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
66
|
* Compute distance between two nodes based on graph structure
|
|
67
|
+
* @param graph - Adjacency map representation of the graph
|
|
68
|
+
* @param distances - Map to store computed distances between nodes
|
|
65
69
|
*/
|
|
66
|
-
function computeGraphDistance<T>(
|
|
67
|
-
graph: Map<T, Set<T>>,
|
|
68
|
-
distances: Map<T, Map<T, number>>,
|
|
69
|
-
): void {
|
|
70
|
+
function computeGraphDistance<T>(graph: Map<T, Set<T>>, distances: Map<T, Map<T, number>>): void {
|
|
70
71
|
// Use BFS to compute shortest path distances
|
|
71
72
|
for (const start of graph.keys()) {
|
|
72
73
|
const dist = new Map<T, number>();
|
|
@@ -97,6 +98,11 @@ function computeGraphDistance<T>(
|
|
|
97
98
|
|
|
98
99
|
/**
|
|
99
100
|
* Compute distance between two clusters based on linkage method
|
|
101
|
+
* @param cluster1 - First cluster node
|
|
102
|
+
* @param cluster2 - Second cluster node
|
|
103
|
+
* @param distances - Precomputed distance matrix between nodes
|
|
104
|
+
* @param linkage - Linkage method to use for computing cluster distance
|
|
105
|
+
* @returns The distance between the two clusters
|
|
100
106
|
*/
|
|
101
107
|
function clusterDistance<T>(
|
|
102
108
|
cluster1: ClusterNode<T>,
|
|
@@ -126,9 +132,9 @@ function clusterDistance<T>(
|
|
|
126
132
|
|
|
127
133
|
switch (linkage) {
|
|
128
134
|
case "single":
|
|
129
|
-
return Math.min(...
|
|
135
|
+
return Math.min(...allDistances);
|
|
130
136
|
case "complete":
|
|
131
|
-
return Math.max(...
|
|
137
|
+
return Math.max(...allDistances);
|
|
132
138
|
case "average":
|
|
133
139
|
return allDistances.reduce((a, b) => a + b, 0) / allDistances.length;
|
|
134
140
|
case "ward": {
|
|
@@ -139,12 +145,15 @@ function clusterDistance<T>(
|
|
|
139
145
|
return avgDist * ((size1 * size2) / (size1 + size2));
|
|
140
146
|
}
|
|
141
147
|
default:
|
|
142
|
-
return Math.min(...
|
|
148
|
+
return Math.min(...allDistances);
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
/**
|
|
147
153
|
* Internal implementation of agglomerative hierarchical clustering
|
|
154
|
+
* @param graph - Adjacency map representation of the graph
|
|
155
|
+
* @param linkage - Linkage method for cluster distance computation
|
|
156
|
+
* @returns Hierarchical clustering result with dendrogram and cluster assignments
|
|
148
157
|
*/
|
|
149
158
|
function hierarchicalClusteringImpl<T>(
|
|
150
159
|
graph: Map<T, Set<T>>,
|
|
@@ -155,7 +164,7 @@ function hierarchicalClusteringImpl<T>(
|
|
|
155
164
|
|
|
156
165
|
if (n === 0) {
|
|
157
166
|
return {
|
|
158
|
-
root: {id: "empty", members: new Set(), distance: 0, height: 0},
|
|
167
|
+
root: { id: "empty", members: new Set(), distance: 0, height: 0 },
|
|
159
168
|
dendrogram: [],
|
|
160
169
|
clusters: new Map(),
|
|
161
170
|
};
|
|
@@ -173,7 +182,7 @@ function hierarchicalClusteringImpl<T>(
|
|
|
173
182
|
height: 0,
|
|
174
183
|
}));
|
|
175
184
|
|
|
176
|
-
const dendrogram: ClusterNode<T>[] = [...
|
|
185
|
+
const dendrogram: ClusterNode<T>[] = [...clusters];
|
|
177
186
|
let clusterCount = n;
|
|
178
187
|
|
|
179
188
|
// Distance matrix between clusters
|
|
@@ -220,9 +229,8 @@ function hierarchicalClusteringImpl<T>(
|
|
|
220
229
|
continue;
|
|
221
230
|
}
|
|
222
231
|
|
|
223
|
-
const dist =
|
|
224
|
-
clusterDistances.get(cluster2.id)?.get(cluster1.id) ??
|
|
225
|
-
Infinity;
|
|
232
|
+
const dist =
|
|
233
|
+
distMap.get(cluster2.id) ?? clusterDistances.get(cluster2.id)?.get(cluster1.id) ?? Infinity;
|
|
226
234
|
|
|
227
235
|
if (dist < minDist) {
|
|
228
236
|
minDist = dist;
|
|
@@ -239,7 +247,7 @@ function hierarchicalClusteringImpl<T>(
|
|
|
239
247
|
id: `forest-${String(clusterCount++)}`,
|
|
240
248
|
members: new Set(nodes),
|
|
241
249
|
distance: Infinity,
|
|
242
|
-
height: Math.max(...
|
|
250
|
+
height: Math.max(...trees.map((t) => t.height)) + 1,
|
|
243
251
|
trees,
|
|
244
252
|
};
|
|
245
253
|
dendrogram.push(forestRoot);
|
|
@@ -251,7 +259,7 @@ function hierarchicalClusteringImpl<T>(
|
|
|
251
259
|
// Create new cluster
|
|
252
260
|
const newCluster: ClusterNode<T> = {
|
|
253
261
|
id: `cluster-${String(clusterCount++)}`,
|
|
254
|
-
members: new Set([...
|
|
262
|
+
members: new Set([...mergeCluster1.members, ...mergeCluster2.members]),
|
|
255
263
|
left: mergeCluster1,
|
|
256
264
|
right: mergeCluster2,
|
|
257
265
|
distance: minDist,
|
|
@@ -301,7 +309,7 @@ function hierarchicalClusteringImpl<T>(
|
|
|
301
309
|
|
|
302
310
|
if (!root) {
|
|
303
311
|
return {
|
|
304
|
-
root: {id: "empty", members: new Set(), distance: 0, height: 0},
|
|
312
|
+
root: { id: "empty", members: new Set(), distance: 0, height: 0 },
|
|
305
313
|
dendrogram: [],
|
|
306
314
|
clusters: new Map(),
|
|
307
315
|
};
|
|
@@ -320,11 +328,11 @@ function hierarchicalClusteringImpl<T>(
|
|
|
320
328
|
|
|
321
329
|
/**
|
|
322
330
|
* Cut dendrogram at specific height to get clusters
|
|
331
|
+
* @param root - Root node of the dendrogram
|
|
332
|
+
* @param height - Height at which to cut the dendrogram
|
|
333
|
+
* @returns Array of sets representing clusters at the specified height
|
|
323
334
|
*/
|
|
324
|
-
export function cutDendrogram<T>(
|
|
325
|
-
root: ClusterNode<T>,
|
|
326
|
-
height: number,
|
|
327
|
-
): Set<T>[] {
|
|
335
|
+
export function cutDendrogram<T>(root: ClusterNode<T>, height: number): Set<T>[] {
|
|
328
336
|
const clusters: Set<T>[] = [];
|
|
329
337
|
|
|
330
338
|
function traverse(node: ClusterNode<T>): void {
|
|
@@ -354,11 +362,11 @@ export function cutDendrogram<T>(
|
|
|
354
362
|
|
|
355
363
|
/**
|
|
356
364
|
* Cut dendrogram to get exactly k clusters
|
|
365
|
+
* @param root - Root node of the dendrogram
|
|
366
|
+
* @param k - Target number of clusters
|
|
367
|
+
* @returns Array of sets representing exactly k clusters
|
|
357
368
|
*/
|
|
358
|
-
export function cutDendrogramKClusters<T>(
|
|
359
|
-
root: ClusterNode<T>,
|
|
360
|
-
k: number,
|
|
361
|
-
): Set<T>[] {
|
|
369
|
+
export function cutDendrogramKClusters<T>(root: ClusterNode<T>, k: number): Set<T>[] {
|
|
362
370
|
if (k <= 0) {
|
|
363
371
|
return [];
|
|
364
372
|
}
|
|
@@ -390,17 +398,17 @@ export function cutDendrogramKClusters<T>(
|
|
|
390
398
|
/**
|
|
391
399
|
* Compute modularity-based hierarchical clustering
|
|
392
400
|
* Uses modularity gain to decide merges
|
|
401
|
+
* @param graph - The input graph to cluster
|
|
402
|
+
* @returns Hierarchical clustering result with dendrogram and cluster assignments
|
|
393
403
|
*/
|
|
394
|
-
export function modularityHierarchicalClustering(
|
|
395
|
-
graph: Graph,
|
|
396
|
-
): HierarchicalClusteringResult<string> {
|
|
404
|
+
export function modularityHierarchicalClustering(graph: Graph): HierarchicalClusteringResult<string> {
|
|
397
405
|
const adjacencySet = graphToAdjacencySet(graph);
|
|
398
406
|
const nodes = Array.from(adjacencySet.keys());
|
|
399
407
|
const n = nodes.length;
|
|
400
408
|
|
|
401
409
|
if (n === 0) {
|
|
402
410
|
return {
|
|
403
|
-
root: {id: "empty", members: new Set(), distance: 0, height: 0},
|
|
411
|
+
root: { id: "empty", members: new Set(), distance: 0, height: 0 },
|
|
404
412
|
dendrogram: [],
|
|
405
413
|
clusters: new Map(),
|
|
406
414
|
};
|
|
@@ -426,7 +434,7 @@ export function modularityHierarchicalClustering(
|
|
|
426
434
|
height: 0,
|
|
427
435
|
}));
|
|
428
436
|
|
|
429
|
-
const dendrogram: ClusterNode<string>[] = [...
|
|
437
|
+
const dendrogram: ClusterNode<string>[] = [...clusters];
|
|
430
438
|
let clusterCount = n;
|
|
431
439
|
|
|
432
440
|
// Active clusters
|
|
@@ -466,8 +474,7 @@ export function modularityHierarchicalClustering(
|
|
|
466
474
|
}
|
|
467
475
|
}
|
|
468
476
|
|
|
469
|
-
const gain =
|
|
470
|
-
(degreeProduct / (4 * totalEdges * totalEdges));
|
|
477
|
+
const gain = edgesBetween / totalEdges - degreeProduct / (4 * totalEdges * totalEdges);
|
|
471
478
|
|
|
472
479
|
if (gain > maxGain) {
|
|
473
480
|
maxGain = gain;
|
|
@@ -484,7 +491,7 @@ export function modularityHierarchicalClustering(
|
|
|
484
491
|
// Create new cluster
|
|
485
492
|
const newCluster: ClusterNode<string> = {
|
|
486
493
|
id: `cluster-${String(clusterCount++)}`,
|
|
487
|
-
members: new Set([...
|
|
494
|
+
members: new Set([...mergeCluster1.members, ...mergeCluster2.members]),
|
|
488
495
|
left: mergeCluster1,
|
|
489
496
|
right: mergeCluster2,
|
|
490
497
|
distance: -maxGain, // Use negative gain as distance
|
|
@@ -507,7 +514,7 @@ export function modularityHierarchicalClustering(
|
|
|
507
514
|
|
|
508
515
|
if (!root) {
|
|
509
516
|
return {
|
|
510
|
-
root: {id: "empty", members: new Set(), distance: 0, height: 0},
|
|
517
|
+
root: { id: "empty", members: new Set(), distance: 0, height: 0 },
|
|
511
518
|
dendrogram: [],
|
|
512
519
|
clusters: new Map(),
|
|
513
520
|
};
|
|
@@ -527,7 +534,6 @@ export function modularityHierarchicalClustering(
|
|
|
527
534
|
/**
|
|
528
535
|
* Agglomerative hierarchical clustering
|
|
529
536
|
* Builds clusters bottom-up by merging closest pairs
|
|
530
|
-
*
|
|
531
537
|
* @param graph - Undirected graph - accepts Graph class or Map<T, Set<T>>
|
|
532
538
|
* @param linkage - Linkage method for cluster distance
|
|
533
539
|
* @returns Hierarchical clustering result
|