@graphty/algorithms 1.0.1 → 1.1.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 +827 -60
- package/dist/algorithms.d.ts +2 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +2 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +22 -7
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +7 -7
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +1 -1
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.js +113 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
- package/dist/src/algorithms/centrality/hits.d.ts +35 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/hits.js +146 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +6 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +3 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +30 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/katz.js +85 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -0
- package/dist/src/algorithms/centrality/pagerank.js +3 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.js +297 -0
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
- package/dist/src/algorithms/community/index.d.ts +12 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -0
- package/dist/src/algorithms/community/index.js +12 -0
- package/dist/src/algorithms/community/index.js.map +1 -0
- package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/community/label-propagation.js +307 -0
- package/dist/src/algorithms/community/label-propagation.js.map +1 -0
- package/dist/src/algorithms/community/leiden.d.ts +33 -0
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
- package/dist/src/algorithms/community/leiden.js +434 -0
- package/dist/src/algorithms/community/leiden.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +19 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain.js +219 -0
- package/dist/src/algorithms/community/louvain.js.map +1 -0
- package/dist/src/algorithms/components/connected.js +7 -7
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +7 -0
- package/dist/src/algorithms/index.d.ts.map +1 -1
- package/dist/src/algorithms/index.js +14 -0
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
- package/dist/src/algorithms/matching/bipartite.js +127 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -0
- package/dist/src/algorithms/matching/index.d.ts +8 -0
- package/dist/src/algorithms/matching/index.d.ts.map +1 -0
- package/dist/src/algorithms/matching/index.js +6 -0
- package/dist/src/algorithms/matching/index.js.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.js +291 -0
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
- package/dist/src/algorithms/mst/index.d.ts +4 -0
- package/dist/src/algorithms/mst/index.d.ts.map +1 -0
- package/dist/src/algorithms/mst/index.js +3 -0
- package/dist/src/algorithms/mst/index.js.map +1 -0
- package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
- package/dist/src/algorithms/mst/kruskal.js +43 -0
- package/dist/src/algorithms/mst/kruskal.js.map +1 -0
- package/dist/src/algorithms/mst/prim.d.ts +5 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
- package/dist/src/algorithms/mst/prim.js +63 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
- package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js +1 -0
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +2 -2
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +11 -11
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +48 -0
- package/dist/src/clustering/hierarchical.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical.js +439 -0
- package/dist/src/clustering/hierarchical.js.map +1 -0
- package/dist/src/clustering/index.d.ts +7 -0
- package/dist/src/clustering/index.d.ts.map +1 -0
- package/dist/src/clustering/index.js +5 -0
- package/dist/src/clustering/index.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +63 -0
- package/dist/src/clustering/k-core.d.ts.map +1 -0
- package/dist/src/clustering/k-core.js +377 -0
- package/dist/src/clustering/k-core.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +36 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -0
- package/dist/src/clustering/mcl.js +343 -0
- package/dist/src/clustering/mcl.js.map +1 -0
- package/dist/src/clustering/spectral.d.ts +28 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -0
- package/dist/src/clustering/spectral.js +447 -0
- package/dist/src/clustering/spectral.js.map +1 -0
- package/dist/src/core/graph.d.ts +9 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +31 -11
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/priority-queue.js +0 -2
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.js +2 -5
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +53 -0
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
- package/dist/src/flow/ford-fulkerson.js +355 -0
- package/dist/src/flow/ford-fulkerson.js.map +1 -0
- package/dist/src/flow/index.d.ts +3 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +3 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/flow/min-cut.d.ts +49 -0
- package/dist/src/flow/min-cut.d.ts.map +1 -0
- package/dist/src/flow/min-cut.js +383 -0
- package/dist/src/flow/min-cut.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
- package/dist/src/link-prediction/adamic-adar.js +256 -0
- package/dist/src/link-prediction/adamic-adar.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors.js +155 -0
- package/dist/src/link-prediction/common-neighbors.js.map +1 -0
- package/dist/src/link-prediction/index.d.ts +7 -0
- package/dist/src/link-prediction/index.d.ts.map +1 -0
- package/dist/src/link-prediction/index.js +6 -0
- package/dist/src/link-prediction/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +50 -0
- package/dist/src/pathfinding/astar.d.ts.map +1 -0
- package/dist/src/pathfinding/astar.js +180 -0
- package/dist/src/pathfinding/astar.js.map +1 -0
- package/dist/src/pathfinding/index.d.ts +3 -0
- package/dist/src/pathfinding/index.d.ts.map +1 -0
- package/dist/src/pathfinding/index.js +3 -0
- package/dist/src/pathfinding/index.js.map +1 -0
- package/dist/src/pathfinding/utils.d.ts +18 -0
- package/dist/src/pathfinding/utils.d.ts.map +1 -0
- package/dist/src/pathfinding/utils.js +68 -0
- package/dist/src/pathfinding/utils.js.map +1 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/graphNode.d.ts +8 -0
- package/dist/src/utils/graphNode.d.ts.map +1 -0
- package/dist/src/utils/graphNode.js +2 -0
- package/dist/src/utils/graphNode.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +21 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -0
- package/dist/src/utils/priorityQueue.js +101 -0
- package/dist/src/utils/priorityQueue.js.map +1 -0
- package/package.json +3 -1
- package/src/algorithms/centrality/betweenness.ts +25 -7
- package/src/algorithms/centrality/closeness.ts +7 -7
- package/src/algorithms/centrality/degree.ts +1 -1
- package/src/algorithms/centrality/eigenvector.ts +162 -0
- package/src/algorithms/centrality/hits.ts +210 -0
- package/src/algorithms/centrality/index.ts +6 -0
- package/src/algorithms/centrality/katz.ts +139 -0
- package/src/algorithms/centrality/pagerank.ts +3 -3
- package/src/algorithms/community/girvan-newman.ts +372 -0
- package/src/algorithms/community/index.ts +13 -0
- package/src/algorithms/community/label-propagation.ts +392 -0
- package/src/algorithms/community/leiden.ts +572 -0
- package/src/algorithms/community/louvain.ts +298 -0
- package/src/algorithms/components/connected.ts +7 -7
- package/src/algorithms/index.ts +21 -0
- package/src/algorithms/matching/bipartite.ts +179 -0
- package/src/algorithms/matching/index.ts +8 -0
- package/src/algorithms/matching/isomorphism.ts +392 -0
- package/src/algorithms/mst/index.ts +4 -0
- package/src/algorithms/mst/kruskal.ts +61 -0
- package/src/algorithms/mst/prim.ts +80 -0
- package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
- package/src/algorithms/shortest-path/dijkstra.ts +5 -5
- package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
- package/src/algorithms/shortest-path/index.ts +2 -0
- package/src/algorithms/traversal/bfs.ts +2 -2
- package/src/algorithms/traversal/dfs.ts +11 -11
- package/src/clustering/hierarchical.ts +555 -0
- package/src/clustering/index.ts +6 -0
- package/src/clustering/k-core.ts +460 -0
- package/src/clustering/mcl.ts +454 -0
- package/src/clustering/spectral.ts +545 -0
- package/src/core/graph.ts +34 -6
- package/src/data-structures/union-find.ts +2 -2
- package/src/flow/ford-fulkerson.ts +459 -0
- package/src/flow/index.ts +2 -0
- package/src/flow/min-cut.ts +482 -0
- package/src/index.ts +2 -0
- package/src/link-prediction/adamic-adar.ts +353 -0
- package/src/link-prediction/common-neighbors.ts +252 -0
- package/src/link-prediction/index.ts +20 -0
- package/src/pathfinding/astar.ts +229 -0
- package/src/pathfinding/index.ts +2 -0
- package/src/pathfinding/utils.ts +87 -0
- package/src/types/index.ts +11 -0
- package/src/utils/graphNode.ts +7 -0
- package/src/utils/priorityQueue.ts +120 -0
- package/dist/test/browser/basic.test.d.ts +0 -2
- package/dist/test/browser/basic.test.d.ts.map +0 -1
- package/dist/test/browser/basic.test.js +0 -70
- package/dist/test/browser/basic.test.js.map +0 -1
- package/dist/test/unit/bellman-ford.test.d.ts +0 -2
- package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
- package/dist/test/unit/bellman-ford.test.js +0 -231
- package/dist/test/unit/bellman-ford.test.js.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
- package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.js +0 -311
- package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
- package/dist/test/unit/bfs.test.d.ts +0 -2
- package/dist/test/unit/bfs.test.d.ts.map +0 -1
- package/dist/test/unit/bfs.test.js +0 -277
- package/dist/test/unit/bfs.test.js.map +0 -1
- package/dist/test/unit/closeness.test.d.ts +0 -2
- package/dist/test/unit/closeness.test.d.ts.map +0 -1
- package/dist/test/unit/closeness.test.js +0 -266
- package/dist/test/unit/closeness.test.js.map +0 -1
- package/dist/test/unit/connected-components.test.d.ts +0 -2
- package/dist/test/unit/connected-components.test.d.ts.map +0 -1
- package/dist/test/unit/connected-components.test.js +0 -355
- package/dist/test/unit/connected-components.test.js.map +0 -1
- package/dist/test/unit/degree-centrality.test.d.ts +0 -2
- package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/degree-centrality.test.js +0 -260
- package/dist/test/unit/degree-centrality.test.js.map +0 -1
- package/dist/test/unit/dfs.test.d.ts +0 -2
- package/dist/test/unit/dfs.test.d.ts.map +0 -1
- package/dist/test/unit/dfs.test.js +0 -350
- package/dist/test/unit/dfs.test.js.map +0 -1
- package/dist/test/unit/dijkstra.test.d.ts +0 -2
- package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
- package/dist/test/unit/dijkstra.test.js +0 -311
- package/dist/test/unit/dijkstra.test.js.map +0 -1
- package/dist/test/unit/graph.test.d.ts +0 -2
- package/dist/test/unit/graph.test.d.ts.map +0 -1
- package/dist/test/unit/graph.test.js +0 -175
- package/dist/test/unit/graph.test.js.map +0 -1
- package/dist/test/unit/pagerank.test.d.ts +0 -2
- package/dist/test/unit/pagerank.test.d.ts.map +0 -1
- package/dist/test/unit/pagerank.test.js +0 -281
- package/dist/test/unit/pagerank.test.js.map +0 -1
- package/dist/test/unit/priority-queue.test.d.ts +0 -2
- package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
- package/dist/test/unit/priority-queue.test.js +0 -189
- package/dist/test/unit/priority-queue.test.js.map +0 -1
- package/dist/test/unit/union-find.test.d.ts +0 -2
- package/dist/test/unit/union-find.test.d.ts.map +0 -1
- package/dist/test/unit/union-find.test.js +0 -245
- package/dist/test/unit/union-find.test.js.map +0 -1
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K-Core Decomposition Algorithm
|
|
3
|
+
*
|
|
4
|
+
* Finds the k-core subgraph where each node has at least k neighbors
|
|
5
|
+
* within the subgraph. Used for identifying cohesive groups and
|
|
6
|
+
* understanding graph structure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface KCoreResult<T> {
|
|
10
|
+
cores: Map<number, Set<T>>;
|
|
11
|
+
coreness: Map<T, number>;
|
|
12
|
+
maxCore: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* K-Core decomposition algorithm
|
|
17
|
+
* Finds all k-cores in the graph and assigns coreness values to nodes
|
|
18
|
+
*
|
|
19
|
+
* @param graph - Undirected graph as adjacency list
|
|
20
|
+
* @returns K-core decomposition results
|
|
21
|
+
*
|
|
22
|
+
* Time Complexity: O(V + E)
|
|
23
|
+
* Space Complexity: O(V)
|
|
24
|
+
*/
|
|
25
|
+
export function kCoreDecomposition<T>(
|
|
26
|
+
graph: Map<T, Set<T>>,
|
|
27
|
+
): KCoreResult<T> {
|
|
28
|
+
if (graph.size === 0) {
|
|
29
|
+
return {cores: new Map(), coreness: new Map(), maxCore: 0};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const n = graph.size;
|
|
33
|
+
const nodes = Array.from(graph.keys());
|
|
34
|
+
const nodeToIndex = new Map<T, number>();
|
|
35
|
+
nodes.forEach((node, i) => nodeToIndex.set(node, i));
|
|
36
|
+
|
|
37
|
+
// Initialize data structures
|
|
38
|
+
const degree = new Array<number>(n).fill(0);
|
|
39
|
+
const pos = new Array<number>(n).fill(0);
|
|
40
|
+
const vert = new Array<number>(n).fill(0);
|
|
41
|
+
const coreness = new Map<T, number>();
|
|
42
|
+
|
|
43
|
+
// Initialize coreness for all nodes to 0
|
|
44
|
+
for (let i = 0; i < n; i++) {
|
|
45
|
+
const node = nodes[i];
|
|
46
|
+
if (node !== undefined) {
|
|
47
|
+
coreness.set(node, 0);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Compute initial degrees
|
|
52
|
+
let maxDegree = 0;
|
|
53
|
+
for (let i = 0; i < n; i++) {
|
|
54
|
+
const node = nodes[i];
|
|
55
|
+
if (!node) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const neighbors = graph.get(node);
|
|
60
|
+
const nodeSize = neighbors ? neighbors.size : 0;
|
|
61
|
+
|
|
62
|
+
degree[i] = nodeSize;
|
|
63
|
+
maxDegree = Math.max(maxDegree, nodeSize);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Count nodes of each degree
|
|
67
|
+
const bin = new Array<number>(maxDegree + 1).fill(0);
|
|
68
|
+
for (let i = 0; i < n; i++) {
|
|
69
|
+
const deg = degree[i];
|
|
70
|
+
if (deg !== undefined && deg >= 0 && deg < bin.length) {
|
|
71
|
+
const currentCount = bin[deg];
|
|
72
|
+
if (currentCount !== undefined) {
|
|
73
|
+
bin[deg] = currentCount + 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Starting position of each degree in sorted array
|
|
79
|
+
let start = 0;
|
|
80
|
+
for (let d = 0; d <= maxDegree; d++) {
|
|
81
|
+
const temp = bin[d] ?? 0;
|
|
82
|
+
bin[d] = start;
|
|
83
|
+
start += temp;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Sort nodes by degree
|
|
87
|
+
for (let i = 0; i < n; i++) {
|
|
88
|
+
const deg = degree[i];
|
|
89
|
+
if (deg !== undefined && deg >= 0 && deg < bin.length) {
|
|
90
|
+
const posIndex = bin[deg] ?? 0;
|
|
91
|
+
pos[i] = posIndex;
|
|
92
|
+
vert[posIndex] = i;
|
|
93
|
+
const currentBin = bin[deg];
|
|
94
|
+
if (currentBin !== undefined) {
|
|
95
|
+
bin[deg] = currentBin + 1;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// If we can't properly place the node in vert, set its position to -1
|
|
99
|
+
// This shouldn't happen with our fixes, but just in case
|
|
100
|
+
pos[i] = -1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Recover starting positions
|
|
105
|
+
for (let d = maxDegree; d > 0; d--) {
|
|
106
|
+
bin[d] = bin[d - 1] ?? 0;
|
|
107
|
+
}
|
|
108
|
+
bin[0] = 0;
|
|
109
|
+
|
|
110
|
+
// Main algorithm
|
|
111
|
+
for (let i = 0; i < n; i++) {
|
|
112
|
+
const v = vert[i];
|
|
113
|
+
if (v === undefined) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const node = nodes[v];
|
|
118
|
+
if (node === undefined) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const degreeV = degree[v];
|
|
123
|
+
if (degreeV !== undefined) {
|
|
124
|
+
coreness.set(node, degreeV);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Process neighbors
|
|
128
|
+
const neighbors = graph.get(node);
|
|
129
|
+
if (!neighbors) {
|
|
130
|
+
// Node has no neighbors, skip neighbor processing but coreness is already set
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const neighbor of neighbors) {
|
|
135
|
+
const u = nodeToIndex.get(neighbor);
|
|
136
|
+
if (u === undefined) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const degreeU = degree[u];
|
|
141
|
+
const degreeV = degree[v];
|
|
142
|
+
if (degreeU !== undefined && degreeV !== undefined && degreeU > degreeV) {
|
|
143
|
+
const du = degreeU;
|
|
144
|
+
const pu = pos[u];
|
|
145
|
+
const pw = bin[du] ?? 0;
|
|
146
|
+
const w = vert[pw];
|
|
147
|
+
|
|
148
|
+
if (u !== w && w !== undefined && pw < n && pu !== undefined) {
|
|
149
|
+
// Swap u and w in the sorted order
|
|
150
|
+
vert[pu] = w;
|
|
151
|
+
vert[pw] = u;
|
|
152
|
+
pos[u] = pw;
|
|
153
|
+
pos[w] = pu;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const currentBin = bin[du];
|
|
157
|
+
if (currentBin !== undefined) {
|
|
158
|
+
bin[du] = currentBin + 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
degree[u] = degreeU - 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Build cores map
|
|
167
|
+
const cores = new Map<number, Set<T>>();
|
|
168
|
+
let maxCore = 0;
|
|
169
|
+
|
|
170
|
+
for (const [node, core] of coreness) {
|
|
171
|
+
if (!cores.has(core)) {
|
|
172
|
+
cores.set(core, new Set());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const coreSet = cores.get(core);
|
|
176
|
+
if (coreSet) {
|
|
177
|
+
coreSet.add(node);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
maxCore = Math.max(maxCore, core);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {cores, coreness, maxCore};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Extract the k-core subgraph
|
|
188
|
+
* Returns nodes that belong to k-core or higher
|
|
189
|
+
*
|
|
190
|
+
* @param graph - Undirected graph
|
|
191
|
+
* @param k - Core number
|
|
192
|
+
* @returns Set of nodes in k-core or higher
|
|
193
|
+
*/
|
|
194
|
+
export function getKCore<T>(
|
|
195
|
+
graph: Map<T, Set<T>>,
|
|
196
|
+
k: number,
|
|
197
|
+
): Set<T> {
|
|
198
|
+
const {coreness} = kCoreDecomposition(graph);
|
|
199
|
+
const kCore = new Set<T>();
|
|
200
|
+
|
|
201
|
+
for (const [node, core] of coreness) {
|
|
202
|
+
if (core >= k) {
|
|
203
|
+
kCore.add(node);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return kCore;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get the induced subgraph for k-core
|
|
212
|
+
* Returns the actual subgraph containing only k-core nodes
|
|
213
|
+
*
|
|
214
|
+
* @param graph - Original graph
|
|
215
|
+
* @param k - Core number
|
|
216
|
+
* @returns K-core subgraph
|
|
217
|
+
*/
|
|
218
|
+
export function getKCoreSubgraph<T>(
|
|
219
|
+
graph: Map<T, Set<T>>,
|
|
220
|
+
k: number,
|
|
221
|
+
): Map<T, Set<T>> {
|
|
222
|
+
const kCoreNodes = getKCore(graph, k);
|
|
223
|
+
const subgraph = new Map<T, Set<T>>();
|
|
224
|
+
|
|
225
|
+
for (const node of kCoreNodes) {
|
|
226
|
+
const neighbors = graph.get(node);
|
|
227
|
+
if (neighbors) {
|
|
228
|
+
const coreNeighbors = new Set<T>();
|
|
229
|
+
for (const neighbor of neighbors) {
|
|
230
|
+
if (kCoreNodes.has(neighbor)) {
|
|
231
|
+
coreNeighbors.add(neighbor);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
subgraph.set(node, coreNeighbors);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return subgraph;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Degeneracy ordering of the graph
|
|
243
|
+
* Orders nodes by their coreness values
|
|
244
|
+
*
|
|
245
|
+
* @param graph - Undirected graph
|
|
246
|
+
* @returns Array of nodes ordered by degeneracy
|
|
247
|
+
*/
|
|
248
|
+
export function degeneracyOrdering<T>(
|
|
249
|
+
graph: Map<T, Set<T>>,
|
|
250
|
+
): T[] {
|
|
251
|
+
const degree = new Map<T, number>();
|
|
252
|
+
const remaining = new Map<T, Set<T>>();
|
|
253
|
+
const ordering: T[] = [];
|
|
254
|
+
|
|
255
|
+
// Initialize
|
|
256
|
+
for (const [node, neighbors] of graph) {
|
|
257
|
+
degree.set(node, neighbors.size);
|
|
258
|
+
remaining.set(node, new Set(neighbors));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Build ordering
|
|
262
|
+
while (ordering.length < graph.size) {
|
|
263
|
+
// Find minimum degree node
|
|
264
|
+
let minDegree = Infinity;
|
|
265
|
+
let minNode: T | undefined;
|
|
266
|
+
|
|
267
|
+
for (const [node, deg] of degree) {
|
|
268
|
+
if (!ordering.includes(node) && deg < minDegree) {
|
|
269
|
+
minDegree = deg;
|
|
270
|
+
minNode = node;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (minNode === undefined) {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
ordering.push(minNode);
|
|
279
|
+
|
|
280
|
+
// Update neighbors
|
|
281
|
+
const neighbors = remaining.get(minNode);
|
|
282
|
+
if (neighbors) {
|
|
283
|
+
for (const neighbor of neighbors) {
|
|
284
|
+
const neighborDegree = degree.get(neighbor);
|
|
285
|
+
if (neighborDegree !== undefined) {
|
|
286
|
+
degree.set(neighbor, neighborDegree - 1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
remaining.get(neighbor)?.delete(minNode);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return ordering;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Find k-truss subgraph (triangular k-cores)
|
|
299
|
+
* Each edge must be part of at least k-2 triangles
|
|
300
|
+
*
|
|
301
|
+
* @param graph - Undirected graph
|
|
302
|
+
* @param k - Truss number (k >= 2)
|
|
303
|
+
* @returns Edges in k-truss
|
|
304
|
+
*/
|
|
305
|
+
export function kTruss<T>(
|
|
306
|
+
graph: Map<T, Set<T>>,
|
|
307
|
+
k: number,
|
|
308
|
+
): Set<string> {
|
|
309
|
+
if (k < 2) {
|
|
310
|
+
throw new Error("k must be at least 2 for k-truss");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Count triangles for each edge
|
|
314
|
+
const edgeTriangles = new Map<string, number>();
|
|
315
|
+
const edges = new Set<string>();
|
|
316
|
+
|
|
317
|
+
// Initialize edges
|
|
318
|
+
for (const [u, neighbors] of graph) {
|
|
319
|
+
for (const v of neighbors) {
|
|
320
|
+
if (u < v) { // Avoid duplicates
|
|
321
|
+
const edge = `${String(u)},${String(v)}`;
|
|
322
|
+
edges.add(edge);
|
|
323
|
+
edgeTriangles.set(edge, 0);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Count triangles
|
|
329
|
+
for (const [u, uNeighbors] of graph) {
|
|
330
|
+
for (const v of uNeighbors) {
|
|
331
|
+
if (u < v) {
|
|
332
|
+
const vNeighbors = graph.get(v);
|
|
333
|
+
if (vNeighbors) {
|
|
334
|
+
// Find common neighbors (triangles)
|
|
335
|
+
for (const w of uNeighbors) {
|
|
336
|
+
if (v < w && vNeighbors.has(w)) {
|
|
337
|
+
// Triangle found: u-v-w
|
|
338
|
+
const edge1 = `${String(u)},${String(v)}`;
|
|
339
|
+
const edge2 = `${String(u)},${String(w)}`;
|
|
340
|
+
const edge3 = `${String(v)},${String(w)}`;
|
|
341
|
+
|
|
342
|
+
const edge1Count = edgeTriangles.get(edge1);
|
|
343
|
+
const edge2Count = edgeTriangles.get(edge2);
|
|
344
|
+
const edge3Count = edgeTriangles.get(edge3);
|
|
345
|
+
if (edge1Count !== undefined) {
|
|
346
|
+
edgeTriangles.set(edge1, edge1Count + 1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (edge2Count !== undefined) {
|
|
350
|
+
edgeTriangles.set(edge2, edge2Count + 1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (edge3Count !== undefined) {
|
|
354
|
+
edgeTriangles.set(edge3, edge3Count + 1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Remove edges with insufficient triangles
|
|
364
|
+
const kTrussEdges = new Set<string>(edges);
|
|
365
|
+
let changed = true;
|
|
366
|
+
|
|
367
|
+
while (changed) {
|
|
368
|
+
changed = false;
|
|
369
|
+
const toRemove = new Set<string>();
|
|
370
|
+
|
|
371
|
+
for (const edge of kTrussEdges) {
|
|
372
|
+
const triangleCount = edgeTriangles.get(edge);
|
|
373
|
+
if (triangleCount !== undefined && triangleCount < k - 2) {
|
|
374
|
+
toRemove.add(edge);
|
|
375
|
+
changed = true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Remove edges and update triangle counts
|
|
380
|
+
for (const edge of toRemove) {
|
|
381
|
+
kTrussEdges.delete(edge);
|
|
382
|
+
const parts = edge.split(",");
|
|
383
|
+
if (parts.length < 2) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const [u, v] = parts;
|
|
388
|
+
if (!u || !v) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Update triangle counts for affected edges
|
|
393
|
+
const uNode = u as T;
|
|
394
|
+
const vNode = v as T;
|
|
395
|
+
const uNeighbors = graph.get(uNode);
|
|
396
|
+
const vNeighbors = graph.get(vNode);
|
|
397
|
+
|
|
398
|
+
if (uNeighbors && vNeighbors) {
|
|
399
|
+
for (const w of uNeighbors) {
|
|
400
|
+
if (vNeighbors.has(w)) {
|
|
401
|
+
const edge1 = uNode < w ? `${String(uNode)},${String(w)}` : `${String(w)},${String(uNode)}`;
|
|
402
|
+
const edge2 = vNode < w ? `${String(vNode)},${String(w)}` : `${String(w)},${String(vNode)}`;
|
|
403
|
+
|
|
404
|
+
if (kTrussEdges.has(edge1)) {
|
|
405
|
+
const edge1Count = edgeTriangles.get(edge1);
|
|
406
|
+
if (edge1Count !== undefined) {
|
|
407
|
+
edgeTriangles.set(edge1, edge1Count - 1);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (kTrussEdges.has(edge2)) {
|
|
412
|
+
const edge2Count = edgeTriangles.get(edge2);
|
|
413
|
+
if (edge2Count !== undefined) {
|
|
414
|
+
edgeTriangles.set(edge2, edge2Count - 1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return kTrussEdges;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Convert directed graph to undirected for k-core analysis
|
|
428
|
+
*/
|
|
429
|
+
export function toUndirected<T>(
|
|
430
|
+
directedGraph: Map<T, Map<T, number>>,
|
|
431
|
+
): Map<T, Set<T>> {
|
|
432
|
+
const undirected = new Map<T, Set<T>>();
|
|
433
|
+
|
|
434
|
+
// Initialize all nodes
|
|
435
|
+
for (const node of directedGraph.keys()) {
|
|
436
|
+
undirected.set(node, new Set());
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Add edges in both directions
|
|
440
|
+
for (const [u, neighbors] of directedGraph) {
|
|
441
|
+
for (const v of neighbors.keys()) {
|
|
442
|
+
const uNeighbors = undirected.get(u);
|
|
443
|
+
if (uNeighbors) {
|
|
444
|
+
uNeighbors.add(v);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (!undirected.has(v)) {
|
|
448
|
+
undirected.set(v, new Set());
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const vNeighbors = undirected.get(v);
|
|
452
|
+
if (vNeighbors) {
|
|
453
|
+
vNeighbors.add(u);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return undirected;
|
|
459
|
+
}
|
|
460
|
+
|