Implementing a Graph Data Structure in JavaScript

Graph data structures are a fundamental data structure used in computer science to represent a variety of problems such as network connections, social networks, and even road maps. In JavaScript, graphs can be represented using various techniques, but the most common ones are adjacency matrix and adjacency list, of which we will explore in this article.

What are graphs useful for?

Graph data structures are useful for modeling relationships and connections between entities. They consist of a set of vertices (also known as nodes) and a set of edges, which connect the vertices.

Graphs are used in a wide range of applications, including:

  • Social networks: Graphs are used to model relationships between people in social networks such as Facebook, Twitter, and LinkedIn.
  • Routing algorithms: Graphs are used to model the connections between different nodes in a network, allowing for efficient routing of data.
  • Recommendation systems: Graphs are used to model the relationships between users and items, allowing for personalized recommendations based on user behavior.
  • Computer networks: Graphs are used to model the connections between devices in a network, allowing for efficient data transfer and network management.
  • GIS (Geographic Information Systems): Graphs are used to model the connections between locations in a map, allowing for efficient routing and analysis of spatial data.
  • Molecular biology: Graphs are used to model the relationships between molecules, allowing for analysis of biological data.
  • Knowledge representation: Graphs are used to model the relationships between concepts in a knowledge base, allowing for efficient query processing and reasoning.

Adjacency Matrix

An adjacency matrix is a two-dimensional array that represents a graph where each node is a row and column in the array. The matrix is filled with 0’s and 1’s, where a 1 indicates that there is an edge between two nodes, and a 0 indicates that there is no edge. Here is an example of a simple graph represented by an adjacency matrix:

  1 2 3
1 0 1 1
2 1 0 0
3 1 0 0

This matrix represents a graph with three nodes (1, 2, and 3) and three edges connecting node 1 to nodes 2 and 3, respectively.

To implement an adjacency matrix in JavaScript, we can use a two-dimensional array. Here is an example implementation:

class Graph {
  constructor(numNodes) {
    this.matrix = [];
    for (let i = 0; i < numNodes; i++) {
      this.matrix.push(new Array(numNodes).fill(0));
    }
  }
  addEdge(fromNode, toNode) {
    this.matrix[fromNode][toNode] = 1;
    this.matrix[toNode][fromNode] = 1;
  }
  removeEdge(fromNode, toNode) {
    this.matrix[fromNode][toNode] = 0;
    this.matrix[toNode][fromNode] = 0;
  }
  isEdge(fromNode, toNode) {
    return this.matrix[fromNode][toNode] === 1;
  }
}

This implementation of the Graph class has a constructor that takes the number of nodes as an argument and initializes an empty adjacency matrix. The addEdge method adds an edge between two nodes, and the removeEdge method removes an edge between two nodes. Finally, the isEdge method checks whether there is an edge between two nodes.

Adjacency List

An adjacency list is a collection of arrays, where each array represents a node in the graph, and the elements in the array are the nodes that are adjacent to the current node. Here is an example of a simple graph represented by an adjacency list:

{
  1: [2, 3],
  2: [1],
  3: [1]
}

This adjacency list represents a graph with three nodes (1, 2, and 3) and three edges connecting node 1 to nodes 2 and 3, respectively.

To implement an adjacency list in JavaScript, we can use an object. Here is an example implementation:

class Graph {
  constructor() {
    this.list = {};
  }
  addNode(node) {
    this.list[node] = [];
  }
  addEdge(fromNode, toNode) {
    this.list[fromNode].push(toNode);
    this.list[toNode].push(fromNode);
  }
  removeEdge(fromNode, toNode) {
    this.list[fromNode] = this.list[fromNode].filter((node) => node !== toNode);
    this.list[toNode] = this.list[toNode].filter((node) => node !== fromNode);
  }
  isEdge(fromNode, toNode) {
    return this.list[fromNode].includes(toNode);
  }
}

This implementation of the Graph class has a constructor that initializes an empty adjacency list. The addNode method adds a node to the graph, and the addEdge method adds an edge between two nodes. The removeEdge method removes an edge between two nodes. Finally, the isEdge method checks whether there is an edge between two nodes.

Traversal Algorithms

Once we have a graph data structure implemented, we can use various algorithms to traverse the graph and perform operations on the nodes and edges.

Two common traversal algorithms are depth-first search (DFS) and breadth-first search (BFS) (we also explored these algorithms in an article about tree data structures). Let’s take a look at implementing them for a graph.

Depth-First Search (DFS)

DFS is an algorithm that visits every node in a graph by exploring as far as possible along each branch before backtracking. Here is an example implementation of DFS for the adjacency list representation of a graph:

class Graph {
  // ...
  dfs(startNode, visited = new Set()) {
    visited.add(startNode);
    console.log(startNode);
    this.list[startNode].forEach((node) => {
      if (!visited.has(node)) {
        this.dfs(node, visited);
      }
    });
  }
}

This implementation of the Graph class adds a dfs method that takes a starting node and a visited set as arguments. The visited set is used to keep track of which nodes have already been visited. The dfs method starts by adding the starting node to the visited set and logging it to the console. It then recursively calls itself on each adjacent node that has not been visited yet.

Breadth-First Search (BFS)

BFS is an algorithm that visits every node in a graph by exploring all the neighbors of a node before moving on to its neighbors' neighbors. Here is an example implementation of BFS for the adjacency list representation of a graph:

class Graph {
  // ...
  bfs(startNode) {
    const visited = new Set();
    const queue = [startNode];
    visited.add(startNode);
    while (queue.length > 0) {
      const currentNode = queue.shift();
      console.log(currentNode);
      this.list[currentNode].forEach((node) => {
        if (!visited.has(node)) {
          visited.add(node);
          queue.push(node);
        }
      });
    }
  }
}

This implementation of the Graph class adds a bfs method that takes a starting node as an argument. It initializes a visited set and a queue with the starting node. It then enters a while loop that dequeues the first node in the queue, logs it to the console, and adds its unvisited neighbors to the queue and visited set.

Conclusion

In conclusion, graphs are a fundamental data structure used in computer science to represent a variety of problems. We can represent graphs using either an adjacency matrix or an adjacency list. Once we have a graph data structure implemented, we can use various traversal algorithms such as DFS and BFS to explore the graph and perform operations on the nodes and edges.

Questions or comments? Let us know down below!

Frequently Asked Questions

Q: What is a graph data structure?

A: A graph is a data structure consisting of a set of vertices (nodes) and a set of edges connecting these vertices.

Q: How can we implement a graph in JavaScript?

A: There are several ways to implement a graph in JavaScript, including using an adjacency matrix, an adjacency list, or an object-based representation.

Q: What is an adjacency matrix?

A: An adjacency matrix is a 2D array where the rows and columns represent vertices and the entries indicate whether there is an edge between the corresponding vertices.

Q: What is an adjacency list?

A: An adjacency list is a collection of arrays or linked lists where each array or linked list represents the neighbors of a vertex in the graph.

Q: How can we traverse a graph in JavaScript?

A: There are several ways to traverse a graph in JavaScript, including depth-first search (DFS) and breadth-first search (BFS).

Q: What is DFS?

A: DFS is a graph traversal algorithm that visits vertices in a depth-first manner, exploring as far as possible along each branch before backtracking.

Q: What is BFS?

A: BFS is a graph traversal algorithm that visits vertices in a breadth-first manner, exploring all the vertices at a given depth before moving on to vertices at the next depth.

Q: How can we find the shortest path between two vertices in a graph?

A: We can use BFS to find the shortest path between two vertices in an unweighted graph. For weighted graphs, we can use algorithms such as Dijkstra’s algorithm or the A* algorithm.

Q: What is Dijkstra’s algorithm?

A: Dijkstra’s algorithm is a shortest path algorithm that finds the shortest path between a source vertex and all other vertices in a weighted graph.

Q: What is the A* algorithm?

A: The A* algorithm is a shortest path algorithm that finds the shortest path between a source vertex and a destination vertex in a weighted graph by using a heuristic function to guide the search.

comments powered by Disqus

Related Posts

Solving a Common Interview Question: the Two Sum Algorithm in JavaScript

Imagine you’re at a lively party, and everyone is carrying a specific number on their back. The host announces a game – find two people whose numbers add up to the magic number, and you win a prize!

Read more

Remove Duplicates from Arrays and Strings in JavaScript

Removing duplicates is a common problem in programming that can arise in various contexts, such as cleaning up data or ensuring unique entries.

Read more

Remote First: 5 Websites for Remote Job Opportunities

Would you prefer to work in an office, or while sitting at a beach somewhere in Thailand (i.e. remotely)? Okay, maybe there’s no beach in this scenario, but there’s definitely silence, and maybe your cat.

Read more