UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

234 lines 8.03 kB
import { UnionFind } from "../../data-structures/union-find.js"; /** * Connected components algorithms * * Finds connected components in undirected graphs and strongly connected * components in directed graphs using various efficient algorithms. */ /** * Find connected components in an undirected graph using Union-Find */ export function connectedComponents(graph) { if (graph.isDirected) { throw new Error("Connected components algorithm requires an undirected graph. Use stronglyConnectedComponents for directed graphs."); } const nodes = Array.from(graph.nodes()).map((node) => node.id); if (nodes.length === 0) { return []; } const unionFind = new UnionFind(nodes); // Union connected nodes for (const edge of Array.from(graph.edges())) { unionFind.union(edge.source, edge.target); } return unionFind.getAllComponents(); } /** * Find connected components using DFS (alternative implementation) */ export function connectedComponentsDFS(graph) { if (graph.isDirected) { throw new Error("Connected components algorithm requires an undirected graph"); } const visited = new Set(); const components = []; for (const node of Array.from(graph.nodes())) { if (!visited.has(node.id)) { const component = []; dfsComponent(graph, node.id, visited, component); components.push(component); } } return components; } /** * DFS helper for connected components */ function dfsComponent(graph, nodeId, visited, component) { visited.add(nodeId); component.push(nodeId); for (const neighbor of Array.from(graph.neighbors(nodeId))) { if (!visited.has(neighbor)) { dfsComponent(graph, neighbor, visited, component); } } } /** * Find the number of connected components */ export function numberOfConnectedComponents(graph) { return connectedComponents(graph).length; } /** * Check if the graph is connected (has exactly one connected component) */ export function isConnected(graph) { return numberOfConnectedComponents(graph) <= 1; } /** * Find the largest connected component */ export function largestConnectedComponent(graph) { const components = connectedComponents(graph); if (components.length === 0) { return []; } return components.reduce((largest, current) => current.length > largest.length ? current : largest); } /** * Get the connected component containing a specific node */ export function getConnectedComponent(graph, nodeId) { if (!graph.hasNode(nodeId)) { throw new Error(`Node ${String(nodeId)} not found in graph`); } if (graph.isDirected) { throw new Error("Connected components algorithm requires an undirected graph"); } const visited = new Set(); const component = []; dfsComponent(graph, nodeId, visited, component); return component; } /** * Find strongly connected components using Tarjan's algorithm */ export function stronglyConnectedComponents(graph) { if (!graph.isDirected) { throw new Error("Strongly connected components require a directed graph"); } const nodes = Array.from(graph.nodes()).map((node) => node.id); const components = []; const indices = new Map(); const lowLinks = new Map(); const onStack = new Set(); const stack = []; let index = 0; function tarjanSCC(nodeId) { // Set the depth index for this node indices.set(nodeId, index); lowLinks.set(nodeId, index); index++; stack.push(nodeId); onStack.add(nodeId); // Consider successors for (const neighbor of Array.from(graph.neighbors(nodeId))) { if (!indices.has(neighbor)) { // Successor has not yet been visited; recurse on it tarjanSCC(neighbor); const nodeLL = lowLinks.get(nodeId) ?? 0; const neighborLL = lowLinks.get(neighbor) ?? 0; lowLinks.set(nodeId, Math.min(nodeLL, neighborLL)); } else if (onStack.has(neighbor)) { // Successor is in stack and hence in the current SCC const nodeLL = lowLinks.get(nodeId) ?? 0; const neighborIndex = indices.get(neighbor) ?? 0; lowLinks.set(nodeId, Math.min(nodeLL, neighborIndex)); } } // If nodeId is a root node, pop the stack and create an SCC const nodeIndex = indices.get(nodeId) ?? 0; const nodeLowLink = lowLinks.get(nodeId) ?? 0; if (nodeLowLink === nodeIndex) { const component = []; let w; do { const popped = stack.pop(); if (popped === undefined) { break; } w = popped; onStack.delete(w); component.push(w); } while (w !== nodeId); components.push(component); } } for (const nodeId of nodes) { if (!indices.has(nodeId)) { tarjanSCC(nodeId); } } return components; } /** * Check if a directed graph is strongly connected */ export function isStronglyConnected(graph) { if (!graph.isDirected) { throw new Error("Strong connectivity check requires a directed graph"); } const components = stronglyConnectedComponents(graph); return components.length <= 1; } /** * Find weakly connected components in a directed graph * (treat the graph as undirected for connectivity) */ export function weaklyConnectedComponents(graph) { if (!graph.isDirected) { throw new Error("Weakly connected components are for directed graphs. Use connectedComponents for undirected graphs."); } const nodes = Array.from(graph.nodes()).map((node) => node.id); if (nodes.length === 0) { return []; } const unionFind = new UnionFind(nodes); // Union nodes connected by edges (ignore direction) for (const edge of Array.from(graph.edges())) { unionFind.union(edge.source, edge.target); } return unionFind.getAllComponents(); } /** * Check if a directed graph is weakly connected */ export function isWeaklyConnected(graph) { if (!graph.isDirected) { throw new Error("Weak connectivity check requires a directed graph"); } return weaklyConnectedComponents(graph).length <= 1; } /** * Find condensation graph (quotient graph of strongly connected components) */ export function condensationGraph(graph) { if (!graph.isDirected) { throw new Error("Condensation graph requires a directed graph"); } const components = stronglyConnectedComponents(graph); const componentMap = new Map(); const condensedGraph = new graph.constructor({ directed: true }); // Map each node to its component index for (let i = 0; i < components.length; i++) { const component = components[i]; if (component) { for (const nodeId of component) { componentMap.set(nodeId, i); } // Add component as a node in condensed graph condensedGraph.addNode(i); } } // Add edges between components const addedEdges = new Set(); for (const edge of Array.from(graph.edges())) { const sourceComponent = componentMap.get(edge.source); const targetComponent = componentMap.get(edge.target); if (sourceComponent !== undefined && targetComponent !== undefined && sourceComponent !== targetComponent) { const edgeKey = `${String(sourceComponent)}-${String(targetComponent)}`; if (!addedEdges.has(edgeKey)) { condensedGraph.addEdge(sourceComponent, targetComponent); addedEdges.add(edgeKey); } } } return { condensedGraph, componentMap, components, }; } //# sourceMappingURL=connected.js.map