{"dfs":{"time":"O(V+E)","space":"O(V)"},"bfs":{"time":"O(V+E)","space":"O(V)"},"dijkstra":{"time":"O((V+E) log V)","space":"O(V)"},"union_find":{"time":"O(α(n)) per operation","space":"O(V)"}}
Graph Algorithms Skill
Atomic Responsibility: Execute graph traversal and pathfinding algorithms efficiently.
"""
Iterative DFS using explicit stack.
Use when: Avoiding recursion depth limits.
"""
Set
int
set
List
int
while
if
in
continue
# Add neighbors in reverse for correct order
for
in
reversed
if
not
in
return
BFS - Breadth First Search
defbfs(graph: Graph, start: int) -> List[int]:
"""
BFS traversal from start node.
Time: O(V+E), Space: O(V)
Use for: Shortest path in unweighted graph.
"""
visited = {start}
queue = deque([start])
result: List[int] = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph.get(node, []):
if neighbor notin visited:
visited.add(neighbor)
queue.append(neighbor)
return result
defshortest_path_unweighted(graph: Graph, start: int, end: int) -> int:
"""
Find shortest path length in unweighted graph.
Time: O(V+E), Space: O(V)
Returns:
Shortest distance, or -1 if no path exists
"""if start == end:
return0
visited = {start}
queue = deque([(start, 0)])
while queue:
node, distance = queue.popleft()
for neighbor in graph.get(node, []):
if neighbor == end:
return distance + 1if neighbor notin visited:
visited.add(neighbor)
queue.append((neighbor, distance + 1))
return -1# No path found
Dijkstra's Algorithm
defdijkstra(graph: WeightedGraph, start: int) -> Dict[int, int]:
"""
Single-source shortest paths with non-negative weights.
Time: O((V+E) log V), Space: O(V)
Args:
graph: Weighted adjacency list {node: [(neighbor, weight)]}
start: Source node
Returns:
Dictionary of shortest distances from start
Raises:
ValueError: If negative weight detected
"""
distances: Dict[int, int] = {start: 0}
pq = [(0, start)] # (distance, node)while pq:
current_dist, node = heapq.heappop(pq)
# Skip if we've found a better pathif current_dist > distances.get(node, float('inf')):
continuefor neighbor, weight in graph.get(node, []):
if weight < 0:
raise ValueError(f"Negative weight {weight} not allowed in Dijkstra")
distance = current_dist + weight
if distance < distances.get(neighbor, float('inf')):
distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))
return distances
defdijkstra_with_path(graph: WeightedGraph, start: int, end: int) -> Tuple[int, List[int]]:
"""
Dijkstra with path reconstruction.
Returns:
Tuple of (distance, path) or (inf, []) if no path
"""
distances: Dict[int, int] = {start: 0}
predecessors: Dict[int, Optional[int]] = {start: None}
pq = [(0, start)]
while pq:
current_dist, node = heapq.heappop(pq)
if node == end:
breakif current_dist > distances.get(node, float('inf')):
continuefor neighbor, weight in graph.get(node, []):
distance = current_dist + weight
if distance < distances.get(neighbor, float('inf')):
distances[neighbor] = distance
predecessors[neighbor] = node
heapq.heappush(pq, (distance, neighbor))
# Reconstruct pathif end notin distances:
returnfloat('inf'), []
path = []
current = end
while current isnotNone:
path.append(current)
current = predecessors.get(current)
return distances[end], path[::-1]
Union-Find (Disjoint Set Union)
classUnionFind:
"""
Disjoint Set Union with path compression and union by rank.
Time: O(α(n)) per operation (nearly constant)
Space: O(n)
Use for: Connected components, cycle detection, Kruskal's MST
"""def__init__(self, n: int):
"""Initialize n disjoint sets."""self.parent = list(range(n))
self.rank = [0] * n
self.components = n
deffind(self, x: int) -> int:
"""Find root of x with path compression."""ifself.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
returnself.parent[x]
defunion(self, x: int, y: int) -> bool:
"""
Unite sets containing x and y.
Returns:
True if union performed, False if already in same set
"""
px, py = self.find(x), self.find(y)
if px == py:
returnFalse# Union by rankifself.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
ifself.rank[px] == self.rank[py]:
self.rank[px] += 1self.components -= 1returnTruedefconnected(self, x: int, y: int) -> bool:
"""Check if x and y are in the same set."""returnself.find(x) == self.find(y)
defget_components(self) -> int:
"""Return number of disjoint sets."""returnself.components
Topological Sort
deftopological_sort_kahn(n: int, edges: List[Tuple[int, int]]) -> List[int]:
"""
Topological sort using Kahn's algorithm (BFS).
Time: O(V+E), Space: O(V)
Args:
n: Number of nodes (0 to n-1)
edges: List of (from, to) edges
Returns:
Topologically sorted list, or empty if cycle exists
"""
graph: Graph = {i: [] for i inrange(n)}
in_degree = [0] * n
for src, dst in edges:
graph[src].append(dst)
in_degree[dst] += 1
queue = deque([i for i inrange(n) if in_degree[i] == 0])
result: List[int] = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1if in_degree[neighbor] == 0:
queue.append(neighbor)
# Check for cycleiflen(result) != n:
return [] # Cycle detectedreturn result