Advanced Graph Algorithms in C: Topological Sort, SCC (Kosaraju & Tarjan) & Ford-Fulkerson Max Flow

1. What is Topological Sort?

Topological Sort is an ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge (u → v), vertex u appears before vertex v in the ordering. It’s used in scheduling tasks with dependencies.

2. How does Topological Sort work?

3. Can you give an example of Topological Sort using DFS in C?

Example: Topological sort for a DAG with 6 vertices and edges (5→2, 5→0, 4→0, 4→1, 2→3, 3→1).

#include <stdio.h>
#include <stdlib.h>

#define V 6

struct Node {
    int vertex;
    struct Node* next;
};

struct Graph {
    struct Node* adjList[V];
    int visited[V];
};

void initGraph(struct Graph* g) {
    for (int i = 0; i < V; i++) {
        g->adjList[i] = NULL;
        g->visited[i] = 0;
    }
}

void addEdge(struct Graph* g, int src, int dest) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (!newNode) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    newNode->vertex = dest;
    newNode->next = g->adjList[src];
    g->adjList[src] = newNode;
}

void freeGraph(struct Graph* g) {
    for (int i = 0; i < V; i++) {
        struct Node* temp = g->adjList[i];
        while (temp) {
            struct Node* next = temp->next;
            free(temp);
            temp = next;
        }
    }
}

void dfsTopo(struct Graph* g, int v, int* stack, int* stackIndex) {
    g->visited[v] = 1;
    struct Node* temp = g->adjList[v];
    while (temp) {
        if (!g->visited[temp->vertex]) {
            dfsTopo(g, temp->vertex, stack, stackIndex);
        }
        temp = temp->next;
    }
    stack[(*stackIndex)++] = v; // Push to stack after exploring neighbors
}

void topologicalSort(struct Graph* g) {
    int stack[V], stackIndex = 0;
    for (int i = 0; i < V; i++) {
        if (!g->visited[i]) {
            dfsTopo(g, i, stack, &stackIndex);
        }
    }
    printf("Topological Sort: ");
    for (int i = stackIndex - 1; i >= 0; i--) {
        printf("%d ", stack[i]); // Print in reverse order
    }
    printf("\n");
}

int main() {
    struct Graph g;
    initGraph(&g);
    addEdge(&g, 5, 2);
    addEdge(&g, 5, 0);
    addEdge(&g, 4, 0);
    addEdge(&g, 4, 1);
    addEdge(&g, 2, 3);
    addEdge(&g, 3, 1);
    topologicalSort(&g); // Output: Topological Sort: 5 4 2 3 1 0 (order may vary)
    freeGraph(&g); // Clean up memory
    return 0;
}
      

4. What are the time and space complexities of Topological Sort?

5. What are the applications of Topological Sort?

6. What is a Strongly Connected Component (SCC)?

An SCC in a directed graph is a maximal subgraph where there is a path from every vertex to every other vertex. SCCs are used to analyze connectivity in directed graphs.

7. What is Kosaraju’s Algorithm?

Kosaraju’s Algorithm finds all SCCs in a directed graph using two DFS passes: one on the original graph to get finishing times, and one on the transposed graph in reverse finishing order to identify SCCs.

8. Can you give an example of Kosaraju’s Algorithm in C?

Example: Finding SCCs in a directed graph with 5 vertices and edges (1→0, 0→2, 2→1, 0→3, 3→4).

#include <stdio.h>
#include <stdlib.h>

#define V 5

struct Node {
    int vertex;
    struct Node* next;
};

struct Graph {
    struct Node* adjList[V];
    int visited[V];
};

void initGraph(struct Graph* g) {
    for (int i = 0; i < V; i++) {
        g->adjList[i] = NULL;
        g->visited[i] = 0;
    }
}

void addEdge(struct Graph* g, int src, int dest) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (!newNode) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    newNode->vertex = dest;
    newNode->next = g->adjList[src];
    g->adjList[src] = newNode;
}

void freeGraph(struct Graph* g) {
    for (int i = 0; i < V; i++) {
        struct Node* temp = g->adjList[i];
        while (temp) {
            struct Node* next = temp->next;
            free(temp);
            temp = next;
        }
    }
}

void dfsFill(struct Graph* g, int v, int* stack, int* stackIndex) {
    g->visited[v] = 1;
    struct Node* temp = g->adjList[v];
    while (temp) {
        if (!g->visited[temp->vertex]) {
            dfsFill(g, temp->vertex, stack, stackIndex);
        }
        temp = temp->next;
    }
    stack[(*stackIndex)++] = v; // Push to stack after finishing
}

void dfsSCC(struct Graph* g, int v) {
    g->visited[v] = 1;
    printf("%d ", v);
    struct Node* temp = g->adjList[v];
    while (temp) {
        if (!g->visited[temp->vertex]) {
            dfsSCC(g, temp->vertex);
        }
        temp = temp->next;
    }
}

void kosaraju(struct Graph* g, struct Graph* gT) {
    int stack[V], stackIndex = 0;
    for (int i = 0; i < V; i++) g->visited[i] = 0;
    // First DFS: Fill stack with finishing times
    for (int i = 0; i < V; i++) {
        if (!g->visited[i]) {
            dfsFill(g, i, stack, &stackIndex);
        }
    }
    // Second DFS: Process transposed graph
    for (int i = 0; i < V; i++) gT->visited[i] = 0;
    printf("Strongly Connected Components:\n");
    for (int i = stackIndex - 1; i >= 0; i--) {
        if (!gT->visited[stack[i]]) {
            dfsSCC(gT, stack[i]);
            printf("\n");
        }
    }
}

int main() {
    struct Graph g, gT;
    initGraph(&g);
    initGraph(&gT);
    addEdge(&g, 1, 0); addEdge(&gT, 0, 1);
    addEdge(&g, 0, 2); addEdge(&gT, 2, 0);
    addEdge(&g, 2, 1); addEdge(&gT, 1, 2);
    addEdge(&g, 0, 3); addEdge(&gT, 3, 0);
    addEdge(&g, 3, 4); addEdge(&gT, 4, 3);
    kosaraju(&g, &gT); // Output: Strongly Connected Components: 0 1 2, 3, 4
    freeGraph(&g);
    freeGraph(&gT);
    return 0;
}
      

9. What are the time and space complexities of Kosaraju’s Algorithm?

10. What is Tarjan’s Algorithm for SCCs?

Tarjan’s Algorithm finds SCCs in a single DFS pass by maintaining a stack and tracking discovery times and low-link values to identify strongly connected components.

11. How does Tarjan’s Algorithm work?

12. Can you give an example of Tarjan’s Algorithm in C?

Example: Finding SCCs in a directed graph with 5 vertices and edges (1→0, 0→2, 2→1, 0→3, 3→4).

#include <stdio.h>
#include <stdlib.h>

#define V 5
#define min(a, b) ((a) < (b) ? (a) : (b))

struct Node {
    int vertex;
    struct Node* next;
};

struct Graph {
    struct Node* adjList[V];
    int visited[V];
};

void initGraph(struct Graph* g) {
    for (int i = 0; i < V; i++) {
        g->adjList[i] = NULL;
        g->visited[i] = 0;
    }
}

void addEdge(struct Graph* g, int src, int dest) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (!newNode) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    newNode->vertex = dest;
    newNode->next = g->adjList[src];
    g->adjList[src] = newNode;
}

void freeGraph(struct Graph* g) {
    for (int i = 0; i < V; i++) {
        struct Node* temp = g->adjList[i];
        while (temp) {
            struct Node* next = temp->next;
            free(temp);
            temp = next;
        }
    }
}

void tarjanDFS(struct Graph* g, int v, int* disc, int* low, int* stack, int* stackIndex, int* time) {
    disc[v] = low[v] = ++(*time);
    stack[(*stackIndex)++] = v;
    g->visited[v] = 1;
    
    struct Node* temp = g->adjList[v];
    while (temp) {
        int u = temp->vertex;
        if (!disc[u]) {
            tarjanDFS(g, u, disc, low, stack, stackIndex, time);
            low[v] = min(low[v], low[u]);
        } else if (g->visited[u]) {
            low[v] = min(low[v], disc[u]);
        }
        temp = temp->next;
    }
    
    if (disc[v] == low[v]) {
        printf("SCC: ");
        while (*stackIndex > 0) {
            int w = stack[(*stackIndex) - 1];
            (*stackIndex)--;
            printf("%d ", w);
            g->visited[w] = 0;
            if (w == v) break;
        }
        printf("\n");
    }
}

void tarjan(struct Graph* g) {
    int disc[V] = {0}, low[V] = {0}, stack[V], stackIndex = 0, time = 0;
    for (int i = 0; i < V; i++) {
        if (!disc[i]) {
            tarjanDFS(g, i, disc, low, stack, &stackIndex, &time);
        }
    }
}

int main() {
    struct Graph g;
    initGraph(&g);
    addEdge(&g, 1, 0);
    addEdge(&g, 0, 2);
    addEdge(&g, 2, 1);
    addEdge(&g, 0, 3);
    addEdge(&g, 3, 4);
    printf("Strongly Connected Components:\n");
    tarjan(&g); // Output: SCC: 4, 3, 0 1 2 (order may vary)
    freeGraph(&g);
    return 0;
}
      

13. What are the time and space complexities of Tarjan’s Algorithm?

14. How do Kosaraju’s and Tarjan’s Algorithms compare?

15. What are the applications of SCCs?

16. What is the Network Flow problem?

The Network Flow problem involves finding the maximum flow from a source to a sink in a directed graph with edge capacities, used in applications like traffic flow or matching.

17. What is the Ford-Fulkerson Algorithm?

Ford-Fulkerson finds the maximum flow by repeatedly finding augmenting paths (paths from source to sink with available capacity) and pushing flow until no such paths exist. It uses DFS or BFS (e.g., Edmonds-Karp with BFS).

18. Can you give an example of Ford-Fulkerson in C (using DFS)?

Example: Computing maximum flow in a flow network with 6 vertices and given capacities.

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#define V 6
#define min(a, b) ((a) < (b) ? (a) : (b))

bool dfs(int residual[V][V], int s, int t, int parent[], int visited[]) {
    visited[s] = 1;
    if (s == t) return true;
    for (int v = 0; v < V; v++) {
        if (!visited[v] && residual[s][v] > 0) {
            parent[v] = s;
            if (dfs(residual, v, t, parent, visited)) return true;
        }
    }
    return false;
}

int fordFulkerson(int graph[V][V], int s, int t) {
    if (s < 0 || t < 0 || s >= V || t >= V) return -1; // Invalid source/sink
    int residual[V][V];
    memcpy(residual, graph, sizeof(residual)); // Copy graph
    int parent[V], max_flow = 0;
    
    while (true) {
        int visited[V] = {0};
        if (!dfs(residual, s, t, parent, visited)) break;
        
        int path_flow = 1000000; // Large number for min capacity
        for (int v = t; v != s; v = parent[v]) {
            int u = parent[v];
            path_flow = min(path_flow, residual[u][v]);
        }
        
        for (int v = t; v != s; v = parent[v]) {
            int u = parent[v];
            residual[u][v] -= path_flow; // Update forward edge
            residual[v][u] += path_flow; // Update reverse edge
        }
        max_flow += path_flow;
    }
    return max_flow;
}

int main() {
    int graph[V][V] = {
        {0, 16, 13, 0, 0, 0},
        {0, 0, 10, 12, 0, 0},
        {0, 4, 0, 0, 14, 0},
        {0, 0, 9, 0, 0, 20},
        {0, 0, 0, 7, 0, 4},
        {0, 0, 0, 0, 0, 0}
    };
    int result = fordFulkerson(graph, 0, 5);
    if (result == -1) {
        printf("Invalid input\n");
    } else {
        printf("Maximum Flow: %d\n", result); // Output: Maximum Flow: 23
    }
    return 0;
}
      

19. What are the time and space complexities of Ford-Fulkerson?

20. What are the applications of Ford-Fulkerson?

21. What are the advantages and disadvantages of Ford-Fulkerson?