@graphty/algorithms
Version:
Graph algorithms library for browser environments implemented in TypeScript
259 lines • 8.52 kB
JavaScript
import { Graph } from "../../core/graph.js";
/**
* Perform depth-first search starting from a given node
*/
export function depthFirstSearch(graph, startNode, options = {}) {
if (!graph.hasNode(startNode)) {
throw new Error(`Start node ${String(startNode)} not found in graph`);
}
const visited = new Set();
const order = [];
const tree = new Map();
if (options.recursive) {
dfsRecursive(graph, startNode, visited, order, tree, options, 0);
}
else {
dfsIterative(graph, startNode, visited, order, tree, options);
}
return { visited, order, tree };
}
/**
* Iterative DFS implementation (safer for browsers)
*/
function dfsIterative(graph, startNode, visited, order, tree, options) {
if (options.preOrder === false) {
// For post-order, use a simpler recursive approach to ensure correctness
dfsRecursive(graph, startNode, visited, order, tree, options, 0, null);
return;
}
const stack = [];
stack.push({ node: startNode, parent: null, depth: 0 });
while (stack.length > 0) {
const current = stack.pop();
if (!current) {
break;
}
if (!visited.has(current.node)) {
visited.add(current.node);
tree.set(current.node, current.parent);
// Pre-order processing
order.push(current.node);
// Call visitor callback if provided
if (options.visitCallback) {
options.visitCallback(current.node, current.depth);
}
// Early termination if target found
if (options.targetNode && current.node === options.targetNode) {
break;
}
// Add neighbors to stack in reverse order to maintain left-to-right traversal
const neighbors = Array.from(graph.neighbors(current.node));
for (let i = neighbors.length - 1; i >= 0; i--) {
const neighbor = neighbors[i];
if (neighbor !== undefined && !visited.has(neighbor)) {
stack.push({ node: neighbor, parent: current.node, depth: current.depth + 1 });
}
}
}
}
}
/**
* Recursive DFS implementation
*/
function dfsRecursive(graph, node, visited, order, tree, options, depth, parent = null) {
visited.add(node);
tree.set(node, parent);
// Pre-order processing
if (options.preOrder !== false) {
order.push(node);
if (options.visitCallback) {
options.visitCallback(node, depth);
}
// Early termination if target found
if (options.targetNode && node === options.targetNode) {
return;
}
}
// Recursively visit neighbors
for (const neighbor of Array.from(graph.neighbors(node))) {
if (!visited.has(neighbor)) {
dfsRecursive(graph, neighbor, visited, order, tree, options, depth + 1, node);
}
}
// Post-order processing
if (options.preOrder === false) {
order.push(node);
if (options.visitCallback) {
options.visitCallback(node, depth);
}
}
}
/**
* Detect cycles in a graph using DFS
*/
export function hasCycleDFS(graph) {
const visited = new Set();
// Check each unvisited node for cycles
for (const node of Array.from(graph.nodes())) {
if (!visited.has(node.id)) {
if (graph.isDirected) {
const recursionStack = new Set();
if (hasCycleUtilDirected(graph, node.id, visited, recursionStack)) {
return true;
}
}
else {
if (hasCycleUtilUndirected(graph, node.id, visited, null)) {
return true;
}
}
}
}
return false;
}
/**
* Utility function for cycle detection in directed graphs
*/
function hasCycleUtilDirected(graph, node, visited, recursionStack) {
visited.add(node);
recursionStack.add(node);
// Check all neighbors
for (const neighbor of Array.from(graph.neighbors(node))) {
if (!visited.has(neighbor)) {
if (hasCycleUtilDirected(graph, neighbor, visited, recursionStack)) {
return true;
}
}
else if (recursionStack.has(neighbor)) {
// Back edge found - cycle detected
return true;
}
}
recursionStack.delete(node);
return false;
}
/**
* Utility function for cycle detection in undirected graphs
*/
function hasCycleUtilUndirected(graph, node, visited, parent) {
visited.add(node);
// Check all neighbors
for (const neighbor of Array.from(graph.neighbors(node))) {
if (!visited.has(neighbor)) {
if (hasCycleUtilUndirected(graph, neighbor, visited, node)) {
return true;
}
}
else if (neighbor !== parent) {
// Found a visited node that's not the parent - cycle detected
return true;
}
}
return false;
}
/**
* Topological sorting using DFS (for directed acyclic graphs)
*/
export function topologicalSort(graph) {
if (!graph.isDirected) {
throw new Error("Topological sort requires a directed graph");
}
// First check if graph has cycles
if (hasCycleDFS(graph)) {
return null; // Cannot topologically sort a graph with cycles
}
const visited = new Set();
const stack = [];
// Perform DFS from each unvisited node
for (const node of Array.from(graph.nodes())) {
if (!visited.has(node.id)) {
topologicalSortUtil(graph, node.id, visited, stack);
}
}
// Return nodes in reverse order of finishing times
return stack.reverse();
}
/**
* Utility function for topological sorting
*/
function topologicalSortUtil(graph, node, visited, stack) {
visited.add(node);
// Visit all neighbors first
for (const neighbor of Array.from(graph.neighbors(node))) {
if (!visited.has(neighbor)) {
topologicalSortUtil(graph, neighbor, visited, stack);
}
}
// Add current node to stack after visiting all neighbors
stack.push(node);
}
/**
* Find strongly connected components using DFS (for directed graphs)
*/
export function findStronglyConnectedComponents(graph) {
if (!graph.isDirected) {
throw new Error("Strongly connected components require a directed graph");
}
const visited = new Set();
const finishOrder = [];
// Step 1: Get nodes in order of finishing times
for (const node of Array.from(graph.nodes())) {
if (!visited.has(node.id)) {
dfsFinishOrder(graph, node.id, visited, finishOrder);
}
}
// Step 2: Create transpose graph (reverse all edges)
const transposeGraph = createTransposeGraph(graph);
// Step 3: DFS on transpose graph in reverse finish order
const visited2 = new Set();
const components = [];
for (let i = finishOrder.length - 1; i >= 0; i--) {
const node = finishOrder[i];
if (node !== undefined && !visited2.has(node)) {
const component = [];
dfsCollectComponent(transposeGraph, node, visited2, component);
components.push(component);
}
}
return components;
}
/**
* DFS to record finish order
*/
function dfsFinishOrder(graph, node, visited, finishOrder) {
visited.add(node);
for (const neighbor of Array.from(graph.neighbors(node))) {
if (!visited.has(neighbor)) {
dfsFinishOrder(graph, neighbor, visited, finishOrder);
}
}
finishOrder.push(node);
}
/**
* DFS to collect nodes in a component
*/
function dfsCollectComponent(graph, node, visited, component) {
visited.add(node);
component.push(node);
for (const neighbor of Array.from(graph.neighbors(node))) {
if (!visited.has(neighbor)) {
dfsCollectComponent(graph, neighbor, visited, component);
}
}
}
/**
* Create transpose (reverse) of a directed graph
*/
function createTransposeGraph(graph) {
const transpose = new Graph({ directed: true });
// Add all nodes
for (const node of Array.from(graph.nodes())) {
transpose.addNode(node.id, node.data);
}
// Add reverse edges
for (const edge of Array.from(graph.edges())) {
transpose.addEdge(edge.target, edge.source, edge.weight, edge.data);
}
return transpose;
}
//# sourceMappingURL=dfs.js.map