A graph data structure with topological sort.
This library provides a minimalist implementation of a directed graph data structure. Nodes are represented by unique strings or any other object. Internally, an adjacency list is used to represent nodes and edges.
The primary use case for this library is in implementing dataflow programming or reactive programming. The key algorithm necessary for these is topological sorting, to get an ordering of nodes such that for each edge (u -> v), u comes before v in the sorted order. The topological sorting algorithm exposed here has modifications useful for computing the order in which functions in a data flow graph should be executed, namely specifying source nodes for propagation and specifying to exclude the source nodes themselves from the result.
Table of Contents
This library is distributed only via NPM. Install by running
npm install graph-data-structure
Require it in your code like this.
import { Graph, serializeGraph, deserializeGraph, topologicalSort, shortestPath } from 'graph-data-structure';
Start by creating a new Graph object.
var graph = new Graph();
Add some nodes and edges with addNode and addEdge.
graph.addNode('a');
graph.addNode('b');
graph.addEdge('a', 'b');
Nodes are added implicitly when edges are added.
graph.addEdge('b', 'c');
Now we have the following graph.
Topological sorting can be done by invoking the standalone function topologicalSort like this.
topologicalSort(graph); // Returns ["a", "b", "c"]
Here's an example of topological sort with getting dressed (from Cormen et al. "Introduction to Algorithms" page 550).
const graph = new Graph();
graph.addEdge('socks', 'shoes')
.addEdge('shirt', 'belt')
.addEdge('shirt', 'tie')
.addEdge('tie', 'jacket')
.addEdge('belt', 'jacket')
.addEdge('pants', 'shoes')
.addEdge('underpants', 'pants')
.addEdge('pants', 'belt');
// prints [ "underpants", "pants", "shirt", "tie", "belt", "jacket", "socks", "shoes" ]
console.log(topologicalSort(graph));
For more detailed example code that shows more methods, have a look at the tests.
- Creating a Graph
- Adding and Removing Nodes
- Adding and Removing Edges
- Querying the Graph
- Serialization
- Graph Algorithms
# Graph([serialized])
Constructs an instance of the graph data structure.
The optional argument serialized is a serialized graph that may have been generated by serializeGraph. If serialized is present, it is deserialized by invoking deserializeGraph(mySerializedObject).
# graph.addNode(node)
Adds a node to the graph. Returns graph to support method chaining. If the given node was already added to the graph, this function does nothing.
# graph.removeNode(node)
Removes the specified node. Returns graph to support method chaining. The argument node is a string or object identifier for the node to remove. This function also removes all edges connected to the specified node, both incoming and outgoing.
Note: You have to remove them using the exact same reference as when they were created. One can use getNode() to retrieve such reference.
# graph.addEdge(u, v[,weight])
Adds an edge from node u to node v. Returns graph to support method chaining. The arguments u and v are node references (either objects or strings). This function also adds u and v as nodes if they were not already added.
The last argument weight (optional) specifies the weight of this edge.
# graph.removeEdge(u, v)
Removes the edge from node u to node v. Returns graph to support method chaining. The arguments u and v are node references. This function does not remove the nodes u and v. Does nothing if the edge does not exist.
# graph.hasEdge(u, v)
Returns true
if there exists an edge from node u to node v. Returns false
otherwise.
# graph.setEdgeWeight(u, v, weight)
Sets the weight (a number) of the edge from node u to node v.
# graph.getEdgeWeight(u, v)
Gets the weight of the edge from node u to node v. If no weight was previously set on this edge, then the value 1 is returned.
# graph.adjacent(node)
Gets the adjacent node list for the specified node. The argument node is a node reference (object or string). Returns a Set
of adjacent node references or undefined
if the node is not found.
# serializeGraph(graph)
Serializes the graph. Returns an object with the following properties.
nodes
An array of objects, each representing a node reference.links
An array of objects representing edges, each with the following properties.source
The node reference of the source node (u).target
The node reference of the target node (v).weight
The weight of the edge between the source and target nodes.
Here's example code for serializing a graph.
var graph = new Graph();
graph.addEdge('a', 'b');
graph.addEdge('b', 'c');
var serialized = serializeGraph(graph);
# deserializeGraph(serialized)
Deserializes the given serialized graph. Returns a new graph. The argument serialized is a graph representation with the structure described in serializeGraph.
# topologicalSort(graph)
Performs [Topological Sort](
https://en.wikipedia.org/wiki/Topological_sorting). Returns an array of node identifier strings. The returned array includes nodes in topologically sorted order. This means that for each visited edge (u -> v), u comes before v in the topologically sorted order.
Note: this function raises a CycleError
when the input is not a DAG.
# shortestPath(graph, sourceNode, destinationNode)
Performs Dijkstra's Algorithm. Returns an object with two properties: nodes
, an array of node references representing the path, and weight
, the total weight of the path.
var result = shortestPath(graph, 'a', 'c');
console.log(result.nodes); // Prints the array of nodes in the shortest path
console.log(result.weight); // Prints the total weight of the path