Algorithms

Module 7· 8 min read·completed

7. Graph Algorithms & Traversals

BFS, DFS (edge classification & cycle detection), Topological Sort (Kahn & DFS), Minimum Spanning Trees (Kruskal & Prim), and Shortest Paths (Dijkstra, Bellman-Ford, Floyd-Warshall) across C, C++, and Python

NPTEL / GATE CS / UGC NET Core Subject Key Exam Questions: DFS Edge Classification (Back Edge     \iff Cycle!), Topological Sort validity conditions (DAG only), Dijkstra vs Bellman-Ford for negative weights, and Kruskal vs Prim MST cut properties.


1. Prerequisites & What You Should Know#

Before analyzing graph algorithms, ensure you understand:

  • Graph Representations: Adjacency Matrix (O(V2)O(V^2) space) vs Adjacency List (O(V+E)O(V + E) space).
  • Visited States: Tracking visited vertices via boolean array or three-color states (White = Unvisited, Gray = Active in recursion stack, Black = Fully processed).
  • FIFO Queues & LIFO Stacks: BFS uses a FIFO queue; DFS uses a LIFO call stack.
  • Priority Queues (Heaps): Used by Prim's and Dijkstra's algorithms for efficient minimum extraction in O(logV)O(\log V) time.

2. Intuition & Real-World Mental Models#

Diagram / Text
1. Breadth-First Search (BFS): Ripples in a Pond
   - Drop a stone in water: ripples expand outward concentrically.
   - Level 0: Source node
   - Level 1: Immediate neighbors (distance = 1)
   - Level 2: Friends of friends (distance = 2)
   - Guarantees SHORTEST PATH in unweighted graphs!

2. Depth-First Search (DFS): Navigating a Labyrinth
   - Follow one path until you hit a dead end.
   - Unwind your ball of string (backtrack) to the last fork in the road.
   - Try the next unvisited corridor.

3. Topological Sort: College Course Prerequisites
   - CS101 -> Data Structures -> Algorithms -> Machine Learning.
   - You cannot take Machine Learning until its prerequisites are completed.
   - Only possible on Directed Acyclic Graphs (DAGs)!

3. Graph Traversals: BFS vs DFS#

3.1 Breadth-First Search (BFS)#

  • Traverses level-by-level using a FIFO Queue.
  • Time Complexity: Θ(V+E)\Theta(V + E), Space Complexity: O(V)O(V).
  • Finds shortest path in unweighted graphs.

3.2 Depth-First Search (DFS) & Edge Classification#

Explores deeply along branches using a LIFO Stack / Recursion. Tracks Discovery Time d[u]d[u] and Finish Time f[u]f[u].

The 4 Edge Types in Directed DFS (GATE Favorite):#

Diagram / Text
Tree Edge:    u ---------> v (Discovers unvisited vertex v)
Back Edge:    u <--------- v (Points to an active ancestor on call stack -> CYCLE!)
Forward Edge: u ---------> v (Points to an already completed descendant)
Cross Edge:   u ---------> v (Points between unrelated subtrees)
Important

GATE Fundamental Theorem: A directed graph contains a cycle IF AND ONLY IF its DFS traversal produces at least one Back Edge! In terms of timestamps: A directed edge (u,v)(u, v) is a Back Edge if d[v]<d[u]<f[u]<f[v]d[v] < d[u] < f[u] < f[v] (vv is still active when uu is discovered).


4. Topological Sorting (DAGs Only)#

A Topological Sort of a directed graph is a linear ordering of its vertices such that for every directed edge uvu \to v, vertex uu comes before vv in the ordering.

Diagram / Text
Two Standard Approaches:
1. Kahn's Algorithm (BFS with In-Degrees):
   - Compute in-degree for every vertex.
   - Enqueue all vertices with in-degree = 0.
   - While queue not empty: dequeue u, add to topo order, decrement in-degree of all neighbors.
   - If neighbor's in-degree reaches 0, enqueue it.
   - CYCLE DETECTION: If processed nodes < V, graph has a cycle!

2. DFS-Based Topological Sort:
   - Run DFS on all unvisited nodes.
   - As each node FINISHES (f[u]), push it onto a stack.
   - Pop stack from top to bottom -> Valid topological order!

5. Minimum Spanning Trees & Shortest Path Comparison#

ProblemAlgorithmParadigmTime ComplexityNegative Weights?Key Data Structure
MSTKruskal'sGreedy (Edge-centric)O(ElogE)O(E \log E)N/A (Undirected)Disjoint Set Union (DSU)
MSTPrim'sGreedy (Vertex-growing)O((V+E)logV)O((V + E) \log V)N/A (Undirected)Min-Heap / Priority Queue
Single-Source SPDijkstra'sGreedy (Distance relaxation)O((V+E)logV)O((V + E) \log V)NO (Greedy fails!)Min-Heap / Priority Queue
Single-Source SPBellman-FordDP (Relax all edges V1V-1 times)O(VE)O(V \cdot E)YES (Detects neg cycles)Edge List array
All-Pairs SPFloyd-WarshallDP (kk-th intermediate vertex)O(V3)O(V^3)YES (No neg cycles)2D Adjacency Matrix

5.1 Why Dijkstra Fails on Negative Edge Weights#

Dijkstra greedily marks a vertex as "visited / finalized" once it is popped from the min-heap, assuming no future path can ever reduce its distance. However, a negative-weight edge downstream can retroactively decrease the total path cost! Solution: Use Bellman-Ford, which relaxes all E|E| edges V1|V|-1 times. If an edge can still be relaxed on the V|V|-th pass, a negative weight cycle exists!


6. Real-World Applications#

  1. GPS Navigation (Google Maps, Apple Maps): Uses bidirectional Dijkstra / A* search with contraction hierarchies to compute driving routes in milliseconds over millions of road intersections.
  2. Build Systems & Package Managers (npm, Cargo, Makefile): Constructs a DAG of library dependencies and executes builds via Topological Sort.
  3. Garbage Collection (Java JVM, V8 Engine): Mark-and-sweep tracing garbage collectors perform BFS/DFS starting from Root references to discover live reachable objects on the heap.
  4. Internet Routing (BGP & OSPF Protocols): The Open Shortest Path First (OSPF) routing protocol runs Dijkstra's algorithm inside every core Internet router.

7. Implementation in C, C++, and Python with Syntax Logic#

Dijkstra's Algorithm via Adjacency Matrix & Distance Array
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>

/**
 * C Syntax Logic Note:
 * 1. INT_MAX: Represents infinity for initial distance states.
 * 2. Relaxation: If dist[u] != INT_MAX and dist[u] + graph[u][v] < dist[v], update dist[v].
 * 3. O(V^2) implementation: Suitable for dense graphs where E ≈ V^2.
 */

#define V 5

int minDistance(int dist[], bool sptSet[]) {
    int min = INT_MAX, min_index = -1;
    for (int v = 0; v < V; v++) {
        if (!sptSet[v] && dist[v] <= min) {
            min = dist[v];
            min_index = v;
        }
    }
    return min_index;
}

void dijkstra(int graph[V][V], int src) {
    int dist[V];
    bool sptSet[V];

    for (int i = 0; i < V; i++) {
        dist[i] = INT_MAX;
        sptSet[i] = false;
    }

    dist[src] = 0; // Distance to self is 0

    for (int count = 0; count < V - 1; count++) {
        int u = minDistance(dist, sptSet);
        if (u == -1) break;

        sptSet[u] = true;

        for (int v = 0; v < V; v++) {
            // Relaxation condition
            if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX
                && dist[u] + graph[u][v] < dist[v]) {
                dist[v] = dist[u] + graph[u][v];
            }
        }
    }
}