Graphs are fundamental data structures used in computer science and software engineering for modeling relationships between entities. They consist of nodes (also called vertices) connected by edges, which can be directed or undirected. Understanding graphs is crucial for solving a wide range of problems, including network analysis, social media applications, routing protocols, and more.
A graph \( G \) is defined as an ordered pair \( G = (V, E) \), where:
\( V \) is the set of vertices.\( E \) is the set of edges, each connecting two vertices in \( V \).\( u \) and vertex \( v \), it can be traversed from \( u \) to \( v \) or \( v \) to \( u \).\( u \) to vertex \( v \) cannot be traversed in the reverse direction.There are two primary ways to represent graphs in memory:
Adjacency Matrix:
\( M[i][j] = 1 \) if there is an edge from vertex \( i \) to vertex \( j \), otherwise \( 0 \).\( O(V^2) \).Adjacency List:
\( O(V + E) \).Here's how you can represent an undirected graph using both adjacency matrix and adjacency list in Python:
# Adjacency Matrix Representation
class GraphMatrix:
def __init__(self, num_vertices):
self.num_vertices = num_vertices
self.matrix = [[0 for _ in range(num_vertices)] for _ in range(num_vertices)]
def add_edge(self, u, v):
self.matrix[u][v] = 1
self.matrix[v][u] = 1
# Adjacency List Representation
class GraphList:
def __init__(self, num_vertices):
self.num_vertices = num_vertices
self.adj_list = [[] for _ in range(num_vertices)]
def add_edge(self, u, v):
self.adj_list[u].append(v)
self.adj_list[v].append(u)
# Usage
matrix_graph = GraphMatrix(5)
matrix_graph.add_edge(0, 1)
matrix_graph.add_edge(0, 2)
list_graph = GraphList(5)
list_graph.add_edge(0, 1)
list_graph.add_edge(0, 2)
Graph traversal involves visiting all vertices and edges. The two main types are:
def dfs(graph, start_vertex):
visited = set()
stack = [start_vertex]
while stack:
vertex = stack.pop()
if vertex not in visited:
print(vertex, end=' ')
visited.add(vertex)
for neighbor in graph[vertex]:
if neighbor not in visited:
stack.append(neighbor)
# Example usage
graph = {0: [1, 2], 1: [2], 2: [3]}
dfs(graph, 0) # Output: 0 2 3 1
from collections import deque
def bfs(graph, start_vertex):
visited = set()
queue = deque([start_vertex])
while queue:
vertex = queue.popleft()
if vertex not in visited:
print(vertex, end=' ')
visited.add(vertex)
for neighbor in graph[vertex]:
if neighbor not in visited:
queue.append(neighbor)
# Example usage
graph = {0: [1, 2], 1: [2], 2: [3]}
bfs(graph, 0) # Output: 0 1 2 3
Graphs are versatile data structures that model complex relationships. Understanding their properties and operations is essential for solving real-world problems efficiently. By choosing the right representation and traversal method, you can optimize performance and resource usage in your applications.