Discover the power of graphs - one of the most versatile and widely used data structures in computer science. Learn about different graph representations and fundamental traversal techniques.
A graph is a data structure consisting of vertices (nodes) and edges that connect these vertices. Graphs are highly versatile and can represent many real-world relationships and networks.
An adjacency list is one of the most common ways to represent a graph. It uses an array or object where each vertex is associated with a list of its adjacent vertices (neighbors).
class Graph {
constructor() {
this.adjacencyList = {};
}
addVertex(vertex) {
if (!this.adjacencyList[vertex]) {
this.adjacencyList[vertex] = [];
}
}
addEdge(vertex1, vertex2) {
// For undirected graph
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];
}
}
An adjacency matrix is a 2D array where rows and columns represent vertices. The value at matrix[i][j] indicates if there is an edge from vertex i to vertex j.
class GraphMatrix {
constructor(numVertices) {
this.numVertices = numVertices;
// Initialize matrix with zeros
this.matrix = Array(numVertices).fill()
.map(() => Array(numVertices).fill(0));
}
// Add edge for undirected graph
addEdge(v1, v2) {
// Set values to 1 to indicate an edge
this.matrix[v1][v2] = 1;
this.matrix[v2][v1] = 1; // Remove this line for directed graph
}
// Add edge with weight
addWeightedEdge(v1, v2, weight) {
this.matrix[v1][v2] = weight;
this.matrix[v2][v1] = weight; // Remove this line for directed graph
}
// Remove edge
removeEdge(v1, v2) {
this.matrix[v1][v2] = 0;
this.matrix[v2][v1] = 0; // Remove this line for directed graph
}
// Check if there is an edge between vertices
hasEdge(v1, v2) {
return this.matrix[v1][v2] !== 0;
}
// Get all adjacent vertices of a vertex
getAdjacentVertices(v) {
const adjacent = [];
for (let i = 0; i < this.numVertices; i++) {
if (this.matrix[v][i] !== 0) {
adjacent.push(i);
}
}
return adjacent;
}
}
Depth-First Traversal is an algorithm used to explore nodes and edges of a graph. It starts at a source node and explores as far as possible along each branch before backtracking.
class Graph {
// ... previous methods ...
depthFirstTraversal(start) {
const result = [];
const visited = {};
// Helper function to perform recursive DFT
const dfs = (vertex) => {
if (!vertex) return null;
// Mark as visited and add to result
visited[vertex] = true;
result.push(vertex);
// Visit all neighbors
this.adjacencyList[vertex].forEach(neighbor => {
if (!visited[neighbor]) {
return dfs(neighbor);
}
});
};
// Start traversal
dfs(start);
return result;
}
// Iterative implementation using stack
depthFirstIterative(start) {
const stack = [start];
const result = [];
const visited = {};
visited[start] = true;
while (stack.length) {
const currentVertex = stack.pop();
result.push(currentVertex);
this.adjacencyList[currentVertex].forEach(neighbor => {
if (!visited[neighbor]) {
visited[neighbor] = true;
stack.push(neighbor);
}
});
}
return result;
}
}
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];
}
}
class Graph {
// ... previous methods ...
depthFirstTraversal(start) {
const result = [];
const visited = {};
const adjacencyList = this.adjacencyList;
(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;
}
}
// Create a new graph
const graph = new Graph();
// Add vertices
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
// Add edges
graph.addEdge("A", "B");
graph.addEdge("A", "C");
graph.addEdge("B", "D");
graph.addEdge("C", "D");
// Perform DFS starting from vertex "A"
console.log(graph.depthFirstTraversal("A"));
// Output: ["A", "B", "D", "C"]
Implement a graph class that supports both directed and undirected graphs using adjacency lists.
Implement a method to find if a path exists between two vertices using depth-first search.
Write a method to find all connected components in an undirected graph.
The following LeetCode collections are excellent resources for practicing graph problems and preparing for technical interviews.
Note: Previously, this course referenced the CodeSignal Arcade, which is no longer available. The LeetCode problems above follow the same principles and are an excellent alternative for practicing graph algorithms and preparing for technical interviews.