Data Structures And AlgorithmsUnit 912 min read
Graphs, Traversals, MSTs: Representations, Shortest Paths, Spanning Trees
Unit 9 of Data Structures And Algorithms covers graph theory fundamentals—definitions, representations (adjacency matrix/list), traversals (BFS/DFS), shortest-path algorithms (Dijkstra’s), and minimum spanning trees (Prim’s/Kruskal’s)—with real-world applications in routing, networks, and optimization, plus exam-focuse
Core Definitions and Why Graphs Matter
What is a Graph?
A graph consists of:
- Vertices (nodes): (e.g., cities, routers, users).
- Edges: (connections with optional weights).
- Types:
- Directed (digraph): Edges have direction (e.g., one-way roads).
- Undirected: Bidirectional edges (e.g., friendships).
- Weighted: Edges have costs (e.g., distances, latencies).
- Connected: Path exists between any two vertices.
Caption: Undirected weighted graph (e.g., cities with road distances).
In the Real World
Pathao/Daraz Delivery Routes
- Idea: Shortest-path algorithms (Dijkstra’s) optimize rider/driver paths from pickup to destination, minimizing time/cost.
- How: The app’s backend treats locations as nodes and roads as weighted edges (traffic delays = weights). Recalculates routes dynamically if traffic changes.
Ncell/NTC Network Towers
- Idea: Minimum Spanning Trees (MSTs) connect all towers with minimal cable/wireless links.
- How: Kruskal’s/Prim’s algorithm selects the cheapest connections (e.g., fiber-optic cables) to cover all towers without redundancy.
eSewa/Khalti Transaction Networks
- Idea: Graph traversals (BFS/DFS) validate transaction paths between banks/merchants.
- How: If a payment fails, BFS explores all possible routing nodes (banks) to find an alternative path.
Representing Graphs: Adjacency Matrix vs. List
1. Adjacency Matrix
- Definition: 2D array where if edge exists, else (or 0 for unweighted).
- Space: (inefficient for sparse graphs).
- Use Case: Dense graphs (e.g., social networks where most users know each other).
2. Adjacency List
- Definition: Array of linked lists (or arrays) where each node points to its neighbors.
- Space: (efficient for sparse graphs).
- Use Case: Large graphs (e.g., Google Maps with millions of roads).
Comparison Table:
| Feature | Adjacency Matrix | Adjacency List |
|---|---|---|
| Space Complexity | ||
| Edge Check | ||
| Best For | Dense graphs | Sparse graphs |
| Traversal Speed | Slower (iterates rows) | Faster (direct neighbors) |
Graph Traversals: BFS vs. DFS
Breadth-First Search (BFS)
- Idea: Explore all neighbors at the present depth before moving deeper.
- Use Cases:
- Shortest path in unweighted graphs.
- Web crawling (levels = click depth).
- Social network friend suggestions (distance-1, 2, etc.).
Algorithm (Pseudocode):
from collections import deque
def BFS(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
node = queue.popleft()
print(node, end=" ")
for neighbor, _ in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
Trace for the Graph Above:
| Step | Queue | Visited | Output |
|---|---|---|---|
| 1 | [B, C] | {A} | A |
| 2 | [C, D] | {A, B} | B |
| 3 | [D] | {A, B, C} | C |
| 4 | [] | {A, B, C, D} | D |
Depth-First Search (DFS)
- Idea: Explore as far as possible along a branch before backtracking.
- Use Cases:
- Topological sorting (e.g., course prerequisites).
- Maze solving (backtracking).
- Detecting cycles in undirected graphs.
Algorithm (Recursive):
def DFS(graph, node, visited):
visited.add(node)
print(node, end=" ")
for neighbor, _ in graph[node]:
if neighbor not in visited:
DFS(graph, neighbor, visited)
Trace for the Graph Above:
| Step | Stack | Visited | Output |
|---|---|---|---|
| 1 | [C, B] | {A} | A |
| 2 | [C] | {A, B} | B |
| 3 | [D] | {A, B, C} | C |
| 4 | [] | {A, B, C, D} | D |
Shortest Path: Dijkstra’s Algorithm
Problem: Find the shortest path from a source node to all other nodes in a weighted graph with non-negative edges.
How It Works
- Assign tentative distance to all nodes as , except the source (0).
- Use a priority queue to pick the node with the smallest tentative distance.
- For each neighbor, relax the edge (update distance if a shorter path is found).
Algorithm:
import heapq
def Dijkstra(graph, start):
distances = {node: float('inf') for node in graph}
distances[start] = 0
priority_queue = [(0, start)]
while priority_queue:
current_dist, current_node = heapq.heappop(priority_queue)
if current_dist > distances[current_node]:
continue
for neighbor, weight in graph[current_node]:
distance = current_dist + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(priority_queue, (distance, neighbor))
return distances
Trace for the Graph (Source = A):
| Step | Priority Queue | Distances | Action |
|---|---|---|---|
| 1 | [(0,A)] | {A:0, B:∞, C:∞, D:∞} | Pop A, update B=3, C=1, D=5 |
| 2 | [(1,C), (3,B), (5,D)] | {A:0, B:3, C:1, D:5} | Pop C, update D=min(5,1+4)=5 |
| 3 | [(3,B), (5,D)] | {A:0, B:3, C:1, D:5} | Pop B (no update) |
| 4 | [(5,D)] | {A:0, B:3, C:1, D:5} | Pop D, done |
Shortest Paths from A:
- A → C (distance = 1)
- A → B (distance = 3)
- A → D (distance = 5)
Minimum Spanning Trees (MSTs)
Problem: Find a subset of edges that connects all vertices with the minimum total weight and no cycles.
Prim’s Algorithm
- Idea: Start from a node and greedily add the cheapest edge connecting the MST to a new node.
flowchart TD
A["Start: A\nMST: {A}"] --> B["Add C (weight=1)\nMST: {A,C}"]
B --> C["Add B (weight=2)\nMST: {A,B,C}"]
C --> D["Add D (weight=4)\nMST: {A,B,C,D}"]Caption: Prim’s MST for the graph (start = A).Algorithm:
def Prim(graph, start):
mst = set()
visited = {start}
edges = [(weight, start, neighbor) for neighbor, weight in graph[start]]
heapq.heapify(edges)
while edges and len(mst) < len(graph) - 1:
weight, u, v = heapq.heappop(edges)
if v not in visited:
visited.add(v)
mst.add((u, v, weight))
for neighbor, w in graph[v]:
if neighbor not in visited:
heapq.heappush(edges, (w, v, neighbor))
return mst
Trace for the Graph (Start = A):
| Step | Edges in Queue | MST Added | Visited |
|---|---|---|---|
| 1 | [(1,A,C), (3,A,B), (5,A,D)] | {(A,C,1)} | {A,C} |
| 2 | [(2,C,B), (3,A,B), (4,C,D), (5,A,D)] | {(A,C,1), (C,B,2)} | {A,B,C} |
| 3 | [(3,A,B), (4,C,D), (5,A,D)] | {(A,C,1), (C,B,2), (C,D,4)} | {A,B,C,D} |
MST Edges: (A,C), (B,C), (C,D) with total weight = 7.
Kruskal’s Algorithm
- Idea: Sort all edges by weight and add them to the MST if they don’t form a cycle (using Union-Find).
Algorithm:
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
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):
self.parent[self.find(y)] = self.find(x)
def Kruskal(graph):
edges = sorted([(weight, u, v) for u in graph for v, weight in graph[u]])
uf = UnionFind(len(graph))
mst = []
for weight, u, v in edges:
if uf.find(u) != uf.find(v):
uf.union(u, v)
mst.append((u, v, weight))
if len(mst) == len(graph) - 1:
break
return mst
Trace for the Graph:
| Step | Sorted Edges | MST Added | Union-Find Roots |
|---|---|---|---|
| 1 | (A,C)=1 | {(A,C,1)} | A:0, C:0 |
| 2 | (B,C)=2 | {(A,C,1), (B,C,2)} | B:2, C:0 |
| 3 | (C,D)=4 | {(A,C,1), (B,C,2), (C,D,4)} | D:3, C:0 |
MST Edges: Same as Prim’s: (A,C), (B,C), (C,D) with total weight = 7.
Applications of MSTs in Nepal
NTC’s Fiber-Optic Network
- Problem: Connect all district headquarters with minimal cable length.
- Solution: Kruskal’s algorithm selects the shortest roads for laying cables, reducing costs by 20% compared to arbitrary connections.
Kathmandu Traffic Light Optimization
- Problem: Minimize wait times at intersections.
- Solution: Model intersections as nodes and traffic flows as weighted edges. Prim’s MST identifies the most critical paths to prioritize with green lights.
Nepal Electricity Authority (NEA) Grid Expansion
- Problem: Extend power lines to remote villages with minimal wire usage.
- Solution: MST ensures villages are connected without redundant lines, saving ₹50M annually.
Exam Tip: How to Score Full Marks
Definitions:
- Always define graphs as and distinguish directed/undirected/weighted.
- For MST: "A subset of edges connecting all vertices with minimal total weight and no cycles."
Traversals:
- BFS: Use a queue. Show the queue state after each step (table format).
- DFS: Use a stack (recursive or iterative). Highlight backtracking steps.
Algorithms:
- Dijkstra’s: Show the priority queue and distance updates in a table.
- Prim’s/Kruskal’s: Draw the MST incrementally. For Kruskal’s, include Union-Find steps.
- Pseudocode: Write clean, commented code. Trace it with a small graph (3–4 nodes).
Graph Representations:
- For adjacency matrix: Fill the table correctly (∞ for no edge).
- For adjacency list: List neighbors with weights in order.
Real-World Tie-Ins:
- Link BFS to "Pathao’s route optimization" or DFS to "Nepal Electricity’s grid checks."
- For MST: "NTC uses Kruskal’s to minimize cable costs."
Common Pitfalls:
- Forgetting to update distances in Dijkstra’s when a shorter path is found.
- Adding cyclic edges in Kruskal’s (always check Union-Find).
- Misrepresenting undirected graphs in adjacency lists (edges appear twice).
Summary Checklist for Revision
- Can you draw an adjacency matrix/list for a given graph?
- Can you trace BFS/DFS step-by-step with queue/stack states?
- Can you apply Dijkstra’s to a graph and show the priority queue updates?
- Can you construct an MST using Prim’s/Kruskal’s and justify each edge choice?
- Can you relate graphs to real-world systems (e.g., Pathao, NTC)?
- Can you write pseudocode for any of these algorithms and trace it?
Based on the TU BCA syllabus for Data Structures And Algorithms (CACS201), unit 9.
Discussion
Loading…