April 22, 2023
O. Wolfson
In this tutorial, we'll learn about the graph data structure, it's applications, and how to implement it in JavaScript.
A graph is a data structure that consists of vertices (also called nodes) and edges, where the edges represent the relationships between the vertices. Graphs can be used to model a wide range of complex systems, such as:
JavaScript is often used to handle front-end tasks and user interactions, where the primary data structures tend to be arrays, objects, and maps. However, graphs can be useful in JavaScript for specific use cases, such as modeling relationships within a web application, handling complex UI interactions, or working with data visualization libraries. JavaScript libraries like D3.js or Cytoscape.js, for example, are built for handling graph-like structures for data visualization and manipulation purposes.
In other development environments, particularly in back-end systems, databases, or algorithm-heavy applications, graph data structures may be more common. Languages like Python, Java, and C++ frequently implement graphs for tasks like network analysis, route optimization, or natural language processing, among others. Graph databases like Neo4j are also popular for handling complex graph data on the back-end.
Here are some key terms to understand when working with graphs:
There are two common ways to represent graphs in computer memory:
Adjacency List: This is a space-efficient method that is suitable for sparse graphs (few edges relative to the number of vertices). It involves creating a list of vertices and storing their adjacent vertices in a linked list or an array.
Adjacency Matrix: This method is suitable for dense graphs (many edges relative to the number of vertices). It involves creating a two-dimensional matrix, where the cell at position (i, j) indicates the presence of an edge between vertices i and j.
In this section, we'll implement an undirected graph using the adjacency list representation. Here's the basic structure of our Graph class:
javascriptclass Graph {
constructor() {
this.adjacencyList = new Map();
}
}
Now let's add some methods for adding vertices and edges:
javascriptclass Graph {
constructor() {
this.adjacencyList = new Map();
}
addVertex(vertex) {
if (!this.adjacencyList.has(vertex)) {
this.adjacencyList.set(vertex, []);
}
}
addEdge(vertex1, vertex2) {
this.adjacencyList.get(vertex1).push(vertex2);
this.adjacencyList.get(vertex2).push(vertex1);
}
}
There are two main ways to traverse a graph: depth-first search (DFS) and breadth-first search (BFS). In this section, we'll implement both methods in our Graph class.
Depth-First Search (DFS)
DFS is a graph traversal algorithm that starts at a source vertex and visits vertices as deep as possible before backtracking. Here's the implementation:
javascriptclass Graph {
// ...
dfs(startVertex, visited = new Set()) {
if (visited.has(startVertex)) {
return;
}
console.log(startVertex);
visited.add(startVertex);
const neighbors = this.adjacencyList.get(startVertex);
for (const neighbor of neighbors) {
this.dfs(neighbor, visited);
}
}
}
Breadth-First Search (BFS)
BFS is another graph traversal algorithm that visits all the vertices at the same level before moving on to the next level. Here's the implementation:
javascriptclass Graph {
// ...
bfs(startVertex) {
const visited = new Set();
const queue = [startVertex];
while (queue.length > 0) {
const currentVertex = queue.shift();
if (!visited.has(currentVertex)) {
console.log(currentVertex);
visited.add(currentVertex);
const neighbors = this.adjacencyList.get(currentVertex);
for (const neighbor of neighbors) {
queue.push(neighbor);
}
}
}
}
}
Here's an example of how to use the Graph class and its traversal methods:
javascriptconst graph = new Graph();
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
graph.addVertex("E");
graph.addVertex("F");
graph.addEdge("A", "B");
graph.addEdge("A", "C");
graph.addEdge("B", "D");
graph.addEdge("C", "E");
graph.addEdge("D", "E");
graph.addEdge("D", "F");
graph.addEdge("E", "F");
console.log("DFS:");
graph.dfs("A");
console.log("BFS:");
graph.bfs("A");
//Output:
// DFS: A;
// B;
// D;
// E;
// C;
// F;
// BFS: A;
// B;
// C;
// D;
// E;
// F;
In this tutorial, we've covered the basics of graphs, their terminology, and representation. We've implemented an undirected graph in JavaScript using the adjacency list representation and provided examples of depth-first and breadth-first search traversal algorithms. This foundation will help you understand and work with more complex graph problems and applications in the future.