@graphty/algorithms 1.0.1

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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -0
  3. package/dist/src/algorithms/centrality/betweenness.d.ts +34 -0
  4. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -0
  5. package/dist/src/algorithms/centrality/betweenness.js +223 -0
  6. package/dist/src/algorithms/centrality/betweenness.js.map +1 -0
  7. package/dist/src/algorithms/centrality/closeness.d.ts +43 -0
  8. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -0
  9. package/dist/src/algorithms/centrality/closeness.js +202 -0
  10. package/dist/src/algorithms/centrality/closeness.js.map +1 -0
  11. package/dist/src/algorithms/centrality/degree.d.ts +17 -0
  12. package/dist/src/algorithms/centrality/degree.d.ts.map +1 -0
  13. package/dist/src/algorithms/centrality/degree.js +86 -0
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -0
  15. package/dist/src/algorithms/centrality/index.d.ts +11 -0
  16. package/dist/src/algorithms/centrality/index.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/index.js +8 -0
  18. package/dist/src/algorithms/centrality/index.js.map +1 -0
  19. package/dist/src/algorithms/centrality/pagerank.d.ts +74 -0
  20. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/pagerank.js +191 -0
  22. package/dist/src/algorithms/centrality/pagerank.js.map +1 -0
  23. package/dist/src/algorithms/components/connected.d.ts +58 -0
  24. package/dist/src/algorithms/components/connected.d.ts.map +1 -0
  25. package/dist/src/algorithms/components/connected.js +234 -0
  26. package/dist/src/algorithms/components/connected.js.map +1 -0
  27. package/dist/src/algorithms/components/index.d.ts +5 -0
  28. package/dist/src/algorithms/components/index.d.ts.map +1 -0
  29. package/dist/src/algorithms/components/index.js +5 -0
  30. package/dist/src/algorithms/components/index.js.map +1 -0
  31. package/dist/src/algorithms/index.d.ts +8 -0
  32. package/dist/src/algorithms/index.d.ts.map +1 -0
  33. package/dist/src/algorithms/index.js +12 -0
  34. package/dist/src/algorithms/index.js.map +1 -0
  35. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +52 -0
  36. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -0
  37. package/dist/src/algorithms/shortest-path/bellman-ford.js +131 -0
  38. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -0
  39. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +26 -0
  40. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -0
  41. package/dist/src/algorithms/shortest-path/dijkstra.js +199 -0
  42. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -0
  43. package/dist/src/algorithms/shortest-path/index.d.ts +7 -0
  44. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -0
  45. package/dist/src/algorithms/shortest-path/index.js +6 -0
  46. package/dist/src/algorithms/shortest-path/index.js.map +1 -0
  47. package/dist/src/algorithms/traversal/bfs.d.ts +25 -0
  48. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -0
  49. package/dist/src/algorithms/traversal/bfs.js +189 -0
  50. package/dist/src/algorithms/traversal/bfs.js.map +1 -0
  51. package/dist/src/algorithms/traversal/dfs.d.ts +32 -0
  52. package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -0
  53. package/dist/src/algorithms/traversal/dfs.js +259 -0
  54. package/dist/src/algorithms/traversal/dfs.js.map +1 -0
  55. package/dist/src/algorithms/traversal/index.d.ts +7 -0
  56. package/dist/src/algorithms/traversal/index.d.ts.map +1 -0
  57. package/dist/src/algorithms/traversal/index.js +6 -0
  58. package/dist/src/algorithms/traversal/index.js.map +1 -0
  59. package/dist/src/core/graph.d.ts +100 -0
  60. package/dist/src/core/graph.d.ts.map +1 -0
  61. package/dist/src/core/graph.js +276 -0
  62. package/dist/src/core/graph.js.map +1 -0
  63. package/dist/src/data-structures/index.d.ts +6 -0
  64. package/dist/src/data-structures/index.d.ts.map +1 -0
  65. package/dist/src/data-structures/index.js +6 -0
  66. package/dist/src/data-structures/index.js.map +1 -0
  67. package/dist/src/data-structures/priority-queue.d.ts +60 -0
  68. package/dist/src/data-structures/priority-queue.d.ts.map +1 -0
  69. package/dist/src/data-structures/priority-queue.js +152 -0
  70. package/dist/src/data-structures/priority-queue.js.map +1 -0
  71. package/dist/src/data-structures/union-find.d.ts +54 -0
  72. package/dist/src/data-structures/union-find.d.ts.map +1 -0
  73. package/dist/src/data-structures/union-find.js +145 -0
  74. package/dist/src/data-structures/union-find.js.map +1 -0
  75. package/dist/src/index.d.ts +11 -0
  76. package/dist/src/index.d.ts.map +1 -0
  77. package/dist/src/index.js +13 -0
  78. package/dist/src/index.js.map +1 -0
  79. package/dist/src/types/index.d.ts +74 -0
  80. package/dist/src/types/index.d.ts.map +1 -0
  81. package/dist/src/types/index.js +5 -0
  82. package/dist/src/types/index.js.map +1 -0
  83. package/dist/test/browser/basic.test.d.ts +2 -0
  84. package/dist/test/browser/basic.test.d.ts.map +1 -0
  85. package/dist/test/browser/basic.test.js +70 -0
  86. package/dist/test/browser/basic.test.js.map +1 -0
  87. package/dist/test/unit/bellman-ford.test.d.ts +2 -0
  88. package/dist/test/unit/bellman-ford.test.d.ts.map +1 -0
  89. package/dist/test/unit/bellman-ford.test.js +231 -0
  90. package/dist/test/unit/bellman-ford.test.js.map +1 -0
  91. package/dist/test/unit/betweenness-centrality.test.d.ts +2 -0
  92. package/dist/test/unit/betweenness-centrality.test.d.ts.map +1 -0
  93. package/dist/test/unit/betweenness-centrality.test.js +311 -0
  94. package/dist/test/unit/betweenness-centrality.test.js.map +1 -0
  95. package/dist/test/unit/bfs.test.d.ts +2 -0
  96. package/dist/test/unit/bfs.test.d.ts.map +1 -0
  97. package/dist/test/unit/bfs.test.js +277 -0
  98. package/dist/test/unit/bfs.test.js.map +1 -0
  99. package/dist/test/unit/closeness.test.d.ts +2 -0
  100. package/dist/test/unit/closeness.test.d.ts.map +1 -0
  101. package/dist/test/unit/closeness.test.js +266 -0
  102. package/dist/test/unit/closeness.test.js.map +1 -0
  103. package/dist/test/unit/connected-components.test.d.ts +2 -0
  104. package/dist/test/unit/connected-components.test.d.ts.map +1 -0
  105. package/dist/test/unit/connected-components.test.js +355 -0
  106. package/dist/test/unit/connected-components.test.js.map +1 -0
  107. package/dist/test/unit/degree-centrality.test.d.ts +2 -0
  108. package/dist/test/unit/degree-centrality.test.d.ts.map +1 -0
  109. package/dist/test/unit/degree-centrality.test.js +260 -0
  110. package/dist/test/unit/degree-centrality.test.js.map +1 -0
  111. package/dist/test/unit/dfs.test.d.ts +2 -0
  112. package/dist/test/unit/dfs.test.d.ts.map +1 -0
  113. package/dist/test/unit/dfs.test.js +350 -0
  114. package/dist/test/unit/dfs.test.js.map +1 -0
  115. package/dist/test/unit/dijkstra.test.d.ts +2 -0
  116. package/dist/test/unit/dijkstra.test.d.ts.map +1 -0
  117. package/dist/test/unit/dijkstra.test.js +311 -0
  118. package/dist/test/unit/dijkstra.test.js.map +1 -0
  119. package/dist/test/unit/graph.test.d.ts +2 -0
  120. package/dist/test/unit/graph.test.d.ts.map +1 -0
  121. package/dist/test/unit/graph.test.js +175 -0
  122. package/dist/test/unit/graph.test.js.map +1 -0
  123. package/dist/test/unit/pagerank.test.d.ts +2 -0
  124. package/dist/test/unit/pagerank.test.d.ts.map +1 -0
  125. package/dist/test/unit/pagerank.test.js +281 -0
  126. package/dist/test/unit/pagerank.test.js.map +1 -0
  127. package/dist/test/unit/priority-queue.test.d.ts +2 -0
  128. package/dist/test/unit/priority-queue.test.d.ts.map +1 -0
  129. package/dist/test/unit/priority-queue.test.js +189 -0
  130. package/dist/test/unit/priority-queue.test.js.map +1 -0
  131. package/dist/test/unit/union-find.test.d.ts +2 -0
  132. package/dist/test/unit/union-find.test.d.ts.map +1 -0
  133. package/dist/test/unit/union-find.test.js +245 -0
  134. package/dist/test/unit/union-find.test.js.map +1 -0
  135. package/package.json +100 -0
  136. package/src/algorithms/centrality/betweenness.ts +303 -0
  137. package/src/algorithms/centrality/closeness.ts +296 -0
  138. package/src/algorithms/centrality/degree.ts +105 -0
  139. package/src/algorithms/centrality/index.ts +11 -0
  140. package/src/algorithms/centrality/pagerank.ts +302 -0
  141. package/src/algorithms/components/connected.ts +298 -0
  142. package/src/algorithms/components/index.ts +17 -0
  143. package/src/algorithms/index.ts +15 -0
  144. package/src/algorithms/shortest-path/bellman-ford.ts +214 -0
  145. package/src/algorithms/shortest-path/dijkstra.ts +256 -0
  146. package/src/algorithms/shortest-path/index.ts +7 -0
  147. package/src/algorithms/traversal/bfs.ts +236 -0
  148. package/src/algorithms/traversal/dfs.ts +367 -0
  149. package/src/algorithms/traversal/index.ts +7 -0
  150. package/src/core/graph.ts +326 -0
  151. package/src/data-structures/index.ts +6 -0
  152. package/src/data-structures/priority-queue.ts +180 -0
  153. package/src/data-structures/union-find.ts +170 -0
  154. package/src/index.ts +35 -0
  155. package/src/types/index.ts +95 -0
@@ -0,0 +1,256 @@
1
+ import type {Graph} from "../../core/graph.js";
2
+ import {PriorityQueue} from "../../data-structures/priority-queue.js";
3
+ import type {DijkstraOptions, NodeId, ShortestPathResult} from "../../types/index.js";
4
+
5
+ /**
6
+ * Dijkstra's algorithm implementation for single-source shortest paths
7
+ *
8
+ * Finds shortest paths from a source node to all other nodes in a weighted graph
9
+ * with non-negative edge weights using a priority queue for efficiency.
10
+ */
11
+
12
+ /**
13
+ * Find shortest paths from source to all reachable nodes using Dijkstra's algorithm
14
+ */
15
+ export function dijkstra(
16
+ graph: Graph,
17
+ source: NodeId,
18
+ options: DijkstraOptions = {},
19
+ ): Map<NodeId, ShortestPathResult> {
20
+ if (!graph.hasNode(source)) {
21
+ throw new Error(`Source node ${String(source)} not found in graph`);
22
+ }
23
+
24
+ const distances = new Map<NodeId, number>();
25
+ const previous = new Map<NodeId, NodeId | null>();
26
+ const visited = new Set<NodeId>();
27
+ const pq = new PriorityQueue<NodeId>();
28
+
29
+ // Initialize distances
30
+ for (const node of graph.nodes()) {
31
+ const distance = node.id === source ? 0 : Infinity;
32
+ distances.set(node.id, distance);
33
+ previous.set(node.id, null);
34
+ pq.enqueue(node.id, distance);
35
+ }
36
+
37
+ while (!pq.isEmpty()) {
38
+ const currentNode = pq.dequeue();
39
+ if (currentNode === undefined) {
40
+ break;
41
+ }
42
+
43
+ const currentDistance = distances.get(currentNode);
44
+ if (currentDistance === undefined) {
45
+ continue;
46
+ }
47
+
48
+ // Skip if already visited (can happen with priority queue updates)
49
+ if (visited.has(currentNode)) {
50
+ continue;
51
+ }
52
+
53
+ visited.add(currentNode);
54
+
55
+ // Early termination if target reached
56
+ if (options.target && currentNode === options.target) {
57
+ break;
58
+ }
59
+
60
+ // Skip if this node is unreachable
61
+ if (currentDistance === Infinity) {
62
+ break;
63
+ }
64
+
65
+ // Check all neighbors
66
+ for (const neighbor of graph.neighbors(currentNode)) {
67
+ if (visited.has(neighbor)) {
68
+ continue;
69
+ }
70
+
71
+ const edge = graph.getEdge(currentNode, neighbor);
72
+ if (!edge) {
73
+ continue;
74
+ }
75
+
76
+ const edgeWeight = edge.weight ?? 1;
77
+
78
+ if (edgeWeight < 0) {
79
+ throw new Error("Dijkstra's algorithm does not support negative edge weights");
80
+ }
81
+
82
+ const tentativeDistance = currentDistance + edgeWeight;
83
+ const neighborDistance = distances.get(neighbor);
84
+ if (neighborDistance === undefined) {
85
+ continue;
86
+ }
87
+
88
+ // Found a shorter path
89
+ if (tentativeDistance < neighborDistance) {
90
+ distances.set(neighbor, tentativeDistance);
91
+ previous.set(neighbor, currentNode);
92
+ // Add to priority queue with new distance
93
+ pq.enqueue(neighbor, tentativeDistance);
94
+ }
95
+ }
96
+ }
97
+
98
+ // Build results
99
+ const results = new Map<NodeId, ShortestPathResult>();
100
+
101
+ for (const [nodeId, distance] of distances) {
102
+ if (distance < Infinity) {
103
+ const path = reconstructPath(nodeId, previous);
104
+ results.set(nodeId, {
105
+ distance,
106
+ path,
107
+ predecessor: new Map(previous),
108
+ });
109
+ }
110
+ }
111
+
112
+ return results;
113
+ }
114
+
115
+ /**
116
+ * Find shortest path between two specific nodes using Dijkstra's algorithm
117
+ */
118
+ export function dijkstraPath(
119
+ graph: Graph,
120
+ source: NodeId,
121
+ target: NodeId,
122
+ ): ShortestPathResult | null {
123
+ if (!graph.hasNode(source)) {
124
+ throw new Error(`Source node ${String(source)} not found in graph`);
125
+ }
126
+
127
+ if (!graph.hasNode(target)) {
128
+ throw new Error(`Target node ${String(target)} not found in graph`);
129
+ }
130
+
131
+ // Special case: source equals target
132
+ if (source === target) {
133
+ return {
134
+ distance: 0,
135
+ path: [source],
136
+ predecessor: new Map([[source, null]]),
137
+ };
138
+ }
139
+
140
+ const results = dijkstra(graph, source, {target});
141
+ return results.get(target) ?? null;
142
+ }
143
+
144
+ /**
145
+ * Single-source shortest paths with early termination optimization
146
+ */
147
+ export function singleSourceShortestPath(
148
+ graph: Graph,
149
+ source: NodeId,
150
+ cutoff?: number,
151
+ ): Map<NodeId, number> {
152
+ if (!graph.hasNode(source)) {
153
+ throw new Error(`Source node ${String(source)} not found in graph`);
154
+ }
155
+
156
+ const distances = new Map<NodeId, number>();
157
+ const visited = new Set<NodeId>();
158
+ const pq = new PriorityQueue<NodeId>();
159
+
160
+ // Initialize distances
161
+ for (const node of graph.nodes()) {
162
+ const distance = node.id === source ? 0 : Infinity;
163
+ distances.set(node.id, distance);
164
+ pq.enqueue(node.id, distance);
165
+ }
166
+
167
+ while (!pq.isEmpty()) {
168
+ const currentNode = pq.dequeue();
169
+ if (currentNode === undefined) {
170
+ break;
171
+ }
172
+
173
+ const currentDistance = distances.get(currentNode);
174
+ if (currentDistance === undefined) {
175
+ continue;
176
+ }
177
+
178
+ // Skip if already visited
179
+ if (visited.has(currentNode)) {
180
+ continue;
181
+ }
182
+
183
+ visited.add(currentNode);
184
+
185
+ // Skip if unreachable or beyond cutoff
186
+ if (currentDistance === Infinity || (cutoff !== undefined && currentDistance > cutoff)) {
187
+ break;
188
+ }
189
+
190
+ // Check all neighbors
191
+ for (const neighbor of graph.neighbors(currentNode)) {
192
+ if (visited.has(neighbor)) {
193
+ continue;
194
+ }
195
+
196
+ const edge = graph.getEdge(currentNode, neighbor);
197
+ if (!edge) {
198
+ continue;
199
+ }
200
+
201
+ const edgeWeight = edge.weight ?? 1;
202
+ const tentativeDistance = currentDistance + edgeWeight;
203
+ const neighborDistance = distances.get(neighbor);
204
+ if (neighborDistance === undefined) {
205
+ continue;
206
+ }
207
+
208
+ // Found a shorter path
209
+ if (tentativeDistance < neighborDistance) {
210
+ distances.set(neighbor, tentativeDistance);
211
+ pq.enqueue(neighbor, tentativeDistance);
212
+ }
213
+ }
214
+ }
215
+
216
+ // Filter out unreachable nodes and apply cutoff
217
+ const result = new Map<NodeId, number>();
218
+
219
+ for (const [nodeId, distance] of distances) {
220
+ if (distance < Infinity && (cutoff === undefined || distance <= cutoff)) {
221
+ result.set(nodeId, distance);
222
+ }
223
+ }
224
+
225
+ return result;
226
+ }
227
+
228
+ /**
229
+ * All-pairs shortest paths using repeated Dijkstra
230
+ * Note: For dense graphs, consider Floyd-Warshall algorithm instead
231
+ */
232
+ export function allPairsShortestPath(graph: Graph): Map<NodeId, Map<NodeId, number>> {
233
+ const results = new Map<NodeId, Map<NodeId, number>>();
234
+
235
+ for (const node of graph.nodes()) {
236
+ const distances = singleSourceShortestPath(graph, node.id);
237
+ results.set(node.id, distances);
238
+ }
239
+
240
+ return results;
241
+ }
242
+
243
+ /**
244
+ * Reconstruct path from predecessor map
245
+ */
246
+ function reconstructPath(target: NodeId, previous: Map<NodeId, NodeId | null>): NodeId[] {
247
+ const path: NodeId[] = [];
248
+ let current: NodeId | null = target;
249
+
250
+ while (current !== null) {
251
+ path.unshift(current);
252
+ current = previous.get(current) ?? null;
253
+ }
254
+
255
+ return path;
256
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shortest path algorithms
3
+ */
4
+
5
+ export type {BellmanFordOptions, BellmanFordResult} from "./bellman-ford.js";
6
+ export {bellmanFord, bellmanFordPath, hasNegativeCycle} from "./bellman-ford.js";
7
+ export {allPairsShortestPath, dijkstra, dijkstraPath, singleSourceShortestPath} from "./dijkstra.js";
@@ -0,0 +1,236 @@
1
+ import type {Graph} from "../../core/graph.js";
2
+ import type {NodeId, ShortestPathResult, TraversalOptions, TraversalResult} from "../../types/index.js";
3
+
4
+ /**
5
+ * Breadth-First Search (BFS) implementation
6
+ *
7
+ * Explores graph level by level from a starting node. Guarantees shortest path
8
+ * in unweighted graphs and can be used for various graph analysis tasks.
9
+ */
10
+
11
+ /**
12
+ * Perform breadth-first search starting from a given node
13
+ */
14
+ export function breadthFirstSearch(
15
+ graph: Graph,
16
+ startNode: NodeId,
17
+ options: TraversalOptions = {},
18
+ ): TraversalResult {
19
+ if (!graph.hasNode(startNode)) {
20
+ throw new Error(`Start node ${String(startNode)} not found in graph`);
21
+ }
22
+
23
+ const visited = new Set<NodeId>();
24
+ const queue: {node: NodeId, level: number}[] = [];
25
+ const order: NodeId[] = [];
26
+ const tree = new Map<NodeId, NodeId | null>();
27
+
28
+ // Initialize with start node
29
+ queue.push({node: startNode, level: 0});
30
+ visited.add(startNode);
31
+ tree.set(startNode, null);
32
+
33
+ while (queue.length > 0) {
34
+ const current = queue.shift();
35
+ if (!current) {
36
+ break;
37
+ }
38
+
39
+ order.push(current.node);
40
+
41
+ // Call visitor callback if provided
42
+ if (options.visitCallback) {
43
+ options.visitCallback(current.node, current.level);
44
+ }
45
+
46
+ // Early termination if target found
47
+ if (options.targetNode && current.node === options.targetNode) {
48
+ break;
49
+ }
50
+
51
+ // Explore neighbors
52
+ for (const neighbor of graph.neighbors(current.node)) {
53
+ if (!visited.has(neighbor)) {
54
+ visited.add(neighbor);
55
+ tree.set(neighbor, current.node);
56
+ queue.push({node: neighbor, level: current.level + 1});
57
+ }
58
+ }
59
+ }
60
+
61
+ return {visited, order, tree};
62
+ }
63
+
64
+ /**
65
+ * Find shortest path between two nodes in an unweighted graph using BFS
66
+ */
67
+ export function shortestPathBFS(
68
+ graph: Graph,
69
+ source: NodeId,
70
+ target: NodeId,
71
+ ): ShortestPathResult | null {
72
+ if (!graph.hasNode(source)) {
73
+ throw new Error(`Source node ${String(source)} not found in graph`);
74
+ }
75
+
76
+ if (!graph.hasNode(target)) {
77
+ throw new Error(`Target node ${String(target)} not found in graph`);
78
+ }
79
+
80
+ // Special case: source equals target
81
+ if (source === target) {
82
+ return {
83
+ distance: 0,
84
+ path: [source],
85
+ predecessor: new Map([[source, null]]),
86
+ };
87
+ }
88
+
89
+ const visited = new Set<NodeId>();
90
+ const queue: {node: NodeId, distance: number}[] = [];
91
+ const predecessor = new Map<NodeId, NodeId | null>();
92
+
93
+ // Initialize BFS
94
+ queue.push({node: source, distance: 0});
95
+ visited.add(source);
96
+ predecessor.set(source, null);
97
+
98
+ while (queue.length > 0) {
99
+ const current = queue.shift();
100
+ if (!current) {
101
+ break;
102
+ }
103
+
104
+ // Target found
105
+ if (current.node === target) {
106
+ const path = reconstructPath(target, predecessor);
107
+ return {
108
+ distance: current.distance,
109
+ path,
110
+ predecessor,
111
+ };
112
+ }
113
+
114
+ // Explore neighbors
115
+ for (const neighbor of graph.neighbors(current.node)) {
116
+ if (!visited.has(neighbor)) {
117
+ visited.add(neighbor);
118
+ predecessor.set(neighbor, current.node);
119
+ queue.push({node: neighbor, distance: current.distance + 1});
120
+ }
121
+ }
122
+ }
123
+
124
+ // No path found
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * Find shortest paths from source to all reachable nodes using BFS
130
+ */
131
+ export function singleSourceShortestPathBFS(
132
+ graph: Graph,
133
+ source: NodeId,
134
+ ): Map<NodeId, ShortestPathResult> {
135
+ if (!graph.hasNode(source)) {
136
+ throw new Error(`Source node ${String(source)} not found in graph`);
137
+ }
138
+
139
+ const results = new Map<NodeId, ShortestPathResult>();
140
+ const visited = new Set<NodeId>();
141
+ const queue: {node: NodeId, distance: number}[] = [];
142
+ const predecessor = new Map<NodeId, NodeId | null>();
143
+
144
+ // Initialize BFS
145
+ queue.push({node: source, distance: 0});
146
+ visited.add(source);
147
+ predecessor.set(source, null);
148
+
149
+ while (queue.length > 0) {
150
+ const current = queue.shift();
151
+ if (!current) {
152
+ break;
153
+ }
154
+
155
+ // Store result for current node
156
+ const path = reconstructPath(current.node, predecessor);
157
+ results.set(current.node, {
158
+ distance: current.distance,
159
+ path,
160
+ predecessor: new Map(predecessor),
161
+ });
162
+
163
+ // Explore neighbors
164
+ for (const neighbor of graph.neighbors(current.node)) {
165
+ if (!visited.has(neighbor)) {
166
+ visited.add(neighbor);
167
+ predecessor.set(neighbor, current.node);
168
+ queue.push({node: neighbor, distance: current.distance + 1});
169
+ }
170
+ }
171
+ }
172
+
173
+ return results;
174
+ }
175
+
176
+ /**
177
+ * Check if the graph is bipartite using BFS coloring
178
+ */
179
+ export function isBipartite(graph: Graph): boolean {
180
+ if (graph.isDirected) {
181
+ throw new Error("Bipartite test requires an undirected graph");
182
+ }
183
+
184
+ const color = new Map<NodeId, 0 | 1>();
185
+ const visited = new Set<NodeId>();
186
+
187
+ // Check each connected component
188
+ for (const node of graph.nodes()) {
189
+ if (!visited.has(node.id)) {
190
+ const queue: NodeId[] = [node.id];
191
+ color.set(node.id, 0);
192
+ visited.add(node.id);
193
+
194
+ while (queue.length > 0) {
195
+ const current = queue.shift();
196
+ if (!current) {
197
+ break;
198
+ }
199
+
200
+ const currentColor = color.get(current);
201
+ if (currentColor === undefined) {
202
+ continue;
203
+ }
204
+
205
+ for (const neighbor of graph.neighbors(current)) {
206
+ if (!visited.has(neighbor)) {
207
+ // Color with opposite color
208
+ color.set(neighbor, currentColor === 0 ? 1 : 0);
209
+ visited.add(neighbor);
210
+ queue.push(neighbor);
211
+ } else if (color.get(neighbor) === currentColor) {
212
+ // Same color as current node - not bipartite
213
+ return false;
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ return true;
221
+ }
222
+
223
+ /**
224
+ * Reconstruct path from predecessor map
225
+ */
226
+ function reconstructPath(target: NodeId, predecessor: Map<NodeId, NodeId | null>): NodeId[] {
227
+ const path: NodeId[] = [];
228
+ let current: NodeId | null = target;
229
+
230
+ while (current !== null) {
231
+ path.unshift(current);
232
+ current = predecessor.get(current) ?? null;
233
+ }
234
+
235
+ return path;
236
+ }