@cedoor/nfa
Version:
TypeScript implementation of some network flow algorithms.
678 lines (664 loc) • 28.4 kB
JavaScript
/**
* @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