# Graph Data Structure in Javascript

A graph is a pictorial representation of a set of objects where some pairs of objects are connected by links. The interconnected objects are represented by points termed as vertices, and the links that connect the vertices are called edges.

Formally, a graph is a pair of sets (V, E), where V is the set of vertices and E is the set of edges, connecting the pairs of vertices. Take a look at the following graph − In the above graph,

V = {a, b, c, d, e}
E = {ab, ac, bd, cd, de}

## Terminology

Mathematical graphs can be represented in the data structure. We can represent a graph using an array of vertices and a two-dimensional array of edges. Before we proceed further, let's familiarize ourselves with some important terms −

• Vertex − Each node of the graph is represented as a vertex. In the following example, the labeled circle represents vertices. Thus, A to G are vertices. We can represent them using an array as shown in the following image. Here A can be identified by index 0. B can be identified using index 1 and so on.

• Edge − Edge represents a path between two vertices or a line between two vertices. In the following example, the lines from A to B, B to C, and so on represent edges. We can use a two-dimensional array to represent an array as shown in the following image. Here AB can be represented as 1 at row 0, column 1, BC as 1 at row 1, column 2 and so on, keeping other combinations as 0.

• Adjacency − Two node or vertices are adjacent if they are connected to each other through an edge. In the following example, B is adjacent to A, C is adjacent to B, and so on.

• Path − Path represents a sequence of edges between the two vertices. In the following example, ABCD represents a path from A to D.

Here is the complete implementation of the Graph Class using Javascript.

## Example

const Queue = require("./Queue");
const Stack = require("./Stack");
const PriorityQueue = require("./PriorityQueue");

class Graph {
constructor() {
this.edges = {};
this.nodes = [];
}

this.nodes.push(node);
this.edges[node] = [];
}

addEdge(node1, node2, weight = 1) {
this.edges[node1].push({ node: node2, weight: weight });
this.edges[node2].push({ node: node1, weight: weight });
}

addDirectedEdge(node1, node2, weight = 1) {
this.edges[node1].push({ node: node2, weight: weight });
}

//   this.edges[node1].push(node2);
//   this.edges[node2].push(node1);
// }

//   this.edges[node1].push(node2);
// }

display() {
let graph = "";
this.nodes.forEach(node => {
graph += node + "->" + this.edges[node].map(n => n.node).join(", ") + "";
});
console.log(graph);
}

BFS(node) {
let q = new Queue(this.nodes.length);
let explored = new Set();
q.enqueue(node);
while (!q.isEmpty()) {
let t = q.dequeue();
console.log(t);
this.edges[t].filter(n => !explored.has(n)).forEach(n => {
q.enqueue(n);
});
}
}

DFS(node) {
// Create a Stack and add our initial node in it
let s = new Stack(this.nodes.length);
let explored = new Set();
s.push(node);

// Mark the first node as explored

// We'll continue till our Stack gets empty
while (!s.isEmpty()) {
let t = s.pop();

// Log every element that comes out of the Stack
console.log(t);

// 1. In the edges object, we search for nodes this node is directly connected to.
// 2. We filter out the nodes that have already been explored.
// 3. Then we mark each unexplored node as explored and push it to the Stack.
this.edges[t].filter(n => !explored.has(n)).forEach(n => {
s.push(n);
});
}
}

topologicalSortHelper(node, explored, s) {
this.edges[node].forEach(n => {
if (!explored.has(n)) {
this.topologicalSortHelper(n, explored, s);
}
});
s.push(node);
}

topologicalSort() {
// Create a Stack and add our initial node in it
let s = new Stack(this.nodes.length);
let explored = new Set();
this.nodes.forEach(node => {
if (!explored.has(node)) {
this.topologicalSortHelper(node, explored, s);
}
});
while (!s.isEmpty()) {
console.log(s.pop());
}
}

BFSShortestPath(n1, n2) {
let q = new Queue(this.nodes.length);
let explored = new Set();
let distances = { n1: 0 };
q.enqueue(n1);
while (!q.isEmpty()) {
let t = q.dequeue();
this.edges[t].filter(n => !explored.has(n)).forEach(n => {
distances[n] = distances[t] == undefined ? 1 : distances[t] + 1;
q.enqueue(n);
});
}
return distances[n2];
}

primsMST() {
// Initialize graph that'll contain the MST
const MST = new Graph();
if (this.nodes.length === 0) {
return MST;
}

// Select first node as starting node
let s = this.nodes;

// Create a Priority Queue and explored set
let edgeQueue = new PriorityQueue(this.nodes.length * this.nodes.length);
let explored = new Set();

// Add all edges from this starting node to the PQ taking weights as priority
this.edges[s].forEach(edge => {
edgeQueue.enqueue([s, edge.node], edge.weight);
});

// Take the smallest edge and add that to the new graph
let currentMinEdge = edgeQueue.dequeue();
while (!edgeQueue.isEmpty()) {
// COntinue removing edges till we get an edge with an unexplored node
while (!edgeQueue.isEmpty() && explored.has(currentMinEdge.data)) {
currentMinEdge = edgeQueue.dequeue();
}
let nextNode = currentMinEdge.data;
// Check again as queue might get empty without giving back unexplored element
if (!explored.has(nextNode)) {

// Again add all edges to the PQ
this.edges[nextNode].forEach(edge => {
edgeQueue.enqueue([nextNode, edge.node], edge.weight);
});

// Mark this node as explored
s = nextNode;
}
}
return MST;
}

kruskalsMST() {
// Initialize graph that'll contain the MST
const MST = new Graph();

if (this.nodes.length === 0) {
return MST;
}

// Create a Priority Queue
let edgeQueue = new PriorityQueue(this.nodes.length * this.nodes.length);

// Add all edges to the Queue:
for (let node in this.edges) {
this.edges[node].forEach(edge => {
edgeQueue.enqueue([node, edge.node], edge.weight);
});
}
let uf = new UnionFind(this.nodes);

// Loop until either we explore all nodes or queue is empty
while (!edgeQueue.isEmpty()) {
// Get the edge data using destructuring
let nextEdge = edgeQueue.dequeue();
let nodes = nextEdge.data;
let weight = nextEdge.priority;

if (!uf.connected(nodes, nodes)) {
uf.union(nodes, nodes);
}
}
return MST;
}

djikstraAlgorithm(startNode) {
let distances = {};

// Stores the reference to previous nodes
let prev = {};
let pq = new PriorityQueue(this.nodes.length * this.nodes.length);

// Set distances to all nodes to be infinite except startNode
distances[startNode] = 0;
pq.enqueue(startNode, 0);

this.nodes.forEach(node => {
if (node !== startNode) distances[node] = Infinity;
prev[node] = null;
});

while (!pq.isEmpty()) {
let minNode = pq.dequeue();
let currNode = minNode.data;
let weight = minNode.priority;

this.edges[currNode].forEach(neighbor => {
let alt = distances[currNode] + neighbor.weight;
if (alt < distances[neighbor.node]) {
distances[neighbor.node] = alt;
prev[neighbor.node] = currNode;
pq.enqueue(neighbor.node, distances[neighbor.node]);
}
});
}
return distances;
}

floydWarshallAlgorithm() {
let dist = {};
for (let i = 0; i < this.nodes.length; i++) {
dist[this.nodes[i]] = {};

// For existing edges assign the dist to be same as weight
this.edges[this.nodes[i]].forEach(e => (dist[this.nodes[i]][e.node] = e.weight));

this.nodes.forEach(n => {
// For all other nodes assign it to infinity
if (dist[this.nodes[i]][n] == undefined)
dist[this.nodes[i]][n] = Infinity;
// For self edge assign dist to be 0
if (this.nodes[i] === n) dist[this.nodes[i]][n] = 0;
});
}

this.nodes.forEach(i => {
this.nodes.forEach(j => {
this.nodes.forEach(k => {
// Check if going from i to k then from k to j is better
// than directly going from i to j. If yes then update
// i to j value to the new value
if (dist[i][k] + dist[k][j] < dist[i][j])
dist[i][j] = dist[i][k] + dist[k][j];
});
});
});
return dist;
}
}

class UnionFind {
constructor(elements) {
// Number of disconnected components
this.count = elements.length;

// Keep Track of connected components
this.parent = {};
// Initialize the data structure such that all elements have themselves as parents
elements.forEach(e => (this.parent[e] = e));
}

union(a, b) {
let rootA = this.find(a);
let rootB = this.find(b);

// Roots are same so these are already connected.
if (rootA === rootB) return;

// Always make the element with smaller root the parent.
if (rootA < rootB) {
if (this.parent[b] != b) this.union(this.parent[b], a);
this.parent[b] = this.parent[a];
} else {
if (this.parent[a] != a) this.union(this.parent[a], b);
this.parent[a] = this.parent[b];
}
}

// Returns final parent of a node
find(a) {
while (this.parent[a] !== a) {
a = this.parent[a];
}
return a;
}

// Checks connectivity of the 2 nodes
connected(a, b) {
return this.find(a) === this.find(b);
}
}