Graphs
Graph is a data structure that is a collection of nodes and its connections.
It consists of vertex - a node, edge - connections between them.
Graphs are used in social networks, locations, routing algorithms, file system optimization etc.
There are types of graphs.
- Undirected graph : An undirected graph is a graph where edges do not have a specific direction, meaning connections go both ways.
 - Directed graph: A graph in which edges have a direction, i.e., the edges have arrows indicating the direction of traversal.
 - Weighted graph: Has units on the edges. Often used with algorithms to know shortest path.
 
Adjacency matrix
An adjacency matrix is a 2D matrix A of size n × n, where n is the number of vertices in the graph. The elements of the matrix indicate whether pairs of vertices are adjacent or not in the graph.
Adjacency List
An adjacency list is a common way to represent graphs. In this structure:
- Each vertex stores a list of adjacent vertices.
 - It's typically implemented using an array (or dictionary) of lists.
 - It is very efficient for sparse graphs, where the number of edges is much less than the square of the number of vertices.
 
Time Complexity between Adjacency List and Adjacency Matrix
| Feature | Adjacency List | Adjacency Matrix | 
|---|---|---|
| Add Vertex | O(1) | O(V2) | 
| Add an Edge | O(1) | O(1) | 
| Remove Vertex | O(V + E) | O(V2) | 
| Remove an Edge | O(E) | O(1) | 
| Edge Lookup Time | O(V + E) | O(1) | 
| Storage | O(V + E) | O(V2) | 
V is the number of vertices and E is the Edges of the graph
Graph Traversal
Graph traversal is the process of visiting all the nodes (or vertices) in a graph, typically to search or explore the structure. The two main types of graph traversal are Depth-First Search (DFS) and Breadth-First Search (BFS).
In DFS, you start from a node and explore as far as possible along each branch before backtracking. In BFS, you explore all the neighbors of a node before moving on to their neighbors.
Pseudocode Depth First Traversal
There is a recursive and an iterative solution. both will give a different order.
- The function should take a starting node.
 - Create a list to store the end result.
 - Create an object to store visited vertices.
 - Create a helper function which accepts a vertex
- This helper function should return early if the vertex is empty.
 - The helper function should place the vertex it accepts into the visited object and push that vertex into the result array.
 - Loop over all of the values in the adjacency list for that vertex.
 - If any of those values are not listed/visited, recursively invoke the helper function with that vertex.
 
 
Pseudocode Breadth First Traversal
- The function should take a starting node.
 - Create a queue and place the starting vertex in it.
 - Create an array to store the nodes visited.
 - Create an object to store visited vertices.
 - Mark the starting vertex as visited.
 - Loop as long as there is anything in the queue.
 - Remove the first vertex from the queue and push it into the array that stores the nodes visited.
 - Loop over each vertex in the adjacency list for the vertex you are visiting.
 - If it is not inside the object that stores nodes visited, mark it as visited and enqueue that vertex.
 - Return the array of visited nodes.
 
Implementation
Below implementation with undirected graphs.
// * Undirected
// * not doing error handling
class Graph {
  constructor() {
    this.adjacencyList = {};
  }
  addVertex(vertex) {
    if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [];
  }
  addEdge(vertex1, vertex2) {
    this.adjacencyList[vertex1].push(vertex2);
    this.adjacencyList[vertex2].push(vertex1);
  }
  removeEdge(vertex1, vertex2) {
    this.adjacencyList[vertex1] = this.adjacencyList[vertex1].filter(
      (v) => v !== vertex2
    );
    this.adjacencyList[vertex2] = this.adjacencyList[vertex2].filter(
      (v) => v !== vertex1
    );
  }
  removeVertex(vertex) {
    while (this.adjacencyList[vertex].length) {
      const adjacentVertex = this.adjacencyList[vertex].pop();
      this.removeEdge(vertex, adjacentVertex);
    }
    delete this.adjacencyList[vertex];
  }
  // recursive
  depthFirstTraversal(start) {
    const result = [];
    const visited = {};
    const adjacencyList = this.adjacencyList;
    //IIFE. Can do without IIFE as well
    (function dfs(vertex) {
      if (!vertex) return null;
      visited[vertex] = true;
      result.push(vertex);
      adjacencyList[vertex].forEach((neighbor) => {
        if (!visited[neighbor]) {
          return dfs(neighbor);
        }
      });
    })(start);
    return result;
  }
  // Iterative.
  dfsIterative(start) {
    let stack = [start];
    let visited = {};
    let currentVertex;
    let result = [];
    visited[start] = true;
    while (stack.length) {
      currentVertex = stack.pop();
      result.push(currentVertex);
      this.adjacencyList[currentVertex].forEach((neighbor) => {
        if (!visited[neighbor]) {
          visited[neighbor] = true;
          stack.push(neighbor);
        }
      });
    }
    return result;
  }
  breadthFirst(start) {
    const queue = [start];
    const result = [];
    const visited = {};
    let currentVertex;
    visited[start] = true;
    while (queue.length) {
      currentVertex = queue.shift();
      result.push(currentVertex);
      this.adjacencyList(currentVertex).forEach((neighbor) => {
        if (!visited[neighbor]) {
          visited[neighbor] = true;
          queue.push(neighbor);
        }
      });
    }
    return result;
  }
}