|
| 1 | +/** |
| 2 | + * Condensation via Kosaraju's algorithm |
| 3 | + * |
| 4 | + * @see https://cp-algorithms.com/graph/strongly-connected-components.html |
| 5 | + * This file computes the strongly connected components (SCCs) and the |
| 6 | + * condensation (component DAG) for a directed graph given as an edge list. |
| 7 | + * It uses the project's existing Kosaraju implementation (Kosaraju.js). |
| 8 | + */ |
| 9 | + |
| 10 | +import { kosaraju } from './Kosaraju.js' |
| 11 | + |
| 12 | +/** |
| 13 | + * Compute SCCs and the condensation graph (component DAG) for a directed graph. |
| 14 | + * @param {Array<Array<number>>} graph edges as [u, v] |
| 15 | + * @returns {{sccs: Array<Array<number>>, condensation: Array<Array<number>>}} |
| 16 | + */ |
| 17 | + |
| 18 | +function condensation(graph) { |
| 19 | + // Using Kosaraju implementation to compute SCCs |
| 20 | + const sccs = kosaraju(graph) |
| 21 | + |
| 22 | + // Build adjacency map to include isolated nodes |
| 23 | + const g = {} |
| 24 | + const addNode = (n) => { |
| 25 | + if (!(n in g)) g[n] = new Set() |
| 26 | + } |
| 27 | + for (const [u, v] of graph) { |
| 28 | + addNode(u) |
| 29 | + addNode(v) |
| 30 | + g[u].add(v) |
| 31 | + } |
| 32 | + |
| 33 | + // Map node -> component index (string keys) |
| 34 | + const compIndex = {} |
| 35 | + for (let i = 0; i < sccs.length; i++) { |
| 36 | + for (const node of sccs[i]) compIndex[String(node)] = i |
| 37 | + } |
| 38 | + |
| 39 | + // Build condensation adjacency list (component graph) |
| 40 | + const condensationSets = Array.from({ length: sccs.length }, () => new Set()) |
| 41 | + for (const u of Object.keys(g)) { |
| 42 | + const ci = compIndex[u] |
| 43 | + if (ci === undefined) continue |
| 44 | + for (const v of g[u]) { |
| 45 | + const cj = compIndex[String(v)] |
| 46 | + if (cj === undefined) continue |
| 47 | + if (ci !== cj) condensationSets[ci].add(cj) |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + const condensation = condensationSets.map((s) => Array.from(s)) |
| 52 | + |
| 53 | + return { sccs, condensation, compIndex } |
| 54 | +} |
| 55 | + |
| 56 | +export { condensation } |
| 57 | + |
| 58 | +/** |
| 59 | + * Time and Space Complexity |
| 60 | + * |
| 61 | + * Kosaraju's algorithm (finding SCCs) and condensation graph construction: |
| 62 | + * |
| 63 | + * - Time complexity: O(V + E) |
| 64 | + * - Building the adjacency list from the input edge list: O(E) |
| 65 | + * - Running Kosaraju's two DFS passes over all vertices and edges: O(V + E) |
| 66 | + * - Building the component index and condensation edges: O(V + E) |
| 67 | + * Overall: O(V + E), where V is the number of vertices and E is the number of edges. |
| 68 | + * |
| 69 | + * - Space complexity: O(V + E) |
| 70 | + * - Adjacency lists (graph): O(V + E) |
| 71 | + * - Kosaraju bookkeeping (stacks, visited sets, topo order, sccs, compIndex): O(V) |
| 72 | + * - Condensation adjacency structures: O(C + E') where C <= V and E' <= E, so |
| 73 | + * bounded by O(V + E). |
| 74 | + * Overall: O(V + E). |
| 75 | + * |
| 76 | + * Notes: |
| 77 | + * - Recursion may use O(V) call-stack space in the worst case during DFS. |
| 78 | + * - Constants depend on the data-structures used (Sets/Arrays), but the |
| 79 | + * asymptotic bounds above hold for this implementation. |
| 80 | + */ |
| 81 | + |
| 82 | +/** |
| 83 | + * Visual diagram and usage |
| 84 | + * |
| 85 | + * Consider the directed edge list: |
| 86 | + * |
| 87 | + * edges = [ |
| 88 | + * [1,2], [2,3], [3,1], // cycle among 1,2,3 |
| 89 | + * [2,4], // edge from the first cycle to node 4 |
| 90 | + * [4,5], [5,6], [6,4] // cycle among 4,5,6 |
| 91 | + * ] |
| 92 | + * |
| 93 | + * Original graph (conceptual): |
| 94 | + * |
| 95 | + * 1 --> 2 --> 4 --> 5 |
| 96 | + * ^ | | |
| 97 | + * | v v |
| 98 | + * 3 <-- - 6 |
| 99 | + * |
| 100 | + * Strongly connected components (SCCs) in this graph: |
| 101 | + * |
| 102 | + * SCC A: [1, 2, 3] // a cycle 1->2->3->1 |
| 103 | + * SCC B: [4, 5, 6] // a cycle 4->5->6->4 |
| 104 | + * |
| 105 | + * Condensation graph (component DAG): |
| 106 | + * |
| 107 | + * [A] ---> [B] |
| 108 | + * |
| 109 | + * Where each node in the condensation is a whole SCC from the original graph. |
| 110 | + * |
| 111 | + * Example return values from `condensation(edges)`: |
| 112 | + * |
| 113 | + * { |
| 114 | + * sccs: [[1,2,3], [4,5,6]], |
| 115 | + * condensation: [[1], []], // component 0 has an edge to component 1 |
| 116 | + * compIndex: { '1': 0, '2': 0, '3': 0, '4': 1, '5': 1, '6': 1 } |
| 117 | + * } |
| 118 | + * |
| 119 | + * Usage: |
| 120 | + * |
| 121 | + * const edges = [[1,2],[2,3],[3,1],[2,4],[4,5],[5,6],[6,4]] |
| 122 | + * const { sccs, condensation, compIndex } = condensation(edges) |
| 123 | + * |
| 124 | + * Interpretation summary: |
| 125 | + * - `sccs` is an array of components; each component is an array of original nodes. |
| 126 | + * - `condensation` is the adjacency list of the component DAG; indices refer to `sccs`. |
| 127 | + * - `compIndex` maps an original node (string key) to its component index. |
| 128 | + * |
| 129 | + * This ASCII diagram and mapping should make it easy to visualize how the |
| 130 | + * condensation function groups cycles and produces a DAG of components. |
| 131 | + */ |
0 commit comments