UNPKG

@cedoor/nfa

Version:

TypeScript implementation of some network flow algorithms.

678 lines (664 loc) 28.4 kB
/** * @module @cedoor/nfa * @version 0.1.1 * @file TypeScript implementation of some network flow algorithms. * @copyright Omar Desogus 2020 * @license MIT * @see [Github]{@link https://github.com/cedoor/network-flow-algorithms} */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.nfa = {})); }(this, (function (exports) { 'use strict'; /** * The Node class contains the methods to add, remove and * modify the arcs of the node in a constant time. Each node * contains id, balance and a map of outgoing arcs. */ var Node = /** @class */ (function () { function Node(id, balance, arcs) { if (arcs === void 0) { arcs = []; } this.id = id; this.balance = balance; this.arcs = new Map(arcs.map(function (arc) { return [arc.head, arc]; })); } /** * Adds an arc in the node. * @param {Arc} The arc to add. */ Node.prototype.addArc = function (arc) { if (this.hasArc(arc.head)) { throw Error("Arc with head " + arc.head + " already exists"); } this.arcs.set(arc.head, arc); }; /** * Removes an arc from the node. * @param {number} The head of arc the to remove. */ Node.prototype.removeArc = function (head) { if (!this.hasArc(head)) { throw Error("Arc with head " + head + " does not exists"); } this.arcs.delete(head); }; /** * Returns an arc from the node. * @param {number} The head of the arc to return. * @returns {Arc} The arc to return. */ Node.prototype.getArc = function (head) { var arc = this.arcs.get(head); if (!arc) { throw Error("Arc with head " + head + " does not exists"); } return arc; }; /** * Checks if there is an arc in the node. * @param {number} The head of the arc to check. * @param {boolean} True if the arc exists, false otherwise. */ Node.prototype.hasArc = function (head) { return this.arcs.has(head); }; /** * Returns all the arcs of the node. * @returns {Arc[]} The node arcs. */ Node.prototype.getArcs = function () { return Array.from(this.arcs.values()); }; /** * Returns the number of the arcs of the node. * @returns {number} The number of the node arcs. */ Node.prototype.size = function () { return this.arcs.size; }; return Node; }()); /** * The Arc class contains the parameters of an arc in a * directed graph: head node, cost, capacity and flow. */ var Arc = /** @class */ (function () { function Arc(head, cost, capacity, flow) { if (flow === void 0) { flow = 0; } this.head = head; this.cost = cost; this.capacity = capacity; this.flow = flow; } return Arc; }()); /** * The Graph class is a data structure that allow you to create a directed * graph, in which to add, remove and modify nodes and arcs * in constant time. Nodes and arcs are stored in maps. */ var Graph = /** @class */ (function () { function Graph(graphData) { this.nodes = new Map(); if (graphData) { for (var _i = 0, graphData_1 = graphData; _i < graphData_1.length; _i++) { var node = graphData_1[_i]; var arcs = node.arcs ? node.arcs.map(function (arc) { return new Arc(arc.head, arc.cost, arc.capacity); }) : []; this.addNode(new Node(node.id, node.balance, arcs)); } } } /** * Adds a node in the graph. * @param {Node} The node to add. */ Graph.prototype.addNode = function (node) { if (this.hasNode(node.id)) { throw Error("Node with id " + node.id + " already exists"); } this.nodes.set(node.id, node); }; /** * Removes a node from the graph. * @param {number} The id of the node to remove. */ Graph.prototype.removeNode = function (id) { if (!this.hasNode(id)) { throw Error("Node with id " + id + " does not exists"); } this.nodes.delete(id); }; /** * Returns a node from the graph. * @param {number} The id of the node to return. * @returns {Node} The node to return. */ Graph.prototype.getNode = function (id) { var node = this.nodes.get(id); if (!node) { throw Error("Node with id " + id + " does not exists"); } return node; }; /** * Checks if there is a node in the graph. * @param {number} The id of the node to check. * @param {boolean} True if the node exists, false otherwise. */ Graph.prototype.hasNode = function (id) { return this.nodes.has(id); }; /** * Returns all the nodes of the graph. * @returns {Node[]} The graph nodes. */ Graph.prototype.getNodes = function () { return Array.from(this.nodes.values()); }; /** * Returns the number of the nodes of the graph. * @returns {number} The number of the graph nodes. */ Graph.prototype.size = function () { return this.nodes.size; }; /** * Returns an instance of the copy of the current graph. */ Graph.prototype.copy = function () { return new Graph(this.export()); }; /** * Checks the integrity of the graph. All the arcs * must have an existing head node. * @returns {number} True if the graph is correct, false otherwise. */ Graph.prototype.checkIntegrity = function () { for (var _i = 0, _a = this.getNodes(); _i < _a.length; _i++) { var node = _a[_i]; for (var _b = 0, _c = node.getArcs(); _b < _c.length; _b++) { var arc = _c[_b]; if (!this.hasNode(arc.head)) { return false; } } } return true; }; /** * Returns the graph data of the graph. * @returns {GraphData} The graph data. */ Graph.prototype.export = function () { var graphData = []; for (var _i = 0, _a = this.getNodes(); _i < _a.length; _i++) { var node = _a[_i]; var arcs = node.getArcs().map(function (arc) { return ({ head: arc.head, cost: arc.cost, capacity: arc.capacity, flow: arc.flow }); }); graphData.push({ id: node.id, balance: node.balance, arcs: arcs }); } return graphData; }; return Graph; }()); /** * The queue data structure allows you to perform FIFO * operations in constant time. You can enqueue, dequeue * and peek an item, and get the size of the queue. * JavaScript arrays have 'shift' and * 'push' methods but 'shift' take O(n) time. */ var Queue = /** @class */ (function () { function Queue() { this.front = 0; this.end = 0; this.store = {}; } /** * Adds an item to the end of the queue. * Time complexity: O(1). * @param {any} The value to store. */ Queue.prototype.enqueue = function (value) { this.store[this.end] = value; this.end++; }; /** * Removes an item from the queue and return its value. * Time complexity: O(1). * @return {any | undefined} The value stored in item. */ Queue.prototype.dequeue = function () { if (this.front === this.end) { return undefined; } var value = this.store[this.front]; delete this.store[this.front]; this.front++; return value; }; /** * Returns the current size of the queue. * Time complexity: O(1). * @return {number} Size of the queue. */ Queue.prototype.size = function () { return this.end - this.front; }; /** * Returns the item at front of the queue without dequeuing. * Time complexity: O(1). * @return {any | undefined} The value stored in the item. */ Queue.prototype.peek = function () { if (this.size() === 0) { return undefined; } return this.store[this.front]; }; return Queue; }()); /** * Retrieves the path from a node of the graph to the source node * using the predecessors map. If there is a cycle return the cycle path. * Time complexity: O(n). * @param {Map<number, number>} The predecessor nodes. * @param {number} The node to start from. * @returns {number[]} The path from a node to the source node or a cycle path. */ function retrievePath(predecessors, nodeId) { // Path starts from a node id. var pathSet = new Set([nodeId]); var nextNodeId = predecessors.get(nodeId); // The loop stops when the last path node is the source node. while (nextNodeId !== -1 && !pathSet.has(nextNodeId)) { pathSet.add(nextNodeId); nextNodeId = predecessors.get(nextNodeId); } var path = Array.from(pathSet); if (pathSet.has(nextNodeId)) { // Removes all the nodes outside the cycle. path = path.slice(path.indexOf(nextNodeId)); } // Reverses the path. return path.reverse(); } /** * Converts a graph in a residual graph, in which the new arc flow * represent the residual capacity. * Time complexity: O(m). * @param {Graph} The original graph. * @returns {Graph} The residual graph. */ function getResidualGraph(graph) { var residualGraph = graph.copy(); for (var _i = 0, _a = graph.getNodes(); _i < _a.length; _i++) { var node = _a[_i]; for (var _b = 0, _c = node.getArcs(); _b < _c.length; _b++) { var arc = _c[_b]; if (arc.flow > 0) { // Creates the reverse arc with the correct residual capacity. var rAdjacentNode = residualGraph.getNode(arc.head); rAdjacentNode.addArc(new Arc(node.id, -arc.cost, arc.capacity, arc.flow)); } var rNode = residualGraph.getNode(node.id); // Updates the residual capacity of the arc or removes it. if (arc.capacity > arc.flow) { var rArc = rNode.getArc(arc.head); rArc.flow = arc.capacity - arc.flow; } else { rNode.removeArc(arc.head); } } } return residualGraph; } /** * Returns the arc minimum residual capacity of the path. * Time complexity: O(n). * @param {Graph} Graph containing the path. * @param {number[]} The path of the nodes. * @returns {number} The minimum capacity. */ function getResidualCapacity(graph, path) { var residualCapacity = Infinity; for (var i = 0; i < path.length - 1; i++) { var node = graph.getNode(path[i]); var arc = node.getArc(path[i + 1]); if (arc.flow < residualCapacity) { residualCapacity = arc.flow; } } return residualCapacity; } /** * Augments the path in the graph updating the flow of the arcs. * Time complexity: O(n). * @param {Graph} The graph containing the path. * @param {number[]} The path of the nodes. * @param {number} The flow to send in the path. */ function sendFlow(graph, path, flow) { for (var i = 0; i < path.length - 1; i++) { var node = graph.getNode(path[i]); var arc = node.getArc(path[i + 1]); var adjacentNode = graph.getNode(arc.head); if (arc.flow === flow) { node.removeArc(adjacentNode.id); } else { arc.flow -= flow; } if (!adjacentNode.hasArc(node.id)) { adjacentNode.addArc(new Arc(node.id, -arc.cost, arc.capacity, flow)); } else { var reverseArc = adjacentNode.getArc(node.id); reverseArc.flow += flow; } } } /** * Convert the residual graph in an optimal graph in which the * residual capacity is converted in flow. * Time complexity: O(m). * @param {Graph} The graph to update. * @returns {Graph} The optimal graph. */ function getOptimalGraph(graph) { var optimalGraph = new Graph(); for (var _i = 0, _a = graph.getNodes(); _i < _a.length; _i++) { var node = _a[_i]; optimalGraph.addNode(new Node(node.id, node.balance)); } for (var _b = 0, _c = graph.getNodes(); _b < _c.length; _b++) { var node = _c[_b]; for (var _d = 0, _e = node.getArcs(); _d < _e.length; _d++) { var arc = _e[_d]; var adjacentNode = graph.getNode(arc.head); if (arc.cost < 0 || Object.is(arc.cost, -0)) { var oAdjacentNode = optimalGraph.getNode(arc.head); var reverseArc = new Arc(node.id, -arc.cost, arc.capacity, arc.flow); oAdjacentNode.addArc(reverseArc); } else if (!adjacentNode.hasArc(node.id)) { var oNode = optimalGraph.getNode(node.id); oNode.addArc(new Arc(arc.head, arc.cost, arc.capacity)); } } } return optimalGraph; } /** * The Breadth-First Search algorithm finds the tree of the * shortest directed paths from the source node to the other nodes of * the graph and returns it as map of the <node, previous node> pairs. * If a sink node is specified the algorithm return the specific * directed path from the source node to the sink node as an array. * This algorithm use a queue data structure to visit the nodes. * Time complexity: O(n + m) = O(m). * @param {Graph | GraphData} The graph to visit. * @param {number} The source node of the path. * @param {number} The sink node of the path. * @returns {Map<number, number>, number[]} The tree of the shortest paths or the * path between the source node and the sink node. */ function bfs(graph, sourceNodeId, sinkNodeId) { if (!(graph instanceof Graph)) { graph = new Graph(graph); } // Contains the visited nodes with their predecessor nodes. // Predecessors nodes are useful for reconstructing the path. var predecessors = new Map(); // Initialize the queue with the source node. var queue = new Queue(); queue.enqueue(sourceNodeId); // Marks the source node as visited, with -1 as predecessor. predecessors.set(sourceNodeId, -1); // While loop stops when the queue becomes empty. while (queue.size()) { var nodeId = queue.dequeue(); var node = graph.getNode(nodeId); // If an adjacent node is the sink node retrieves the path backwards. if (typeof sinkNodeId === "number" && node.hasArc(sinkNodeId)) { predecessors.set(sinkNodeId, nodeId); return retrievePath(predecessors, sinkNodeId); } var arcs = graph.getNode(nodeId).getArcs(); // If there is adjacent arcs (and then nodes) marks them as visited nodes // and adds them to the queue. for (var _i = 0, arcs_1 = arcs; _i < arcs_1.length; _i++) { var arc = arcs_1[_i]; if (!predecessors.has(arc.head)) { predecessors.set(arc.head, nodeId); queue.enqueue(arc.head); } } } return predecessors; } /** * The Edmonds–Karp algorithm is an implementation of the Ford–Fulkerson * method for computing the maximum flow between two nodes in a flow network. * The algorithm uses the Breadth-First Search algorithm to find the * augmenting path between the source and the sink node, and increment * the maximum flow with the minimum arc capacity for each path * using the residual graph. Returns the optimal graph, the maximum flow, the source and the sink nodes. * Time complexity: O(n * m^2). * @param {Graph | GraphData} The graph to visit. * @returns {[Graph, number, number, number]} The optimal flow graph, the maximum flow, the source and sink nodes. */ function edmondsKarp(graph) { if (!(graph instanceof Graph)) { graph = new Graph(graph); } // Extends the graph to calculate the feasible graph. var _a = extendGraph(graph), tSourceNodeId = _a[0], tSinkNodeId = _a[1]; var residualGraph = getResidualGraph(graph); var maximumflow = 0; var path = bfs(residualGraph, tSourceNodeId, tSinkNodeId); // While loop stops when there is no path between the source and sink nodes. while (path && Array.isArray(path)) { var residualCapacity = getResidualCapacity(residualGraph, path); maximumflow += residualCapacity; sendFlow(residualGraph, path, residualCapacity); // Searches another path with the new residual graph. path = bfs(residualGraph, tSourceNodeId, tSinkNodeId); } return [getOptimalGraph(residualGraph), maximumflow, tSourceNodeId, tSinkNodeId]; } /** * Extends the graph with two new source and sink nodes. * For each node with balance greater than 0, it creates * an arc with capacity = balance from the new source node to this node. * For each node with balance less than 0, it creates * an arc with capacity = -balance from the node to the new sink nodes. * This allows you to compute a feasible graph, where the mass balance * constraint is respected. * Time complexity: O(n). * @param {Graph} The graph to extend. * @returns {number[]} The new source and sink nodes. */ function extendGraph(graph) { var nodes = graph.getNodes(); var maxId = Math.max.apply(Math, nodes.map(function (node) { return node.id; })); var newSourceNode = new Node(maxId + 1, 0); var newSinkNode = new Node(maxId + 2, 0); graph.addNode(newSourceNode); graph.addNode(newSinkNode); for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { var node = nodes_1[_i]; if (node.balance > 0) { var arc = new Arc(node.id, 0, node.balance); newSourceNode.addArc(arc); newSourceNode.balance += node.balance; } else if (node.balance < 0) { var arc = new Arc(newSinkNode.id, 0, -node.balance); node.addArc(arc); newSinkNode.balance += node.balance; } node.balance = 0; } return [newSourceNode.id, newSinkNode.id]; } /** * The Bellman–Ford algorithm finds the shortest paths of a directed graph. * The algorithm returns a map with the nodes and their predecessors and a map * with the nodes and their distances from the source node. * If there are negative cycles the algorithm returns the path of the cycle. * Time complexity: O(n * m). * @param {Graph | GraphData} The graph to visit. * @param {number} The source node. * @returns {Map<number, [number, number]> | number[]} A map with distances and predecessors * or the path of the negative cycle. */ function bellmanFord(graph, sourceNodeId) { if (!(graph instanceof Graph)) { graph = new Graph(graph); } // Creates a map for the distances from the source node for each node // and a map to save the nodes of the paths and their predecessors. var distances = new Map(); var predecessors = new Map(); var nodes = graph.getNodes(); // Initializes all the node distances with infinity except the source node with 0. for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { var node = nodes_1[_i]; distances.set(node.id, Infinity); } distances.set(sourceNodeId, 0); for (var i = 0; i < graph.size() - 1; i++) { for (var _a = 0, nodes_2 = nodes; _a < nodes_2.length; _a++) { var node = nodes_2[_a]; for (var _b = 0, _c = node.getArcs(); _b < _c.length; _b++) { var arc = _c[_b]; var nodeDistance = distances.get(node.id); var adjacentNodeDistance = distances.get(arc.head); if (adjacentNodeDistance > nodeDistance + arc.cost) { distances.set(arc.head, nodeDistance + arc.cost); predecessors.set(arc.head, node.id); } } } } // If still for some node the condition is not respected then there is a negative cycle. for (var _d = 0, nodes_3 = nodes; _d < nodes_3.length; _d++) { var node = nodes_3[_d]; for (var _e = 0, _f = node.getArcs(); _e < _f.length; _e++) { var arc = _f[_e]; var nodeDistance = distances.get(node.id); var adjacentNodeDistance = distances.get(arc.head); if (adjacentNodeDistance > nodeDistance + arc.cost) { return retrievePath(predecessors, node.id); } } } // Merge distances and predecessors in an unique map. return new Map(Array.from(predecessors).map(function (entry) { return [entry[0], [entry[1], distances.get(entry[0])]]; })); } /** * The Cycle-Canceling algorithm solves the minimum-cost flow problem * starting from a feasible graph obtained with a maximum flow algorithm * and, as long as negative cost cycles exist, augment the flow along * this cycles. This implementation uses the Edmonds-Karp algorithm to * solve the maximum flow problem in O(n * m^2), and the Bellman-Fort * algorithm to find the negative cycles in O(m * n). * Time complexity: O (n * m^2 * C * U). * @param {Graph | GraphData} The graph to visit. * @returns {[Graph, number, number]} The optimal graph, the maximum flow and the minimum cost. */ function cycleCanceling(graph) { if (!(graph instanceof Graph)) { graph = new Graph(graph); } var _a = edmondsKarp(graph), optimalGraph = _a[0], maximumFlow = _a[1], sinkNodeId = _a[3]; var residualGraph = getResidualGraph(optimalGraph); var negativeCycle = bellmanFord(residualGraph, sinkNodeId); while (negativeCycle && Array.isArray(negativeCycle)) { negativeCycle.push(negativeCycle[0]); var residualCapacity = getResidualCapacity(residualGraph, negativeCycle); sendFlow(residualGraph, negativeCycle, residualCapacity); negativeCycle = bellmanFord(residualGraph, sinkNodeId); } // Calculates the minimum cost. var minimumCost = 0; for (var _i = 0, _b = residualGraph.getNodes(); _i < _b.length; _i++) { var node = _b[_i]; for (var _c = 0, _d = node.getArcs(); _c < _d.length; _c++) { var arc = _d[_c]; if (arc.cost < 0) { minimumCost -= arc.cost; } } } return [getOptimalGraph(residualGraph), maximumFlow, minimumCost]; } /** * The Depth-First Search algorithm finds the tree of the * shortest directed paths from the source node to the other nodes of * the graph and returns it as map of the <node, previous node> pairs. * If a sink node is specified the algorithm return the specific * directed path from the source node to the sink node as an array. * This algorithm use a stack data structure to visit the nodes, * and in JavaScript can be used the 'push' and 'pop' array methods. * Time complexity: O(n + m) = O(m). * @param {Graph | GraphData} The graph to visit. * @param {number} The source node of the path. * @param {number} The sink node of the path. * @returns {Map<number, number>, number[]} The tree of the shortest paths or the * path between the source node and the sink node. */ function dfs(graph, sourceNodeId, sinkNodeId) { if (!(graph instanceof Graph)) { graph = new Graph(graph); } // Contains the visited nodes with their predecessor nodes. // Predecessors nodes are useful for reconstructing the path. var predecessors = new Map(); var stack = [sourceNodeId]; // Initialize the stack with the source node. // Marks the source node as visited, with -1 as predecessor. predecessors.set(sourceNodeId, -1); // While loop stops when the stack becomes empty. while (stack.length) { var nodeId = stack.pop(); var node = graph.getNode(nodeId); // If an adjacent node is the sink node retrieves the path backwards. if (typeof sinkNodeId === "number" && node.hasArc(sinkNodeId)) { predecessors.set(sinkNodeId, nodeId); return retrievePath(predecessors, sinkNodeId); } var arcs = node.getArcs(); // If there is adjacent arcs (and then nodes) marks them as visited nodes // and adds them to the stack. for (var _i = 0, arcs_1 = arcs; _i < arcs_1.length; _i++) { var arc = arcs_1[_i]; if (!predecessors.has(arc.head)) { predecessors.set(arc.head, nodeId); stack.push(arc.head); } } } return predecessors; } exports.Arc = Arc; exports.Graph = Graph; exports.Node = Node; exports.bellmanFord = bellmanFord; exports.bfs = bfs; exports.cycleCanceling = cycleCanceling; exports.dfs = dfs; exports.edmondsKarp = edmondsKarp; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=nfa.js.map