This course is designed to help learners master the core concepts of Data Structures and Algorithms (DSA) using the C programming language. Starting from the basics and advancing to complex topics like graphs, dynamic programming, and memory optimization, this course is ideal for students, job seekers, and aspiring developers. You’ll learn how to structure and manipulate data efficiently, solve real-world coding problems, and prepare for technical interviews at top companies. The content is structured step-by-step, combining theory with hands-on coding examples and practice problems to reinforce understanding. Whether you're preparing for university exams, campus placements, or competitive programming, this course provides a strong foundation in logic building, code efficiency, and problem-solving using C. Key Highlights: Covers all major DSA topics from beginner to advanced level 100+ coding examples with explanations Focus on time and space complexity optimization Designed for coding interviews, competitive exams, and CS fundamentals
Imagine you have a list of tasks, and some tasks must be completed before others. How would you determine a valid order to perform all tasks? This is precisely what Topological Sort solves! It's a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge from vertex `u` to vertex `v`, `u` comes before `v` in the ordering. Crucially, a topological sort is only possible on a DAG; if the graph contains a cycle, no such linear ordering can exist.
A Directed Acyclic Graph (DAG) is a directed graph that contains no cycles. This "acyclic" property is fundamental to topological sort, as a cycle would imply an impossible dependency (e.g., Task A depends on B, B depends on C, and C depends on A – a never-ending loop).
Kahn's algorithm is an iterative, BFS-like approach that builds the topological order by repeatedly finding nodes with no incoming edges (an in-degree of zero). These are the tasks that can be started first.
// Conceptual C++ for Kahn's Algorithm
vector<int> topological_sort_kahn(int V, const vector<vector<int>>& adj) {
vector<int> in_degree(V, 0);
for (int i = 0; i < V; ++i) {
for (int neighbor : adj[i]) {
in_degree[neighbor]++;
}
}
queue<int> q;
for (int i = 0; i < V; ++i) {
if (in_degree[i] == 0) {
q.push(i);
}
}
vector<int> result;
int count = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
result.push_back(u);
count++;
for (int v : adj[u]) {
in_degree[v]--;
if (in_degree[v] == 0) {
q.push(v);
}
}
}
if (count != V) {
// Graph has a cycle, topological sort not possible
return {}; // Or throw an error
}
return result;
}
Time Complexity: $O(V + E)$ (similar to BFS, as each vertex and edge is processed constant number of times).
Space Complexity: $O(V)$ for the in-degree array and queue.
The DFS-based approach leverages the nature of Depth-First Search. When performing a DFS, a node is added to the topological sort list only after all its dependent nodes (i.e., all nodes reachable from it) have been processed and added. This is typically done by adding nodes to a stack or list *after* visiting all their children during the DFS recursion.
// Conceptual C++ for DFS-based Topological Sort
// visited: 0 = unvisited, 1 = visiting (in recursion stack), 2 = visited (finished)
bool dfs_topo_util(int u, const vector<vector<int>>& adj, vector<int>& visited, stack<int>& st) {
visited[u] = 1; // Mark as visiting (in recursion stack)
for (int v : adj[u]) {
if (visited[v] == 1) { // Cycle detected (back-edge)
return false;
}
if (visited[v] == 0) { // Not visited
if (!dfs_topo_util(v, adj, visited, st)) {
return false; // Propagate cycle detection
}
}
}
visited[u] = 2; // Mark as visited (finished processing descendants)
st.push(u); // Push to stack after all descendants are processed
return true;
}
vector<int> topological_sort_dfs(int V, const vector<vector<int>>& adj) {
vector<int> visited(V, 0); // 0: unvisited, 1: visiting, 2: visited
stack<int> st;
vector<int> result;
for (int i = 0; i < V; ++i) {
if (visited[i] == 0) {
if (!dfs_topo_util(i, adj, visited, st)) {
// Cycle detected
return {}; // Or throw error
}
}
}
while (!st.empty()) {
result.push_back(st.top());
st.pop();
}
return result;
}
Time Complexity: $O(V + E)$ (standard DFS traversal).
Space Complexity: $O(V)$ for the recursion stack and visited array.
Both algorithms can be used to detect cycles in a directed graph:
Topological Sort is an extremely practical algorithm for ordering dependent tasks. On a platform like UdaanPath, it can be directly applied in several scenarios:
Our journey through graphs continues! Having learned how to traverse and order them, the next logical step is to find optimal paths. The next chapter will introduce you to two cornerstone algorithms for finding the shortest paths in graphs: Dijkstra's Algorithm (for non-negative weights) and Bellman-Ford Algorithm (for handling negative weights).