@graphty/algorithms
Version:
Graph algorithms library for browser environments implemented in TypeScript
279 lines • 8.84 kB
JavaScript
import { bfsAugmentingPath } from "../algorithms/traversal/bfs-variants.js";
import { graphToMap } from "../utils/graph-converters.js";
/**
* Create residual graph from original graph
*/
function createResidualGraph(graph) {
const residual = new Map();
for (const [u, neighbors] of graph) {
residual.set(u, new Map());
for (const [v, capacity] of neighbors) {
const uResidualNeighbors = residual.get(u);
if (uResidualNeighbors) {
uResidualNeighbors.set(v, capacity);
}
}
}
return residual;
}
/**
* Find augmenting path using DFS
*/
function findAugmentingPathDFS(residualGraph, source, sink) {
const visited = new Set();
const path = [];
function dfs(node) {
if (node === sink) {
path.push(node);
return true;
}
visited.add(node);
path.push(node);
const neighbors = residualGraph.get(node);
if (neighbors) {
for (const [neighbor, capacity] of neighbors) {
if (!visited.has(neighbor) && capacity > 0) {
if (dfs(neighbor)) {
return true;
}
}
}
}
path.pop();
return false;
}
if (dfs(source)) {
return path;
}
return null;
}
/**
* Update flow along an augmenting path
*/
function updateFlow(residualGraph, flowGraph, originalGraph, path, pathFlow) {
for (let i = 0; i < path.length - 1; i++) {
const u = path[i];
const v = path[i + 1];
if (!u || !v) {
continue;
}
// Update residual graph
const uEdges = residualGraph.get(u);
if (uEdges) {
const currentCapacity = uEdges.get(v);
if (currentCapacity !== undefined) {
uEdges.set(v, currentCapacity - pathFlow);
}
}
// Add reverse edge
if (!residualGraph.has(v)) {
residualGraph.set(v, new Map());
}
const vEdges = residualGraph.get(v);
if (vEdges) {
vEdges.set(u, (vEdges.get(u) ?? 0) + pathFlow);
}
// Update flow graph
if (originalGraph.get(u)?.has(v)) {
const uFlowEdges = flowGraph.get(u);
if (uFlowEdges) {
const currentFlow = uFlowEdges.get(v) ?? 0;
uFlowEdges.set(v, currentFlow + pathFlow);
}
}
else if (originalGraph.get(v)?.has(u)) {
// This is a reverse flow
const vFlowEdges = flowGraph.get(v);
if (vFlowEdges) {
const currentFlow = vFlowEdges.get(u) ?? 0;
vFlowEdges.set(u, currentFlow - pathFlow);
}
}
}
}
/**
* Find minimum cut from source side
*/
function findMinCut(residualGraph, source) {
// Find all reachable nodes from source in residual graph
const sourceSet = new Set();
const queue = [source];
sourceSet.add(source);
while (queue.length > 0) {
const current = queue.shift();
if (!current) {
continue;
}
const neighbors = residualGraph.get(current);
if (neighbors) {
for (const [neighbor, capacity] of neighbors) {
if (!sourceSet.has(neighbor) && capacity > 0) {
sourceSet.add(neighbor);
queue.push(neighbor);
}
}
}
}
// All other nodes are in sink set
const sinkSet = new Set();
for (const node of residualGraph.keys()) {
if (!sourceSet.has(node)) {
sinkSet.add(node);
}
}
// Find cut edges
const cutEdges = [];
for (const u of sourceSet) {
const neighbors = residualGraph.get(u);
if (neighbors) {
for (const v of neighbors.keys()) {
if (sinkSet.has(v)) {
cutEdges.push([u, v]);
}
}
}
}
return { source: sourceSet, sink: sinkSet, edges: cutEdges };
}
/**
* Core max flow algorithm implementation
*/
function maxFlowCore(graph, source, sink, findPath) {
if (!graph.has(source) || !graph.has(sink)) {
return { maxFlow: 0, flowGraph: new Map() };
}
// Create residual graph
const residualGraph = createResidualGraph(graph);
const flowGraph = new Map();
// Initialize flow graph
for (const [u, neighbors] of graph) {
flowGraph.set(u, new Map());
for (const v of neighbors.keys()) {
const uFlowNeighbors = flowGraph.get(u);
if (uFlowNeighbors) {
uFlowNeighbors.set(v, 0);
}
}
}
let maxFlow = 0;
let pathResult = findPath(residualGraph, source, sink);
while (pathResult !== null) {
const { path, pathCapacity } = pathResult;
// Update flow
updateFlow(residualGraph, flowGraph, graph, path, pathCapacity);
maxFlow += pathCapacity;
// Find next path
pathResult = findPath(residualGraph, source, sink);
}
// Find minimum cut
const minCut = findMinCut(residualGraph, source);
return { maxFlow, flowGraph, minCut };
}
/**
* Internal implementation of Ford-Fulkerson algorithm using DFS
*/
function fordFulkersonImpl(graph, source, sink) {
// Adapter function for DFS path finding
const findPathDFS = (residual, src, snk) => {
const path = findAugmentingPathDFS(residual, src, snk);
if (!path) {
return null;
}
// Calculate path capacity
let pathCapacity = Infinity;
for (let i = 0; i < path.length - 1; i++) {
const u = path[i];
const v = path[i + 1];
if (u && v) {
const capacity = residual.get(u)?.get(v);
if (capacity !== undefined) {
pathCapacity = Math.min(pathCapacity, capacity);
}
}
}
return { path, pathCapacity };
};
return maxFlowCore(graph, source, sink, findPathDFS);
}
/**
* Internal implementation of Edmonds-Karp algorithm
*/
function edmondsKarpImpl(graph, source, sink) {
// Use BFS variant for path finding
const findPathBFS = (residual, src, snk) => {
return bfsAugmentingPath(residual, src, snk);
};
return maxFlowCore(graph, source, sink, findPathBFS);
}
/**
* Utility function to create a flow network for bipartite matching
*/
export function createBipartiteFlowNetwork(leftNodes, rightNodes, edges) {
const graph = new Map();
const source = "__source__";
const sink = "__sink__";
// Add source connections to left nodes
graph.set(source, new Map());
for (const left of leftNodes) {
const sourceNeighbors = graph.get(source);
if (sourceNeighbors) {
sourceNeighbors.set(left, 1);
}
graph.set(left, new Map());
}
// Add edges between left and right
for (const [left, right] of edges) {
if (!graph.has(left)) {
graph.set(left, new Map());
}
const leftNeighbors = graph.get(left);
if (leftNeighbors) {
leftNeighbors.set(right, 1);
}
}
// Add right node connections to sink
for (const right of rightNodes) {
if (!graph.has(right)) {
graph.set(right, new Map());
}
const rightNeighbors = graph.get(right);
if (rightNeighbors) {
rightNeighbors.set(sink, 1);
}
}
graph.set(sink, new Map());
return { graph, source, sink };
}
/**
* Ford-Fulkerson algorithm using DFS for finding augmenting paths
*
* @param graph - Adjacency list representation with capacities - accepts Graph class or Map
* @param source - Source node
* @param sink - Sink node
* @returns Maximum flow value and flow graph
*
* Time Complexity: O(E * f) where f is the maximum flow
* Space Complexity: O(V + E)
*/
export function fordFulkerson(graph, source, sink) {
// Convert Graph to Map representation
const graphMap = graphToMap(graph);
return fordFulkersonImpl(graphMap, source, sink);
}
/**
* Edmonds-Karp algorithm (Ford-Fulkerson with BFS)
* More efficient implementation with better time complexity
*
* @param graph - Adjacency list representation with capacities - accepts Graph class or Map
* @param source - Source node
* @param sink - Sink node
* @returns Maximum flow value and flow graph
*
* Time Complexity: O(V * E²)
*/
export function edmondsKarp(graph, source, sink) {
// Convert Graph to Map representation if needed
const graphMap = graph instanceof Map ? graph : graphToMap(graph);
return edmondsKarpImpl(graphMap, source, sink);
}
//# sourceMappingURL=ford-fulkerson.js.map