@graphty/algorithms 2.1.1 → 2.2.0
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 +139 -50
- package/dist/algorithms.js +15260 -10390
- package/dist/algorithms.js.map +1 -1
- package/dist/algorithms.standalone.js +26675 -21542
- package/dist/algorithms.standalone.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +18 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +40 -1
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +20 -0
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +93 -8
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +9 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +18 -0
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +29 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +9 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +23 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +9 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +31 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +13 -13
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +47 -10
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts +27 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js +335 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts +5 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +34 -308
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +9 -6
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +24 -110
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +28 -77
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +59 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +46 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +27 -0
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +15 -5
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +97 -107
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +2 -12
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +47 -308
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +5 -25
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +0 -3
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +0 -4
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +33 -154
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical-legacy.d.ts +56 -0
- package/dist/src/clustering/hierarchical-legacy.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical-legacy.js +441 -0
- package/dist/src/clustering/hierarchical-legacy.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +7 -39
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +56 -417
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/k-core-legacy.d.ts +30 -0
- package/dist/src/clustering/k-core-legacy.d.ts.map +1 -0
- package/dist/src/clustering/k-core-legacy.js +191 -0
- package/dist/src/clustering/k-core-legacy.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +8 -7
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +33 -174
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl-legacy.d.ts +42 -0
- package/dist/src/clustering/mcl-legacy.d.ts.map +1 -0
- package/dist/src/clustering/mcl-legacy.js +386 -0
- package/dist/src/clustering/mcl-legacy.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +9 -32
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +48 -374
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/index.d.ts +29 -3
- 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/indexed/accelerator.d.ts +141 -11
- package/dist/src/indexed/accelerator.d.ts.map +1 -1
- package/dist/src/indexed/accelerator.js +319 -5
- package/dist/src/indexed/accelerator.js.map +1 -1
- package/dist/src/indexed/all-pairs.d.ts +59 -0
- package/dist/src/indexed/all-pairs.d.ts.map +1 -0
- package/dist/src/indexed/all-pairs.js +274 -0
- package/dist/src/indexed/all-pairs.js.map +1 -0
- package/dist/src/indexed/bellman-ford.d.ts +25 -0
- package/dist/src/indexed/bellman-ford.d.ts.map +1 -0
- package/dist/src/indexed/bellman-ford.js +59 -0
- package/dist/src/indexed/bellman-ford.js.map +1 -0
- package/dist/src/indexed/betweenness.d.ts +85 -0
- package/dist/src/indexed/betweenness.d.ts.map +1 -0
- package/dist/src/indexed/betweenness.js +188 -0
- package/dist/src/indexed/betweenness.js.map +1 -0
- package/dist/src/indexed/bfs.d.ts +69 -2
- package/dist/src/indexed/bfs.d.ts.map +1 -1
- package/dist/src/indexed/bfs.js +148 -1
- package/dist/src/indexed/bfs.js.map +1 -1
- package/dist/src/indexed/bipartite.d.ts +37 -0
- package/dist/src/indexed/bipartite.d.ts.map +1 -0
- package/dist/src/indexed/bipartite.js +54 -0
- package/dist/src/indexed/bipartite.js.map +1 -0
- package/dist/src/indexed/closeness.d.ts +49 -0
- package/dist/src/indexed/closeness.d.ts.map +1 -0
- package/dist/src/indexed/closeness.js +127 -0
- package/dist/src/indexed/closeness.js.map +1 -0
- package/dist/src/indexed/common-neighbors.d.ts +14 -1
- package/dist/src/indexed/common-neighbors.d.ts.map +1 -1
- package/dist/src/indexed/common-neighbors.js +24 -14
- package/dist/src/indexed/common-neighbors.js.map +1 -1
- package/dist/src/indexed/degree.d.ts +19 -0
- package/dist/src/indexed/degree.d.ts.map +1 -0
- package/dist/src/indexed/degree.js +45 -0
- package/dist/src/indexed/degree.js.map +1 -0
- package/dist/src/indexed/delta-pagerank.d.ts +145 -0
- package/dist/src/indexed/delta-pagerank.d.ts.map +1 -0
- package/dist/src/indexed/delta-pagerank.js +428 -0
- package/dist/src/indexed/delta-pagerank.js.map +1 -0
- package/dist/src/indexed/dfs.d.ts +55 -0
- package/dist/src/indexed/dfs.d.ts.map +1 -0
- package/dist/src/indexed/dfs.js +146 -0
- package/dist/src/indexed/dfs.js.map +1 -0
- package/dist/src/indexed/eigenvector.d.ts +48 -0
- package/dist/src/indexed/eigenvector.d.ts.map +1 -0
- package/dist/src/indexed/eigenvector.js +162 -0
- package/dist/src/indexed/eigenvector.js.map +1 -0
- package/dist/src/indexed/facade.d.ts +109 -0
- package/dist/src/indexed/facade.d.ts.map +1 -0
- package/dist/src/indexed/facade.js +204 -0
- package/dist/src/indexed/facade.js.map +1 -0
- package/dist/src/indexed/flow.d.ts +120 -0
- package/dist/src/indexed/flow.d.ts.map +1 -0
- package/dist/src/indexed/flow.js +377 -0
- package/dist/src/indexed/flow.js.map +1 -0
- package/dist/src/indexed/girvan-newman.d.ts +42 -0
- package/dist/src/indexed/girvan-newman.d.ts.map +1 -0
- package/dist/src/indexed/girvan-newman.js +90 -0
- package/dist/src/indexed/girvan-newman.js.map +1 -0
- package/dist/src/indexed/grsbm.d.ts +85 -0
- package/dist/src/indexed/grsbm.d.ts.map +1 -0
- package/dist/src/indexed/grsbm.js +287 -0
- package/dist/src/indexed/grsbm.js.map +1 -0
- package/dist/src/indexed/hierarchical.d.ts +69 -0
- package/dist/src/indexed/hierarchical.d.ts.map +1 -0
- package/dist/src/indexed/hierarchical.js +245 -0
- package/dist/src/indexed/hierarchical.js.map +1 -0
- package/dist/src/indexed/index.d.ts +29 -3
- package/dist/src/indexed/index.d.ts.map +1 -1
- package/dist/src/indexed/index.js +29 -3
- package/dist/src/indexed/index.js.map +1 -1
- package/dist/src/indexed/isomorphism.d.ts +42 -0
- package/dist/src/indexed/isomorphism.d.ts.map +1 -0
- package/dist/src/indexed/isomorphism.js +269 -0
- package/dist/src/indexed/isomorphism.js.map +1 -0
- package/dist/src/indexed/label-propagation.d.ts +134 -0
- package/dist/src/indexed/label-propagation.d.ts.map +1 -0
- package/dist/src/indexed/label-propagation.js +486 -0
- package/dist/src/indexed/label-propagation.js.map +1 -0
- package/dist/src/indexed/leiden.d.ts +52 -0
- package/dist/src/indexed/leiden.d.ts.map +1 -0
- package/dist/src/indexed/leiden.js +384 -0
- package/dist/src/indexed/leiden.js.map +1 -0
- package/dist/src/indexed/link-prediction.d.ts +147 -0
- package/dist/src/indexed/link-prediction.d.ts.map +1 -0
- package/dist/src/indexed/link-prediction.js +317 -0
- package/dist/src/indexed/link-prediction.js.map +1 -0
- package/dist/src/indexed/markov.d.ts +54 -0
- package/dist/src/indexed/markov.d.ts.map +1 -0
- package/dist/src/indexed/markov.js +284 -0
- package/dist/src/indexed/markov.js.map +1 -0
- package/dist/src/indexed/matching.d.ts +53 -0
- package/dist/src/indexed/matching.d.ts.map +1 -0
- package/dist/src/indexed/matching.js +139 -0
- package/dist/src/indexed/matching.js.map +1 -0
- package/dist/src/indexed/min-cut.d.ts +48 -0
- package/dist/src/indexed/min-cut.d.ts.map +1 -0
- package/dist/src/indexed/min-cut.js +175 -0
- package/dist/src/indexed/min-cut.js.map +1 -0
- package/dist/src/indexed/modularity.d.ts +34 -0
- package/dist/src/indexed/modularity.d.ts.map +1 -0
- package/dist/src/indexed/modularity.js +74 -0
- package/dist/src/indexed/modularity.js.map +1 -0
- package/dist/src/indexed/mst.d.ts +29 -1
- package/dist/src/indexed/mst.d.ts.map +1 -1
- package/dist/src/indexed/mst.js +62 -0
- package/dist/src/indexed/mst.js.map +1 -1
- package/dist/src/indexed/pagerank.d.ts +29 -6
- package/dist/src/indexed/pagerank.d.ts.map +1 -1
- package/dist/src/indexed/pagerank.js +107 -11
- package/dist/src/indexed/pagerank.js.map +1 -1
- package/dist/src/indexed/point-to-point.d.ts +53 -0
- package/dist/src/indexed/point-to-point.d.ts.map +1 -0
- package/dist/src/indexed/point-to-point.js +173 -0
- package/dist/src/indexed/point-to-point.js.map +1 -0
- package/dist/src/indexed/scc.d.ts +53 -0
- package/dist/src/indexed/scc.d.ts.map +1 -0
- package/dist/src/indexed/scc.js +110 -0
- package/dist/src/indexed/scc.js.map +1 -0
- package/dist/src/indexed/spectral.d.ts +61 -0
- package/dist/src/indexed/spectral.d.ts.map +1 -0
- package/dist/src/indexed/spectral.js +476 -0
- package/dist/src/indexed/spectral.js.map +1 -0
- package/dist/src/indexed/structures/bit-set.d.ts +45 -0
- package/dist/src/indexed/structures/bit-set.d.ts.map +1 -0
- package/dist/src/indexed/structures/bit-set.js +57 -0
- package/dist/src/indexed/structures/bit-set.js.map +1 -0
- package/dist/src/indexed/structures/max-heap.d.ts +52 -0
- package/dist/src/indexed/structures/max-heap.d.ts.map +1 -0
- package/dist/src/indexed/structures/max-heap.js +66 -0
- package/dist/src/indexed/structures/max-heap.js.map +1 -0
- package/dist/src/indexed/structures/min-heap.d.ts +30 -1
- package/dist/src/indexed/structures/min-heap.d.ts.map +1 -1
- package/dist/src/indexed/structures/min-heap.js +46 -6
- package/dist/src/indexed/structures/min-heap.js.map +1 -1
- package/dist/src/indexed/structures/ring-queue.d.ts +41 -0
- package/dist/src/indexed/structures/ring-queue.d.ts.map +1 -0
- package/dist/src/indexed/structures/ring-queue.js +64 -0
- package/dist/src/indexed/structures/ring-queue.js.map +1 -0
- package/dist/src/indexed/sync.d.ts +50 -0
- package/dist/src/indexed/sync.d.ts.map +1 -0
- package/dist/src/indexed/sync.js +175 -0
- package/dist/src/indexed/sync.js.map +1 -0
- package/dist/src/indexed/terahac.d.ts +58 -0
- package/dist/src/indexed/terahac.d.ts.map +1 -0
- package/dist/src/indexed/terahac.js +244 -0
- package/dist/src/indexed/terahac.js.map +1 -0
- package/dist/src/indexed/to-snapshot.d.ts +45 -2
- package/dist/src/indexed/to-snapshot.d.ts.map +1 -1
- package/dist/src/indexed/to-snapshot.js +140 -6
- package/dist/src/indexed/to-snapshot.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts +71 -0
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js +172 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +11 -19
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +56 -123
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +6 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +6 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/research/grsbm-legacy.d.ts +82 -0
- package/dist/src/research/grsbm-legacy.d.ts.map +1 -0
- package/dist/src/research/grsbm-legacy.js +416 -0
- package/dist/src/research/grsbm-legacy.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +9 -66
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +67 -391
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/sync-legacy.d.ts +48 -0
- package/dist/src/research/sync-legacy.d.ts.map +1 -0
- package/dist/src/research/sync-legacy.js +337 -0
- package/dist/src/research/sync-legacy.js.map +1 -0
- package/dist/src/research/sync.d.ts +10 -32
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +28 -316
- package/dist/src/research/sync.js.map +1 -1
- package/package.json +4 -3
- package/src/algorithms/centrality/betweenness.ts +51 -1
- package/src/algorithms/centrality/closeness.ts +112 -12
- package/src/algorithms/centrality/degree.ts +19 -0
- package/src/algorithms/centrality/eigenvector.ts +34 -0
- package/src/algorithms/centrality/hits.ts +24 -0
- package/src/algorithms/centrality/katz.ts +32 -0
- package/src/algorithms/centrality/pagerank.ts +53 -13
- package/src/algorithms/community/girvan-newman-legacy.ts +414 -0
- package/src/algorithms/community/girvan-newman.ts +36 -386
- package/src/algorithms/community/label-propagation.ts +24 -133
- package/src/algorithms/components/connected.ts +28 -95
- package/src/algorithms/mst/kruskal.ts +65 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +49 -0
- package/src/algorithms/shortest-path/dijkstra.ts +29 -0
- package/src/algorithms/shortest-path/floyd-warshall.ts +106 -136
- package/src/algorithms/traversal/bfs-unified.ts +52 -366
- package/src/algorithms/traversal/bfs-variants.ts +5 -28
- package/src/algorithms/traversal/bfs.ts +0 -4
- package/src/algorithms/traversal/dfs.ts +39 -182
- package/src/clustering/hierarchical-legacy.ts +551 -0
- package/src/clustering/hierarchical.ts +64 -520
- package/src/clustering/k-core-legacy.ts +229 -0
- package/src/clustering/k-core.ts +38 -209
- package/src/clustering/mcl-legacy.ts +498 -0
- package/src/clustering/mcl.ts +46 -479
- package/src/index.ts +90 -3
- package/src/indexed/accelerator.ts +502 -16
- package/src/indexed/all-pairs.ts +342 -0
- package/src/indexed/bellman-ford.ts +72 -0
- package/src/indexed/betweenness.ts +258 -0
- package/src/indexed/bfs.ts +189 -3
- package/src/indexed/bipartite.ts +77 -0
- package/src/indexed/closeness.ts +160 -0
- package/src/indexed/common-neighbors.ts +31 -14
- package/src/indexed/degree.ts +55 -0
- package/src/indexed/delta-pagerank.ts +513 -0
- package/src/indexed/dfs.ts +189 -0
- package/src/indexed/eigenvector.ts +202 -0
- package/src/indexed/facade.ts +229 -0
- package/src/indexed/flow.ts +500 -0
- package/src/indexed/girvan-newman.ts +120 -0
- package/src/indexed/grsbm.ts +390 -0
- package/src/indexed/hierarchical.ts +295 -0
- package/src/indexed/index.ts +94 -3
- package/src/indexed/isomorphism.ts +305 -0
- package/src/indexed/label-propagation.ts +582 -0
- package/src/indexed/leiden.ts +436 -0
- package/src/indexed/link-prediction.ts +399 -0
- package/src/indexed/markov.ts +324 -0
- package/src/indexed/matching.ts +192 -0
- package/src/indexed/min-cut.ts +201 -0
- package/src/indexed/modularity.ts +81 -0
- package/src/indexed/mst.ts +79 -1
- package/src/indexed/pagerank.ts +128 -15
- package/src/indexed/point-to-point.ts +220 -0
- package/src/indexed/scc.ts +132 -0
- package/src/indexed/spectral.ts +550 -0
- package/src/indexed/structures/bit-set.ts +65 -0
- package/src/indexed/structures/max-heap.ts +74 -0
- package/src/indexed/structures/min-heap.ts +51 -6
- package/src/indexed/structures/ring-queue.ts +70 -0
- package/src/indexed/sync.ts +229 -0
- package/src/indexed/terahac.ts +290 -0
- package/src/indexed/to-snapshot.ts +156 -6
- package/src/link-prediction/common-neighbors-legacy.ts +252 -0
- package/src/link-prediction/common-neighbors.ts +65 -168
- package/src/optimized/direction-optimized-bfs.ts +6 -1
- package/src/research/grsbm-legacy.ts +586 -0
- package/src/research/grsbm.ts +76 -555
- package/src/research/sync-legacy.ts +456 -0
- package/src/research/sync.ts +30 -431
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
|
-
import {
|
|
2
|
+
import { weaklyConnectedComponents as indexedWeaklyConnectedComponents } from "../../indexed/components.js";
|
|
3
|
+
import { depthFirstSearch } from "../../indexed/dfs.js";
|
|
4
|
+
import { labelsToGroups } from "../../indexed/facade.js";
|
|
5
|
+
import { tarjan } from "../../indexed/scc.js";
|
|
6
|
+
import { legacyArcOrder, toTopologySnapshot } from "../../indexed/to-snapshot.js";
|
|
3
7
|
import type { NodeId } from "../../types/index.js";
|
|
4
8
|
|
|
5
9
|
/**
|
|
@@ -21,20 +25,19 @@ export function connectedComponents(graph: Graph): NodeId[][] {
|
|
|
21
25
|
);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (nodes.length === 0) {
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const unionFind = new UnionFind(nodes);
|
|
31
|
-
|
|
32
|
-
// Union connected nodes
|
|
33
|
-
for (const edge of Array.from(graph.edges())) {
|
|
34
|
-
unionFind.union(edge.source, edge.target);
|
|
35
|
-
}
|
|
28
|
+
return componentGroups(graph);
|
|
29
|
+
}
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The weakly connected components as legacy lists them: one group per component in the order of its
|
|
33
|
+
* first node, members in node order.
|
|
34
|
+
* @param graph - The input graph
|
|
35
|
+
* @returns The components
|
|
36
|
+
*/
|
|
37
|
+
function componentGroups(graph: Graph): NodeId[][] {
|
|
38
|
+
const s = toTopologySnapshot(graph);
|
|
39
|
+
const { labels, count } = indexedWeaklyConnectedComponents(s);
|
|
40
|
+
return labelsToGroups(s.ids, labels, count);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/**
|
|
@@ -127,12 +130,10 @@ export function getConnectedComponent(graph: Graph, nodeId: NodeId): NodeId[] {
|
|
|
127
130
|
throw new Error("Connected components algorithm requires an undirected graph");
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return component;
|
|
133
|
+
// The members in depth-first discovery order, neighbours in insertion order.
|
|
134
|
+
const s = toTopologySnapshot(graph);
|
|
135
|
+
const { order } = depthFirstSearch(s, s.ids.indexOf(nodeId), { arcOrder: legacyArcOrder(graph, s) });
|
|
136
|
+
return Array.from(order, (i) => s.ids.idOf(i));
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
/**
|
|
@@ -145,68 +146,13 @@ export function stronglyConnectedComponents(graph: Graph): NodeId[][] {
|
|
|
145
146
|
throw new Error("Strongly connected components require a directed graph");
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
let index = 0;
|
|
155
|
-
|
|
156
|
-
function tarjanSCC(nodeId: NodeId): void {
|
|
157
|
-
// Set the depth index for this node
|
|
158
|
-
indices.set(nodeId, index);
|
|
159
|
-
lowLinks.set(nodeId, index);
|
|
160
|
-
index++;
|
|
161
|
-
stack.push(nodeId);
|
|
162
|
-
onStack.add(nodeId);
|
|
163
|
-
|
|
164
|
-
// Consider successors
|
|
165
|
-
for (const neighbor of Array.from(graph.neighbors(nodeId))) {
|
|
166
|
-
if (!indices.has(neighbor)) {
|
|
167
|
-
// Successor has not yet been visited; recurse on it
|
|
168
|
-
tarjanSCC(neighbor);
|
|
169
|
-
const nodeLL = lowLinks.get(nodeId) ?? 0;
|
|
170
|
-
const neighborLL = lowLinks.get(neighbor) ?? 0;
|
|
171
|
-
lowLinks.set(nodeId, Math.min(nodeLL, neighborLL));
|
|
172
|
-
} else if (onStack.has(neighbor)) {
|
|
173
|
-
// Successor is in stack and hence in the current SCC
|
|
174
|
-
const nodeLL = lowLinks.get(nodeId) ?? 0;
|
|
175
|
-
const neighborIndex = indices.get(neighbor) ?? 0;
|
|
176
|
-
lowLinks.set(nodeId, Math.min(nodeLL, neighborIndex));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// If nodeId is a root node, pop the stack and create an SCC
|
|
181
|
-
const nodeIndex = indices.get(nodeId) ?? 0;
|
|
182
|
-
const nodeLowLink = lowLinks.get(nodeId) ?? 0;
|
|
183
|
-
|
|
184
|
-
if (nodeLowLink === nodeIndex) {
|
|
185
|
-
const component: NodeId[] = [];
|
|
186
|
-
let w: NodeId;
|
|
187
|
-
|
|
188
|
-
do {
|
|
189
|
-
const popped = stack.pop();
|
|
190
|
-
if (popped === undefined) {
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
w = popped;
|
|
195
|
-
|
|
196
|
-
onStack.delete(w);
|
|
197
|
-
component.push(w);
|
|
198
|
-
} while (w !== nodeId);
|
|
199
|
-
|
|
200
|
-
components.push(component);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
for (const nodeId of nodes) {
|
|
205
|
-
if (!indices.has(nodeId)) {
|
|
206
|
-
tarjanSCC(nodeId);
|
|
207
|
-
}
|
|
149
|
+
// Tarjan's pop order lists each component as one run, in component order.
|
|
150
|
+
const s = toTopologySnapshot(graph);
|
|
151
|
+
const { labels, count, popped } = tarjan(s, { arcOrder: legacyArcOrder(graph, s) });
|
|
152
|
+
const components: NodeId[][] = Array.from({ length: count }, () => []);
|
|
153
|
+
for (const i of popped) {
|
|
154
|
+
components[labels[i]].push(s.ids.idOf(i));
|
|
208
155
|
}
|
|
209
|
-
|
|
210
156
|
return components;
|
|
211
157
|
}
|
|
212
158
|
|
|
@@ -237,20 +183,7 @@ export function weaklyConnectedComponents(graph: Graph): NodeId[][] {
|
|
|
237
183
|
);
|
|
238
184
|
}
|
|
239
185
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (nodes.length === 0) {
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const unionFind = new UnionFind(nodes);
|
|
247
|
-
|
|
248
|
-
// Union nodes connected by edges (ignore direction)
|
|
249
|
-
for (const edge of Array.from(graph.edges())) {
|
|
250
|
-
unionFind.union(edge.source, edge.target);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return unionFind.getAllComponents();
|
|
186
|
+
return componentGroups(graph);
|
|
254
187
|
}
|
|
255
188
|
|
|
256
189
|
/**
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Graph } from "../../core/graph.js";
|
|
2
2
|
import { UnionFind } from "../../data-structures/union-find.js";
|
|
3
|
+
import { exactArcWeights } from "../../indexed/facade.js";
|
|
4
|
+
import { kruskalMST as indexedKruskalMST } from "../../indexed/mst.js";
|
|
5
|
+
import { toSnapshotOrNull } from "../../indexed/to-snapshot.js";
|
|
3
6
|
import type { Edge } from "../../types/index.js";
|
|
4
7
|
|
|
5
8
|
export interface MSTResult {
|
|
@@ -20,14 +23,72 @@ export function kruskalMST(graph: Graph): MSTResult {
|
|
|
20
23
|
throw new Error("Kruskal's algorithm requires an undirected graph");
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
// A NaN weight makes the legacy sort order depend on the sort's own internals, and legacy drops
|
|
27
|
+
// an edge whose key string another edge already spells (1-2 and "1"-"2"): keep that walk for both.
|
|
28
|
+
const s = legacyDropsAnEdge(graph) ? null : toSnapshotOrNull(graph);
|
|
29
|
+
if (s === null) {
|
|
30
|
+
return kruskalMSTLegacy(graph);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { edges: accepted, totalWeight } = indexedKruskalMST(s, { weights: exactArcWeights(s) });
|
|
34
|
+
const { src, dst } = s.edgeList();
|
|
35
|
+
const edges: Edge[] = [];
|
|
36
|
+
for (const e of accepted) {
|
|
37
|
+
// The graph's own edge object, as legacy returned it.
|
|
38
|
+
const edge = graph.getEdge(s.ids.idOf(src[e]), s.ids.idOf(dst[e]));
|
|
39
|
+
if (edge !== undefined) {
|
|
40
|
+
edges.push(edge);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (edges.length !== graph.nodeCount - 1) {
|
|
45
|
+
throw new Error("Graph is not connected");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { edges, totalWeight };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The key legacy Kruskal dedups edges by: both ids as strings, lesser first.
|
|
53
|
+
* @param edge - The edge
|
|
54
|
+
* @returns Its key
|
|
55
|
+
*/
|
|
56
|
+
function legacyEdgeKey(edge: Edge): string {
|
|
57
|
+
return edge.source < edge.target
|
|
58
|
+
? `${String(edge.source)}-${String(edge.target)}`
|
|
59
|
+
: `${String(edge.target)}-${String(edge.source)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether legacy Kruskal would ignore one of the graph's edges.
|
|
64
|
+
* @param graph - The undirected graph
|
|
65
|
+
* @returns True if two of its edges share a legacy key, so legacy Kruskal ignores one of them
|
|
66
|
+
*/
|
|
67
|
+
function legacyDropsAnEdge(graph: Graph): boolean {
|
|
68
|
+
const keys = new Set<string>();
|
|
69
|
+
for (const edge of graph.edges()) {
|
|
70
|
+
const key = legacyEdgeKey(edge);
|
|
71
|
+
if (keys.has(key)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
keys.add(key);
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The legacy Kruskal behind {@link kruskalMST}, for a graph with a NaN weight or two edges whose
|
|
81
|
+
* keys collide.
|
|
82
|
+
* @param graph - The undirected graph to compute the MST for
|
|
83
|
+
* @returns The minimum spanning tree as a set of edges and the total weight
|
|
84
|
+
* @throws Error if the graph is not connected
|
|
85
|
+
*/
|
|
86
|
+
function kruskalMSTLegacy(graph: Graph): MSTResult {
|
|
23
87
|
const edges: Edge[] = [];
|
|
24
88
|
const visitedEdges = new Set<string>();
|
|
25
89
|
|
|
26
90
|
for (const edge of Array.from(graph.edges())) {
|
|
27
|
-
const edgeKey =
|
|
28
|
-
edge.source < edge.target
|
|
29
|
-
? `${String(edge.source)}-${String(edge.target)}`
|
|
30
|
-
: `${String(edge.target)}-${String(edge.source)}`;
|
|
91
|
+
const edgeKey = legacyEdgeKey(edge);
|
|
31
92
|
|
|
32
93
|
if (!visitedEdges.has(edgeKey)) {
|
|
33
94
|
visitedEdges.add(edgeKey);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import { bellmanFord as indexedBellmanFord } from "../../indexed/bellman-ford.js";
|
|
3
|
+
import { exactArcWeights } from "../../indexed/facade.js";
|
|
4
|
+
import { toSnapshotOrNull } from "../../indexed/to-snapshot.js";
|
|
2
5
|
import type { NodeId, ShortestPathResult } from "../../types/index.js";
|
|
3
6
|
import { reconstructPath } from "../../utils/graph-utilities.js";
|
|
4
7
|
|
|
@@ -210,6 +213,52 @@ export function bellmanFordPath(graph: Graph, source: NodeId, target: NodeId): S
|
|
|
210
213
|
* @returns True if the graph contains a negative cycle
|
|
211
214
|
*/
|
|
212
215
|
export function hasNegativeCycle(graph: Graph): boolean {
|
|
216
|
+
// A -Infinity weight: legacy's answer depends on where its |V| - 1 rounds stopped, so keep that walk.
|
|
217
|
+
const s = hasMinusInfinityWeight(graph) ? null : toSnapshotOrNull(graph);
|
|
218
|
+
if (s === null) {
|
|
219
|
+
return hasNegativeCycleLegacy(graph);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// One search from each node no earlier search reached, as a cycle reachable from a reached node
|
|
223
|
+
// was reachable from that search's source too.
|
|
224
|
+
const weights = exactArcWeights(s);
|
|
225
|
+
const reached = new Uint8Array(s.nodeCount);
|
|
226
|
+
for (let source = 0; source < s.nodeCount; source++) {
|
|
227
|
+
if (reached[source] === 0) {
|
|
228
|
+
const { dist, hasNegativeCycle: found } = indexedBellmanFord(s, source, { weights });
|
|
229
|
+
if (found) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
for (let v = 0; v < s.nodeCount; v++) {
|
|
233
|
+
if (dist[v] !== Infinity) {
|
|
234
|
+
reached[v] = 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Whether any edge of the graph weighs -Infinity.
|
|
244
|
+
* @param graph - The graph to scan
|
|
245
|
+
* @returns True if any edge weighs -Infinity
|
|
246
|
+
*/
|
|
247
|
+
function hasMinusInfinityWeight(graph: Graph): boolean {
|
|
248
|
+
for (const edge of graph.edges()) {
|
|
249
|
+
if (edge.weight === -Infinity) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The legacy check behind {@link hasNegativeCycle}, for a graph with a NaN or -Infinity weight.
|
|
258
|
+
* @param graph - The graph to check for negative cycles
|
|
259
|
+
* @returns True if the graph contains a negative cycle
|
|
260
|
+
*/
|
|
261
|
+
function hasNegativeCycleLegacy(graph: Graph): boolean {
|
|
213
262
|
const nodes = Array.from(graph.nodes());
|
|
214
263
|
|
|
215
264
|
if (nodes.length === 0) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
2
|
import { PriorityQueue } from "../../data-structures/priority-queue.js";
|
|
3
|
+
import { dijkstra as indexedDijkstra } from "../../indexed/dijkstra.js";
|
|
4
|
+
import { exactArcWeights } from "../../indexed/facade.js";
|
|
5
|
+
import { toSnapshotOrNull } from "../../indexed/to-snapshot.js";
|
|
3
6
|
import type { DijkstraOptions, NodeId, ShortestPathResult } from "../../types/index.js";
|
|
4
7
|
import { reconstructPath } from "../../utils/graph-utilities.js";
|
|
5
8
|
import { BidirectionalDijkstra } from "./bidirectional-dijkstra.js";
|
|
@@ -174,6 +177,32 @@ export function singleSourceShortestPath(graph: Graph, source: NodeId, cutoff?:
|
|
|
174
177
|
throw new Error(`Source node ${String(source)} not found in graph`);
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
const s = toSnapshotOrNull(graph);
|
|
181
|
+
const weights = s === null ? undefined : (exactArcWeights(s) ?? s.weights ?? undefined);
|
|
182
|
+
// Dijkstra's answer with a negative or NaN weight depends on the order the legacy walk settled
|
|
183
|
+
// nodes in, which the port does not share: those graphs keep the legacy walk.
|
|
184
|
+
if (s === null || weights?.some((w) => w < 0) === true) {
|
|
185
|
+
return singleSourceShortestPathLegacy(graph, source, cutoff);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { dist } = indexedDijkstra(s, s.ids.indexOf(source), { cutoff, weights });
|
|
189
|
+
const result = new Map<NodeId, number>();
|
|
190
|
+
for (let i = 0; i < s.nodeCount; i++) {
|
|
191
|
+
if (dist[i] < Infinity && (cutoff === undefined || dist[i] <= cutoff)) {
|
|
192
|
+
result.set(s.ids.idOf(i), dist[i]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The legacy walk behind {@link singleSourceShortestPath}, for graphs with a negative or NaN weight.
|
|
200
|
+
* @param graph - The graph to search
|
|
201
|
+
* @param source - The starting node for the search, known to be in the graph
|
|
202
|
+
* @param cutoff - Optional maximum distance to search
|
|
203
|
+
* @returns A map of node IDs to their distances from the source
|
|
204
|
+
*/
|
|
205
|
+
function singleSourceShortestPathLegacy(graph: Graph, source: NodeId, cutoff?: number): Map<NodeId, number> {
|
|
177
206
|
const distances = new Map<NodeId, number>();
|
|
178
207
|
const visited = new Set<NodeId>();
|
|
179
208
|
const pq = new PriorityQueue<NodeId>();
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { INVALID_INDEX } from "@graphty/graph-format";
|
|
2
|
+
|
|
3
|
+
import type { Graph } from "../../core/graph.js";
|
|
4
|
+
import { allPairsShortestPath } from "../../indexed/all-pairs.js";
|
|
5
|
+
import { exactArcWeights } from "../../indexed/facade.js";
|
|
6
|
+
import { toSnapshot } from "../../indexed/to-snapshot.js";
|
|
7
|
+
import type { Edge, NodeId } from "../../types/index.js";
|
|
3
8
|
|
|
4
9
|
export interface FloydWarshallResult {
|
|
5
10
|
distances: Map<NodeId, Map<NodeId, number>>;
|
|
@@ -8,106 +13,91 @@ export interface FloydWarshallResult {
|
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
|
-
*
|
|
16
|
+
* The graph without its +Infinity edges, which the port refuses and which never shortened a path
|
|
17
|
+
* (`x + Infinity < d` is false), and those edges. A NaN or -Infinity weight has no such reading, so it
|
|
18
|
+
* throws.
|
|
19
|
+
* @param graph - The legacy graph
|
|
20
|
+
* @param name - The calling function, for the error message
|
|
21
|
+
* @returns The graph to run on (the input itself when every weight is finite) and the removed edges,
|
|
22
|
+
* each in both orientations on an undirected graph
|
|
23
|
+
* @throws RangeError on a NaN or -Infinity edge weight
|
|
24
|
+
*/
|
|
25
|
+
function withoutInfinite(graph: Graph, name: string): { finite: Graph; removed: Edge[] } {
|
|
26
|
+
const removed: Edge[] = [];
|
|
27
|
+
for (const e of graph.edges()) {
|
|
28
|
+
const w = e.weight ?? 1;
|
|
29
|
+
if (Number.isNaN(w) || w === -Infinity) {
|
|
30
|
+
throw new RangeError(
|
|
31
|
+
`${name}: the edge ${String(e.source)} -> ${String(e.target)} has weight ${String(w)}; weights must be numbers or +Infinity (no edge)`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (w === Infinity) {
|
|
35
|
+
removed.push(e);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (removed.length === 0) {
|
|
39
|
+
return { finite: graph, removed };
|
|
40
|
+
}
|
|
41
|
+
const finite = graph.clone();
|
|
42
|
+
for (const e of removed) {
|
|
43
|
+
finite.removeEdge(e.source, e.target);
|
|
44
|
+
}
|
|
45
|
+
const reversed = graph.isDirected ? [] : removed.map((e) => ({ ...e, source: e.target, target: e.source }));
|
|
46
|
+
return { finite, removed: [...removed, ...reversed] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Computes all-pairs shortest paths using the Floyd-Warshall algorithm: the port's sweep in node order
|
|
51
|
+
* over the exact f64 weights, with no node bound. A self-loop never changes a node's distance to itself
|
|
52
|
+
* (0); its predecessor on the diagonal is the node. Under a negative cycle (a negative undirected edge is
|
|
53
|
+
* one) every distance is NaN and every predecessor null. An infinite edge weight counts as no edge
|
|
54
|
+
* (its predecessor entry is its source while nothing reaches the target); a NaN weight leaves its own
|
|
55
|
+
* entry NaN and shortens nothing.
|
|
12
56
|
* @param graph - The graph to compute shortest paths for
|
|
13
57
|
* @returns The distances, predecessors, and negative cycle detection result
|
|
58
|
+
* @throws RangeError on a NaN or -Infinity edge weight
|
|
14
59
|
*/
|
|
15
60
|
export function floydWarshall(graph: Graph): FloydWarshallResult {
|
|
16
|
-
const
|
|
61
|
+
const { finite, removed } = withoutInfinite(graph, "floydWarshall");
|
|
62
|
+
const s = toSnapshot(finite);
|
|
63
|
+
const { dist, n, predArc, hasNegativeCycle } = allPairsShortestPath(s, {
|
|
64
|
+
weights: exactArcWeights(s),
|
|
65
|
+
method: "floyd-warshall",
|
|
66
|
+
paths: true,
|
|
67
|
+
maxNodes: Infinity,
|
|
68
|
+
});
|
|
17
69
|
const distances = new Map<NodeId, Map<NodeId, number>>();
|
|
18
70
|
const predecessors = new Map<NodeId, Map<NodeId, NodeId | null>>();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (node === other) {
|
|
33
|
-
nodeDistances.set(other, 0);
|
|
34
|
-
nodePredecessors.set(other, null);
|
|
35
|
-
} else {
|
|
36
|
-
nodeDistances.set(other, Infinity);
|
|
37
|
-
nodePredecessors.set(other, null);
|
|
38
|
-
}
|
|
71
|
+
for (let i = 0; i < n; i++) {
|
|
72
|
+
const row = new Map<NodeId, number>();
|
|
73
|
+
const predRow = new Map<NodeId, NodeId | null>();
|
|
74
|
+
for (let j = 0; j < n; j++) {
|
|
75
|
+
const id = s.ids.idOf(j);
|
|
76
|
+
row.set(id, dist[i * n + j]);
|
|
77
|
+
const arc = predArc?.[i * n + j];
|
|
78
|
+
predRow.set(
|
|
79
|
+
id,
|
|
80
|
+
hasNegativeCycle || arc === undefined || i === j || dist[i * n + j] === Infinity
|
|
81
|
+
? null
|
|
82
|
+
: s.ids.idOf(s.arcSource(arc)),
|
|
83
|
+
);
|
|
39
84
|
}
|
|
85
|
+
distances.set(s.ids.idOf(i), row);
|
|
86
|
+
predecessors.set(s.ids.idOf(i), predRow);
|
|
40
87
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const sourcePredecessors = predecessors.get(edge.source);
|
|
46
|
-
|
|
47
|
-
if (sourceDistances && sourcePredecessors) {
|
|
48
|
-
sourceDistances.set(edge.target, weight);
|
|
49
|
-
sourcePredecessors.set(edge.target, edge.source);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!graph.isDirected) {
|
|
53
|
-
const targetDistances = distances.get(edge.target);
|
|
54
|
-
const targetPredecessors = predecessors.get(edge.target);
|
|
55
|
-
|
|
56
|
-
if (targetDistances && targetPredecessors) {
|
|
57
|
-
targetDistances.set(edge.source, weight);
|
|
58
|
-
targetPredecessors.set(edge.source, edge.target);
|
|
88
|
+
if (!hasNegativeCycle) {
|
|
89
|
+
for (const e of graph.edges()) {
|
|
90
|
+
if (e.source === e.target) {
|
|
91
|
+
predecessors.get(e.source)?.set(e.source, e.source);
|
|
59
92
|
}
|
|
60
93
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for (const i of nodes) {
|
|
65
|
-
for (const j of nodes) {
|
|
66
|
-
const distancesI = distances.get(i);
|
|
67
|
-
const distancesK = distances.get(k);
|
|
68
|
-
const predecessorsI = predecessors.get(i);
|
|
69
|
-
const predecessorsK = predecessors.get(k);
|
|
70
|
-
|
|
71
|
-
if (!distancesI || !distancesK || !predecessorsI || !predecessorsK) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const distIK = distancesI.get(k);
|
|
76
|
-
const distKJ = distancesK.get(j);
|
|
77
|
-
const distIJ = distancesI.get(j);
|
|
78
|
-
|
|
79
|
-
if (distIK === undefined || distKJ === undefined || distIJ === undefined) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (distIK + distKJ < distIJ) {
|
|
84
|
-
distancesI.set(j, distIK + distKJ);
|
|
85
|
-
const predKJ = predecessorsK.get(j);
|
|
86
|
-
predecessorsI.set(j, predKJ ?? null);
|
|
87
|
-
}
|
|
94
|
+
for (const { source, target } of removed) {
|
|
95
|
+
if (distances.get(source)?.get(target) === Infinity) {
|
|
96
|
+
predecessors.get(source)?.set(target, source);
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
|
-
|
|
92
|
-
let hasNegativeCycle = false;
|
|
93
|
-
for (const node of nodes) {
|
|
94
|
-
const nodeDistances = distances.get(node);
|
|
95
|
-
if (!nodeDistances) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const selfDistance = nodeDistances.get(node);
|
|
100
|
-
if (selfDistance !== undefined && selfDistance < 0) {
|
|
101
|
-
hasNegativeCycle = true;
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
distances,
|
|
108
|
-
predecessors,
|
|
109
|
-
hasNegativeCycle,
|
|
110
|
-
};
|
|
100
|
+
return { distances, predecessors, hasNegativeCycle };
|
|
111
101
|
}
|
|
112
102
|
|
|
113
103
|
/**
|
|
@@ -115,73 +105,53 @@ export function floydWarshall(graph: Graph): FloydWarshallResult {
|
|
|
115
105
|
* @param graph - The graph to search
|
|
116
106
|
* @param source - The starting node for the path
|
|
117
107
|
* @param target - The destination node for the path
|
|
118
|
-
* @returns The path and distance or null if no path exists
|
|
108
|
+
* @returns The path and distance, or null if either node is missing, no path exists, or the graph
|
|
109
|
+
* has a negative cycle
|
|
110
|
+
* @throws RangeError on a NaN or -Infinity edge weight
|
|
119
111
|
*/
|
|
120
112
|
export function floydWarshallPath(
|
|
121
113
|
graph: Graph,
|
|
122
114
|
source: NodeId,
|
|
123
115
|
target: NodeId,
|
|
124
116
|
): { path: NodeId[]; distance: number } | null {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const sourceDistances = result.distances.get(source);
|
|
132
|
-
if (!sourceDistances) {
|
|
117
|
+
const s = toSnapshot(withoutInfinite(graph, "floydWarshallPath").finite);
|
|
118
|
+
const i = s.ids.indexOf(source);
|
|
119
|
+
const j = s.ids.indexOf(target);
|
|
120
|
+
if (i === INVALID_INDEX || j === INVALID_INDEX) {
|
|
133
121
|
return null;
|
|
134
122
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
123
|
+
const result = allPairsShortestPath(s, {
|
|
124
|
+
weights: exactArcWeights(s),
|
|
125
|
+
method: "floyd-warshall",
|
|
126
|
+
paths: true,
|
|
127
|
+
maxNodes: Infinity,
|
|
128
|
+
});
|
|
129
|
+
const distance = result.dist[i * result.n + j];
|
|
130
|
+
if (result.hasNegativeCycle || distance === Infinity) {
|
|
138
131
|
return null;
|
|
139
132
|
}
|
|
140
|
-
|
|
141
|
-
const path: NodeId[] = [];
|
|
142
|
-
let current: NodeId | null = target;
|
|
143
|
-
|
|
144
|
-
while (current !== null && current !== source) {
|
|
145
|
-
path.unshift(current);
|
|
146
|
-
|
|
147
|
-
const sourcePredecessors = result.predecessors.get(source);
|
|
148
|
-
if (!sourcePredecessors) {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
current = sourcePredecessors.get(current) ?? null;
|
|
153
|
-
|
|
154
|
-
if (path.length > graph.nodeCount) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (current === source) {
|
|
160
|
-
path.unshift(source);
|
|
161
|
-
return { path, distance };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return null;
|
|
133
|
+
return { path: Array.from(result.pathTo(i, j), (k) => s.ids.idOf(k)), distance };
|
|
165
134
|
}
|
|
166
135
|
|
|
167
136
|
/**
|
|
168
|
-
* Computes the transitive closure of a graph
|
|
137
|
+
* Computes the transitive closure of a graph: one breadth-first search per node, edge weights
|
|
138
|
+
* ignored except that a +Infinity edge counts as no edge.
|
|
169
139
|
* @param graph - The graph to compute transitive closure for
|
|
170
|
-
* @returns A map of each node to the set of nodes reachable from it
|
|
140
|
+
* @returns A map of each node to the set of nodes reachable from it, itself included, in node order
|
|
141
|
+
* @throws RangeError on a NaN or -Infinity edge weight
|
|
171
142
|
*/
|
|
172
143
|
export function transitiveClosure(graph: Graph): Map<NodeId, Set<NodeId>> {
|
|
173
|
-
const
|
|
144
|
+
const s = toSnapshot(withoutInfinite(graph, "transitiveClosure").finite);
|
|
145
|
+
const { dist, n } = allPairsShortestPath(s, { weighted: false, maxNodes: Infinity });
|
|
174
146
|
const closure = new Map<NodeId, Set<NodeId>>();
|
|
175
|
-
|
|
176
|
-
for (const [source, distances] of Array.from(result.distances)) {
|
|
147
|
+
for (let i = 0; i < n; i++) {
|
|
177
148
|
const reachable = new Set<NodeId>();
|
|
178
|
-
for (
|
|
179
|
-
if (
|
|
180
|
-
reachable.add(
|
|
149
|
+
for (let j = 0; j < n; j++) {
|
|
150
|
+
if (dist[i * n + j] < Infinity) {
|
|
151
|
+
reachable.add(s.ids.idOf(j));
|
|
181
152
|
}
|
|
182
153
|
}
|
|
183
|
-
closure.set(
|
|
154
|
+
closure.set(s.ids.idOf(i), reachable);
|
|
184
155
|
}
|
|
185
|
-
|
|
186
156
|
return closure;
|
|
187
157
|
}
|