Graph Algorithms Reference
Comprehensive reference for graph algorithms including BFS, DFS, shortest paths, minimum spanning trees, and topological sorting with implementation details.
Cheat Sheet Overview
Quick reference and study metrics
Reading Time
Sections
Downloads
Category
Enhanced SEO fields
seo_title: "Graph Algorithms Cheat Sheet: BFS, DFS, Dijkstra & MST" seo_description: "Master graph algorithms with this comprehensive reference covering BFS, DFS, Dijkstra's algorithm, minimum spanning trees, and advanced graph techniques." seo_keywords: ["graph algorithms cheat sheet", "BFS DFS algorithms", "Dijkstra algorithm", "minimum spanning tree", "topological sorting", "shortest path algorithms", "graph theory"] canonical_url: "https://clelandco.com/cheat-sheets/algorithm-analysis/graph-algorithms" cover_image: "/images/cheat-sheets/algorithm-analysis/graph-algorithms-cover.jpg" target_audience: "Computer Science Students, Software Engineers, Graph Theory Practitioners" content_type: "reference-guide" prerequisites: ["Graph theory basics", "Data structures", "Algorithm complexity"]
Graph Algorithms
📊 Graph Representation
Adjacency Matrix
Space: | Edge Check: | Add Edge:
# Good for: Dense graphs, frequent edge queries
class GraphMatrix:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0] * vertices for _ in range(vertices)]
def add_edge(self, u, v, weight=1):
self.graph[u][v] = weight
self.graph[v][u] = weight # For undirected
Adjacency List
Space: | Edge Check: | Add Edge:
# Good for: Sparse graphs, memory efficiency
from collections import defaultdict
class GraphList:
def __init__(self):
self.graph = defaultdict(list)
def add_edge(self, u, v, weight=1):
self.graph[u].append((v, weight))
self.graph[v].append((u, weight)) # For undirected
🔍 Graph Traversal
Breadth-First Search (BFS)
Time: | Space: Use: Shortest path in unweighted graphs, level-order traversal
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
result = []
while queue:
vertex = queue.popleft()
if vertex not in visited:
visited.add(vertex)
result.append(vertex)
# Add unvisited neighbors
for neighbor in graph[vertex]:
if neighbor not in visited:
queue.append(neighbor)
return result
def bfs_shortest_path(graph, start, end):
if start == end:
return [start]
visited = {start}
queue = deque([(start, [start])])
while queue:
vertex, path = queue.popleft()
for neighbor in graph[vertex]:
if neighbor == end:
return path + [neighbor]
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None # No path found
Depth-First Search (DFS)
Time: | Space: Use: Cycle detection, topological sort, connected components
def dfs_recursive(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
result = [start]
for neighbor in graph[start]:
if neighbor not in visited:
result.extend(dfs_recursive(graph, neighbor, visited))
return result
def dfs_iterative(graph, start):
visited = set()
stack = [start]
result = []
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
result.append(vertex)
# Add neighbors in reverse order for consistent traversal
for neighbor in reversed(graph[vertex]):
if neighbor not in visited:
stack.append(neighbor)
return result
🛣️ Shortest Path Algorithms
Dijkstra's Algorithm
Time: | Space: Use: Single-source shortest path with non-negative weights
import heapq
def dijkstra(graph, start):
distances = {vertex: float('infinity') for vertex in graph}
distances[start] = 0
pq = [(0, start)]
while pq:
current_distance, current_vertex = heapq.heappop(pq)
# Skip if we've already found a better path
if current_distance > distances[current_vertex]:
continue
for neighbor, weight in graph[current_vertex]:
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))
return distances
def dijkstra_with_path(graph, start, end):
distances = {vertex: float('infinity') for vertex in graph}
previous = {vertex: None for vertex in graph}
distances[start] = 0
pq = [(0, start)]
while pq:
current_distance, current_vertex = heapq.heappop(pq)
if current_vertex == end:
break
if current_distance > distances[current_vertex]:
continue
for neighbor, weight in graph[current_vertex]:
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
previous[neighbor] = current_vertex
heapq.heappush(pq, (distance, neighbor))
# Reconstruct path
path = []
current = end
while current is not None:
path.append(current)
current = previous[current]
return distances[end], path[::-1] if path[0] == end else None
Bellman-Ford Algorithm
Time: | Space: Use: Single-source shortest path with negative weights, cycle detection
def bellman_ford(graph, start):
# Step 1: Initialize distances
distances = {vertex: float('infinity') for vertex in graph}
distances[start] = 0
# Step 2: Relax edges V-1 times
vertices = list(graph.keys())
for _ in range(len(vertices) - 1):
for vertex in vertices:
for neighbor, weight in graph[vertex]:
if distances[vertex] + weight < distances[neighbor]:
distances[neighbor] = distances[vertex] + weight
# Step 3: Check for negative cycles
for vertex in vertices:
for neighbor, weight in graph[vertex]:
if distances[vertex] + weight < distances[neighbor]:
raise ValueError("Graph contains negative cycle")
return distances
Floyd-Warshall Algorithm
Time: | Space: Use: All-pairs shortest paths
def floyd_warshall(graph):
vertices = list(graph.keys())
n = len(vertices)
# Initialize distance matrix
dist = {}
for i in vertices:
dist[i] = {}
for j in vertices:
if i == j:
dist[i][j] = 0
else:
dist[i][j] = float('infinity')
# Add edge weights
for vertex in vertices:
for neighbor, weight in graph[vertex]:
dist[vertex][neighbor] = weight
# Floyd-Warshall algorithm
for k in vertices:
for i in vertices:
for j in vertices:
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
return dist
🌳 Minimum Spanning Tree
Kruskal's Algorithm
Time: | Space: Use: Find MST using edge-based approach
class UnionFind:
def __init__(self, vertices):
self.parent = {v: v for v in vertices}
self.rank = {v: 0 for v in vertices}
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py:
return False
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
return True
def kruskal_mst(vertices, edges):
# Sort edges by weight
edges.sort(key=lambda x: x[2])
uf = UnionFind(vertices)
mst = []
total_weight = 0
for u, v, weight in edges:
if uf.union(u, v):
mst.append((u, v, weight))
total_weight += weight
if len(mst) == len(vertices) - 1:
break
return mst, total_weight
Prim's Algorithm
Time: | Space: Use: Find MST using vertex-based approach
def prim_mst(graph, start):
mst = []
visited = {start}
edges = [(weight, start, neighbor) for neighbor, weight in graph[start]]
heapq.heapify(edges)
total_weight = 0
while edges and len(visited) < len(graph):
weight, u, v = heapq.heappop(edges)
if v not in visited:
visited.add(v)
mst.append((u, v, weight))
total_weight += weight
# Add new edges from v
for neighbor, edge_weight in graph[v]:
if neighbor not in visited:
heapq.heappush(edges, (edge_weight, v, neighbor))
return mst, total_weight
🔄 Topological Sorting
Kahn's Algorithm (BFS-based)
Time: | Space:
from collections import deque, defaultdict
def topological_sort_kahn(graph):
# Calculate in-degrees
in_degree = defaultdict(int)
for vertex in graph:
for neighbor in graph[vertex]:
in_degree[neighbor] += 1
# Initialize queue with vertices having 0 in-degree
queue = deque([v for v in graph if in_degree[v] == 0])
result = []
while queue:
vertex = queue.popleft()
result.append(vertex)
# Reduce in-degree of neighbors
for neighbor in graph[vertex]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# Check for cycles
if len(result) != len(graph):
raise ValueError("Graph has a cycle")
return result
DFS-based Topological Sort
Time: | Space:
def topological_sort_dfs(graph):
visited = set()
stack = []
def dfs(vertex):
visited.add(vertex)
for neighbor in graph[vertex]:
if neighbor not in visited:
dfs(neighbor)
stack.append(vertex)
for vertex in graph:
if vertex not in visited:
dfs(vertex)
return stack[::-1] # Reverse to get topological order
🔗 Strongly Connected Components
Kosaraju's Algorithm
Time: | Space:
def kosaraju_scc(graph):
# Step 1: Get finish times with DFS
visited = set()
finish_order = []
def dfs1(vertex):
visited.add(vertex)
for neighbor in graph[vertex]:
if neighbor not in visited:
dfs1(neighbor)
finish_order.append(vertex)
for vertex in graph:
if vertex not in visited:
dfs1(vertex)
# Step 2: Create transpose graph
transpose = defaultdict(list)
for vertex in graph:
for neighbor in graph[vertex]:
transpose[neighbor].append(vertex)
# Step 3: DFS on transpose in reverse finish order
visited = set()
sccs = []
def dfs2(vertex, component):
visited.add(vertex)
component.append(vertex)
for neighbor in transpose[vertex]:
if neighbor not in visited:
dfs2(neighbor, component)
for vertex in reversed(finish_order):
if vertex not in visited:
component = []
dfs2(vertex, component)
sccs.append(component)
return sccs
📋 Algorithm Selection Guide
Problem Type | Algorithm | Time Complexity | Use When |
---|---|---|---|
Unweighted Shortest Path | BFS | Minimum hops | |
Weighted Shortest Path | Dijkstra | Non-negative weights | |
Negative Weights | Bellman-Ford | Detect negative cycles | |
All Pairs Shortest | Floyd-Warshall | Dense graph, all pairs | |
Minimum Spanning Tree | Kruskal/Prim | Connect all vertices minimally | |
Cycle Detection | DFS | Check for cycles | |
Topological Sort | Kahn/DFS | Order dependencies | |
Connected Components | DFS/BFS | Find separate components |
🎯 Common Graph Problems
Cycle Detection
def has_cycle_undirected(graph):
visited = set()
def dfs(vertex, parent):
visited.add(vertex)
for neighbor in graph[vertex]:
if neighbor == parent:
continue
if neighbor in visited or dfs(neighbor, vertex):
return True
return False
for vertex in graph:
if vertex not in visited:
if dfs(vertex, -1):
return True
return False
def has_cycle_directed(graph):
WHITE, GRAY, BLACK = 0, 1, 2
color = {vertex: WHITE for vertex in graph}
def dfs(vertex):
color[vertex] = GRAY
for neighbor in graph[vertex]:
if color[neighbor] == GRAY: # Back edge
return True
if color[neighbor] == WHITE and dfs(neighbor):
return True
color[vertex] = BLACK
return False
for vertex in graph:
if color[vertex] == WHITE:
if dfs(vertex):
return True
return False
Bipartite Check
def is_bipartite(graph):
color = {}
def bfs(start):
queue = deque([start])
color[start] = 0
while queue:
vertex = queue.popleft()
for neighbor in graph[vertex]:
if neighbor not in color:
color[neighbor] = 1 - color[vertex]
queue.append(neighbor)
elif color[neighbor] == color[vertex]:
return False
return True
for vertex in graph:
if vertex not in color:
if not bfs(vertex):
return False
return True