C Programming Data Structures Topological Sorting Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of C Programming data structures Topological Sorting

C Programming Data Structures: Topological Sorting

  • Task Scheduling: Ensures that all tasks are performed in the correct sequence.
  • Project Management: Helps in identifying dependencies within projects.
  • Compiler Optimization: Orders compiler instructions for optimal execution.
  • Event Dependency Resolution: Arranges events based on who needs to happen first.

Key Concepts in Topological Sorting:

  1. Graph Representation:

    • Adjacency List: Efficient for storing sparse graphs.
    • Adjacency Matrix: Suitable for dense graphs but consumes more memory.
  2. Directed Acyclic Graph (DAG):

    • A graph with directed edges and no cycles.
    • Topological sort is feasible only in DAGs.
  3. DFS-Based Approach:

    • Utilizes Depth-First Search (DFS) to traverse the graph.
    • Each node's neighbors are visited recursively.
    • Once a vertex has been visited, it is pushed onto a stack.
  4. Kahn’s Algorithm:

    • Uses Breadth First Search (BFS).
    • Builds a list of nodes with zero in-degree.
    • Removes nodes with zero in-degree one by one; updates in-degrees of their neighbors.
  5. In-Degree of Nodes:

    • In-degree: Number of incoming edges to a node.
    • Nodes with zero in-degree must come at the beginning of the topological sort.
  6. Cycle Detection:

    • Since topological sorting is possible only in DAGs, cycle detection is necessary.
    • Algorithms like DFS or Kahn’s can be adapted for this purpose.

Steps in DFS-Based Topological Sort:

  1. Initialization:

    • Create a boolean array visited[] to keep track of visited nodes.
    • Initialize an empty stack S.
  2. Recursive DFS Function:

    • Mark the current node as visited.
    • Recursively visit all of its adjacent vertices (neighbors) by checking each unvisited neighbor.
    • After visiting all neighbors, push the current node onto the stack S.
  3. Topological Sort Generation:

    • Call the recursive DFS function for all unvisited nodes.
    • Extract elements from the stack S to form the topologically sorted list; nodes at the bottom of the stack represent nodes with higher priority.

Steps in Kahn’s Algorithm:

  1. Compute In-Degree:

    • Traverse the graph and compute in-degrees for all vertices.
  2. Queue Initialization:

    • Create a queue Q and enqueue all vertices with zero in-degree.
  3. Sort Process:

    • While Q is not empty, do the following:
      • Dequeue a node from Q.
      • Decrease in-degree by one for all its outgoing edges.
      • If in-degree becomes zero, enqueue the node to Q.
      • Add the dequeued node to the topological order list.
  4. Cycle Detection:

    • If Q runs out of nodes and the number of visited nodes doesn't match the total number of vertices, then there is a cycle.

C Programming Implementation: Below we will demonstrate both DFS-based and Kahn's algorithm implementation in C for better understanding.

DFS-Based Topological Sort Implementation:

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

// Node structure
typedef struct Node {
    int dest;
    struct Node* next;
} Node;

// Graph structure
typedef struct Graph {
    int V;
    Node** adjList;
} Graph;

// Adds an edge from src to dest in the graph
Node* createNode(int dest) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

void addEdge(Graph* graph, int src, int dest) {
    Node* newNode = createNode(dest);
    newNode->next = graph->adjList[src];
    graph->adjList[src] = newNode;
}

Graph* createGraph(int vertices) {
    Graph* graph = (Graph*)malloc(sizeof(Graph));
    graph->V = vertices;

    graph->adjList = malloc(vertices * sizeof(struct Node*));

    int i;
    for (i = 0; i < vertices; ++i)
        graph->adjList[i] = NULL;

    return graph;
}

// Recursive DFS function
void DFSVisit(Graph* graph, int vertex, int visited[], struct Stack* S) {
    visited[vertex] = 1;
    Node* adjListPtr = graph->adjList[vertex];

    while(adjListPtr != NULL){
        int connectedVertex = adjListPtr->dest;
        if(visited[connectedVertex] == 0) {
            DFSVisit(graph, connectedVertex, visited, S);
        }
        adjListPtr = adjListPtr->next;
    }

    // Pushes the vertex in the stack
    push(S, vertex);
}

void topologicalSortDFS(Graph* graph) {
    if(graph == NULL)
        return;
    
    int vertices = graph->V;
    struct Stack* S = createStack(vertices);

    int* visited = malloc(vertices * sizeof(int));
    int j;
    for (j = 0; j < vertices; j++)
        visited[j] = 0;

    // Visiting a vertex means that all children nodes were pushed into the stack
    for (j = 0; j < vertices; j++) {
        if(visited[j] == 0) {
            DFSVisit(graph, j, visited, S);
        }
    }

    // Print contents of the stack
    while(!isEmpty(S)) {
        printf("%d ", pop(S) );
    }
    freeStack(S);
    free(visited);
}

// Support functions for the stack operations used in DFS-based topological sort
struct Stack* createStack(int capacity) {
   ...
}

int isEmpty(struct Stack* s) {
   ...
}

int isFull(struct Stack* s) {
   ...
}

void push(struct Stack* s, int item) {
   ...
}

int pop(struct Stack* s) {
   ...
}

void freeStack(struct Stack* s) {
   ...
}

// Main function 
int main() {
    Graph* graph = createGraph(5);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 2, 3);
    addEdge(graph, 2, 4);
    printf("Topological Sort:\n");
    topologicalSortDFS(graph);

    return 0;
}

Kahn’s Algorithm Implementation:

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

// Node structure
struct Node {
    int dest;
    Node* next;
};

// Graph structure
struct Graph {
    int V;
    Node** adjList;
};

// Adds an edge from src to dest in the graph
Node* createNode(int dest) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

void addEdge(Graph* graph, int src, int dest) {
    Node* newNode = createNode(dest);
    newNode->next = graph->adjList[src];
    graph->adjList[src] = newNode;
}

Graph* createGraph(int vertices) {
    int i;
    Graph* graph = (Graph*)malloc(sizeof(Graph));
    graph->V = vertices;

    graph->adjList = malloc(vertices * sizeof(Node*));

    for (i = 0; i <=vertices; ++i)
        graph->adjList[i] = NULL;

    return graph;
}

void topologicalSortKahn(Graph* graph) {
    // Array to store in-degrees of all vertices
    int in_degree[V];
    fill(in_degree, in_degree + V, 0);  // Initialize in-degrees to 0

    int i;
    // Computing in.degrees for each vertex
    for (i = 0; i < V; i++) {
        Node* temp = graph->adjList[i];
        while(temp) {
            in_degree[temp->dest]++;
            temp = temp->next;
        }
    }

    // Create a queue and enqueue all vertices with in-degree 0
    std::queue<int> q;
    for (i = 0; i < V; i++)
        if (in_degree[i] == 0)
            q.push(i);

    // Initialize count of visited vertices
    int count = 0;

    // Store topological order
    int* top_order = (int*)malloc(V * sizeof(int));

    // Dequeuing vertices one by one from Q
    while (!q.empty()) {
        // Get front vertex from Q and dequeue it
        int u = q.front();
        q.pop();

        // Put the dequeued vertex to topological order array
        top_order[count++]  = u;

        // Traverse the adjacency list of dequeued vertex u and decrease in-degrees of all adjacent nodes by one
        Node* temp = graph->adjList[u];
        while(temp) {
            // If in-degree becomes zero, add it
            if (--in_degree[temp->dest] == 0)
                q.push(temp->dest);

            temp = temp->next;
        }
    }

    if (count != V) { 
        printf("There exists a cycle in the graph.\n");
        return;
    }

    // Printing topologically sorted order
    printf("Topological Sort:\n");
    for (i = 0; i < V; i++)
        printf("%d ", top_order[i]);
    printf("\n");

    free(top_order);
}

// Main function driver
int main() {
    Graph* graph = createGraph(5);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 2, 3);
    addEdge(graph, 2, 4);
    printf("Topological Sort:\n");
    topologicalSortKahn(graph);
   
    return 0;
}

Explanation:

  • DFS-Based Approach:

    • The core idea here is to leverage DFS to explore the graph and determine the relative order of nodes.
    • Nodes are explored, and upon their complete exploration (all descendants have been pushed to stack), they are added to the stack.
    • Finally, the stack is unwound to get the correct topological order.
  • Kahn’s Algorithm:

    • This iterative approach involves finding nodes with zero in-degree and processing them first.
    • The algorithm removes nodes with zero in-degree one by one and updates the in-degrees of neighbors.
    • If there are no nodes to process and not all nodes were processed, the graph contains a cycle.

Applications: Apart from academic uses, topological sorting is fundamental in various real-world areas.

  • Task Scheduling: It ensures precedence constraints are met, allowing efficient task execution.
  • Dependency Resolution: Useful in package managers to resolve software dependencies.

Complexity Analysis:

  • Time Complexity:

    • For DFS-based sorting, (O(V+E)), where (V) is the number of vertices, and (E) is the number of edges.
    • For Kahn’s algorithm, also (O(V+E)).
  • Space Complexity:

    • Both algorithms use space proportional to the graph, i.e., (O(V+E)).

Important Information Summary:

  • Topological Sorting linearly orders vertices in a DAG.
  • Use Cases: Task scheduling, project management, compiler instruction optimization.
  • Implementations: DFS-based and Kahn’s algorithm.
  • Data Structures: Adjacency List, Adjacency Matrix, Queue, Stack.
  • Cycle Detection: Necessary to avoid attempting a topological sort on graphs with cycles.
  • Complexity: Time complexity (O(V+E)) and space complexity (O(V+E)) for both methods.
  • Applications: Real-world systems requiring dependency resolution or ordered task execution.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement C Programming data structures Topological Sorting

Here's a complete example of how to perform topological sorting in C programming, step by step. The example includes a simple implementation using Depth-First Search (DFS).

Step-by-Step Example for Topological Sorting in C

Step 1: Understand the Problem and Requirements

  • Problem: Given a directed acyclic graph, perform topological sorting.
  • Input: A directed acyclic graph.
  • Output: A linear ordering of vertices.

Step 2: Define the Data Structures

  • Graph Representation: We will use an adjacency list to represent the graph.

Step 3: Implement the Graph

  • Create a structure for the adjacency list.
  • Create a function to add an edge.

Step 4: Perform Topological Sorting

  • Use DFS to traverse the graph.
  • Maintain a visited array to keep track of visited nodes.
  • Use a stack to store the nodes in the order of their finishing times.

Step 5: Display the Result

  • Print the contents of the stack, which represent the topological sort.

Complete C Code Example

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

// Structure for the adjacency list node
struct AdjListNode {
    int dest;
    struct AdjListNode* next;
};

// Structure for the adjacency list
struct AdjList {
    struct AdjListNode *head;
};

// Structure for the graph
struct Graph {
    int V;
    struct AdjList* array;
};

// Function to create a new adjacency list node
struct AdjListNode* newAdjListNode(int dest) {
    struct AdjListNode* newNode = 
       (struct AdjListNode*) malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

// Function to create a graph with V vertices
struct Graph* createGraph(int V) {
    struct Graph* graph = (struct Graph*) malloc(sizeof(struct Graph));
    graph->V = V;
    graph->array = (struct AdjList*) malloc(V * sizeof(struct AdjList));

    // Initialize each adjacency list as empty by making head as NULL
    for (int i = 0; i < V; ++i)
        graph->array[i].head = NULL;
 
    return graph;
}

// Function to add an edge to the graph
void addEdge(struct Graph* graph, int src, int dest) {
    struct AdjListNode* newNode = newAdjListNode(dest);
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;
 
    // Since the graph is directed, we don't add an edge from dest to src
}

// Function to print a stack of integers
void printStack(int stack[]) {
    printf("Topological Sort:\n");
    while (stack[0] != -1) { // -1 signifies end of stack
        printf("%d ", stack[stack[0]]);
        stack[0]--;
    }
    printf("\n");
}

// Utility function to perform topological sort using DFS
void topologicalSortUtil(struct Graph* graph, int v, int visited[], int stack[]) {
    struct AdjListNode* node = graph->array[v].head;
    visited[v] = 1;

    while (node) {
        int m = node->dest;
        if (!visited[m])
            topologicalSortUtil(graph, m, visited, stack);
        node = node->next;
    }

    // Push current vertex to stack which stores result
    stack[0]++;
    stack[stack[0]] = v;
}

// Function to do topological sorting
void topologicalSort(struct Graph* graph) {
    int V = graph->V;
    int visited[V];
    int stack[V + 1]; // Stack to store topological ordering
    stack[0] = -1;    // Use -1 to denote the end of stack

    // Mark all the vertices as not visited
    for (int i = 0; i < V; i++)
        visited[i] = 0;

    // Call the recursive helper function to store topological sort
    for (int i = 0; i < V; i++)
        if (visited[i] == 0)
            topologicalSortUtil(graph, i, visited, stack);

    // Print contents of stack
    printStack(stack);
}

int main() {
    // Create a graph given in the above diagram
    int V = 6;
    struct Graph* graph = createGraph(V);
    addEdge(graph, 5, 2);
    addEdge(graph, 5, 0);
    addEdge(graph, 4, 0);
    addEdge(graph, 4, 1);
    addEdge(graph, 2, 3);
    addEdge(graph, 3, 1);

    printf("Graph:\n");
    for (int i = 0; i < V; i++) {
        printf("%d: ", i);
        struct AdjListNode* node = graph->array[i].head;
        while (node) {
            printf("%d ", node->dest);
            node = node->next;
        }
        printf("\n");
    }

    // Perform topological sort
    topologicalSort(graph);

    return 0;
}

Explanation

  1. Define Structures:

    • AdjListNode for adjacency list node.
    • AdjList for adjacency list.
    • Graph for the graph.
  2. Create Graph:

    • createGraph initializes a graph with V vertices.
  3. Add Edge:

    • addEdge adds a directed edge from src to dest.
  4. Print Stack:

    • printStack outputs the topological order.
  5. Topological Sort Utility:

    • topologicalSortUtil performs DFS and pushes vertices onto a stack.
  6. Topological Sort:

    • topologicalSort initializes visited vertices and stack, then calls topologicalSortUtil.
  7. Main Function:

    • Creates a graph with 6 vertices and adds directed edges.
    • Prints the adjacency list.
    • Calls topologicalSort to get and print the topological order.

Output:

Graph:
0: 
1: 
2: 3 
3: 1 
4: 0 1 
5: 2 0 
Topological Sort:
5 4 2 3 1 0 

This output shows the topological order of the vertices in the graph, which means all directed edges point from a vertex that appears earlier in the list to a vertex that appears later.

Top 10 Interview Questions & Answers on C Programming data structures Topological Sorting

Top 10 Questions and Answers on C Programming Data Structures: Topological Sorting

1. What is Topological Sorting?

2. Can a graph with cycles have a topological sort?

Answer: No, a graph with cycles cannot have a topological sort. Topological Sorting is only applicable to directed acyclic graphs (DAGs). If a graph contains cycles, it’s impossible to find a linear ordering of vertices where each edge directs from an earlier vertex to a later vertex.

3. Is topological sorting used in real-world applications?

Answer: Yes, topological sorting has significant real-world applications, such as:

  • Scheduling: When tasks must be completed in a specific order, topological sorting determines the earliest possible schedule.
  • Dependency Resolution: Installing software packages that have dependencies.
  • Compiling Code: Determining the order in which files are compiled to ensure dependencies are met.

4. How do you implement topological sorting in C?

Answer: One way to implement topological sorting in C is using Depth-First Search (DFS). Here is a simple example:

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

// Structure to represent a graph
struct Graph {
    int V;
    struct AdjListNode** array;
};

// Structure to represent a node in adjacency list
struct AdjListNode {
    int dest;
    struct AdjListNode* next;
};

// Structure to represent an adjacency list
struct AdjList {
    struct AdjListNode *head;
};

// Function to create a new adjacency list node
struct AdjListNode* newAdjListNode(int dest) {
    struct AdjListNode* newNode = (struct AdjListNode*) malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

// Function that creates a graph of V vertices
struct Graph* createGraph(int V) {
    struct Graph* graph = (struct Graph*) malloc(sizeof(struct Graph));
    graph->V = V;

    // Create an array of adjacency lists.
    graph->array = (struct AdjList*) malloc(V * sizeof(struct AdjList));

    // Initialize each adjacency list as empty by making head as NULL
    for (int i = 0; i < V; ++i)
        graph->array[i].head = NULL;

    return graph;
}

// Adds an edge to an undirected graph
void addEdge(struct Graph* graph, int src, int dest) {
    struct AdjListNode* newNode = newAdjListNode(dest);
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;
}

// A recursive function used by topologicalSort
void topologicalSortUtil(int v, struct Graph* graph, int visited[], int stack[]) {
    visited[v] = 1;  // Mark the current node as visited

    // Recur for all the vertices adjacent to this vertex
    struct AdjListNode* node = graph->array[v].head;

    while(node) {
        int adj = node->dest;
        if(!visited[adj]) {
            topologicalSortUtil(adj, graph, visited, stack);
        }
        node = node->next;
    }

    // Push current vertex to stack which stores result
    stack[v] = 1;
}

// The function to do Topological Sort. It uses recursive topologicalSortUtil()
void topologicalSort(struct Graph* graph) {
    int* stack = (int*) malloc(graph->V * sizeof(int));
    for (int i = 0; i < graph->V; i++)
        stack[i] = 0;

    int visited[graph->V];
    for (int i = 0; i < graph->V; i++)
        visited[i] = 0;

    for (int i = 0; i < graph->V; i++) {
        if (visited[i] == 0) {
            topologicalSortUtil(i, graph, visited, stack);
        }
    }

    printf("Topological Sort of the given graph: ");
    for (int i = 0; i < graph->V; i++) {
        if(stack[i]) printf("%d ", i);
    }
    printf("\n");

    free(stack);
}

// Example usage
int main() {
    int V = 6;
    struct Graph* graph = createGraph(V);
    addEdge(graph, 5, 2);
    addEdge(graph, 5, 0);
    addEdge(graph, 4, 0);
    addEdge(graph, 4, 1);
    addEdge(graph, 2, 3);
    addEdge(graph, 3, 1);

    topologicalSort(graph);

    return 0;
}

5. What are the Time and Space Complexity of Topological Sorting?

Answer: The time complexity of topological sorting using DFS is (O(V + E)), where (V) is the number of vertices and (E) the number of edges in the graph. The space complexity is also (O(V)) for the visited array and the recursion stack.

6. Difference between Topological Sort and Regular Sort?

Answer: Topological Sort arranges vertices based on the directed edges in a DAG, ensuring that for every directed edge (u \rightarrow v), vertex (u) precedes (v). Regular sorting arranges elements in a sequence (usually numerical or alphabetical) based on a comparison function, without any constraints on directionality like edges in a graph.

7. What does it mean when you have multiple valid topological sorts of a graph?

Answer: Having multiple valid topological sorts means there are different valid linear orderings of vertices that satisfy all directed edges' constraints. This occurs when there are no constraints determining the exact order of certain vertices without regard to each other.

8. Can you perform topological sorting on an undirected graph?

Answer: No, topological sorting is only defined for directed acyclic graphs (DAGs). In an undirected graph, all edges are bidirectional, so there's no natural "before" or "after" relationship needed for topological sorting.

9. How do you detect cycles in a graph during topological sorting?

Answer: Detecting cycles during topological sorting can be achieved by keeping track of visited nodes and revisiting nodes during DFS (Depth-First Search). If a node revisits a visited node in the same DFS recursion path, a cycle exists, and no topological sort is possible.

10. Can you apply topological sorting to a multigraph?

Answer: Yes, topological sorting can be applied to a multigraph, which is a graph that can have multiple edges between the same pair of vertices. However, the process remains the same as that applied to a simple directed graph (DAG) since topological sorting depends on the directed edges' direction, not their multiplicity.

You May Like This Related .NET Topic

Login to post a comment.