Graph Data Structure

2023-04-22
By: O. Wolfson

In this tutorial, we'll learn about the graph data structure, it's applications, and how to implement it in JavaScript.

Table of Contents

  1. Introduction to Graphs
  2. Graph Terminology
  3. Graph Representation
  4. Implementing Graphs in JavaScript
  5. Traversing Graphs: Depth-First and Breadth-First Search
  6. Conclusion

Introduction to Graphs

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:

  • Social networks
  • Transportation systems
  • Computer networks
  • Web pages and hyperlinks
Graph Data Structure. Image by Neo4j.
Graph Data Structure. Image by Neo4j.

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.

Graph Terminology

Here are some key terms to understand when working with graphs:

  • Vertex (Node): A basic unit of a graph, representing an entity or an object.
  • Edge: A connection between two vertices, representing a relationship.
  • Undirected Graph: A graph in which the edges have no direction, meaning the relationship between connected vertices is bidirectional.
  • Directed Graph: A graph in which the edges have a direction, indicating the relationship is unidirectional.
  • Weighted Graph: A graph in which the edges have a weight, representing the cost or strength of the relationship.
  • Adjacency List: A representation of a graph that uses a list of vertices, where each vertex stores a list of its adjacent vertices.
  • Adjacency Matrix: A representation of a graph that uses a two-dimensional matrix to store the relationships between vertices.

Graph Representation

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.

Implementing Graphs in JavaScript

In this section, we'll implement an undirected graph using the adjacency list representation. Here's the basic structure of our Graph class:

javascript
class Graph {
  constructor() {
    this.adjacencyList = new Map();
  }
}

Now let's add some methods for adding vertices and edges:

javascript
class 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);
  }
}

Traversing Graphs: Depth-First and Breadth-First Search

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:

javascript
class 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:

javascript
class 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);
        }
      }
    }
  }
}

Example Usage

Here's an example of how to use the Graph class and its traversal methods:

javascript
const 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;

Conclusion

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.