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
A cycle in a graph is a path that starts and ends at the same vertex, visiting other vertices and edges along the way. Detecting cycles is a fundamental problem in graph theory with numerous practical applications. For instance, in a task dependency graph, a cycle indicates an impossible set of prerequisites. In a network, a cycle might represent redundant paths. Understanding how to find these loops is crucial for validating graph properties and solving complex problems.
In an undirected graph, a cycle exists if, during a traversal (like DFS or BFS), we encounter a visited vertex that is not the direct parent of the current vertex in the traversal tree.
The most common approach for undirected graphs is a modified DFS. We keep track of visited nodes and the parent of the current node in the DFS tree.
// Conceptual C++ for Cycle Detection in Undirected Graph using DFS
bool dfs_undirected_cycle_util(int u, int parent_u,
const vector<vector<int>>& adj,
vector<bool>& visited) {
visited[u] = true;
for (int v : adj[u]) {
if (!visited[v]) { // If 'v' is not visited, recurse
if (dfs_undirected_cycle_util(v, u, adj, visited)) {
return true; // Cycle found in subtree
}
}
// If 'v' is visited AND 'v' is not the parent of 'u'
// then there is a cycle
else if (v != parent_u) {
return true;
}
}
return false; // No cycle found in this path
}
bool detect_cycle_undirected(int V, const vector<vector<int>>& adj) {
vector<bool> visited(V, false);
for (int i = 0; i < V; ++i) {
if (!visited[i]) {
if (dfs_undirected_cycle_util(i, -1, adj, visited)) {
return true; // Cycle found in this component
}
}
}
return false; // No cycle in the entire graph
}
Time Complexity: $O(V + E)$ (standard DFS traversal).
Space Complexity: $O(V)$ for the recursion stack and visited array.
In a directed graph, a cycle occurs if, during a DFS traversal, we encounter a visited vertex that is currently in the "recursion stack" (i.e., it's an ancestor of the current node in the DFS tree and has not yet finished processing all its descendants).
This approach uses two arrays (or sets) to keep track of visited nodes:
// Conceptual C++ for Cycle Detection in Directed Graph using DFS
bool dfs_directed_cycle_util(int u,
const vector<vector<int>>& adj,
vector<bool>& visited,
vector<bool>& recursionStack) {
visited[u] = true;
recursionStack[u] = true;
for (int v : adj[u]) {
if (!visited[v]) { // If 'v' is not visited, recurse
if (dfs_directed_cycle_util(v, adj, visited, recursionStack)) {
return true; // Cycle found in subtree
}
}
// If 'v' is visited AND 'v' is in recursionStack, then a cycle exists
else if (recursionStack[v]) {
return true;
}
}
recursionStack[u] = false; // Remove 'u' from recursion stack
return false; // No cycle found from this path
}
bool detect_cycle_directed(int V, const vector<vector<int>>& adj) {
vector<bool> visited(V, false);
vector<bool> recursionStack(V, false); // To detect back-edges
for (int i = 0; i < V; ++i) {
if (!visited[i]) {
if (dfs_directed_cycle_util(i, adj, visited, recursionStack)) {
return true; // Cycle found in this component
}
}
}
return false; // No cycle in the entire graph
}
Time Complexity: $O(V + E)$ (standard DFS traversal).
Space Complexity: $O(V)$ for the recursion stack and visited arrays.
As discussed in the Topological Sort chapter, Kahn's algorithm inherently detects cycles. If the number of vertices processed (added to the topological order) is less than the total number of vertices in the graph, it means a cycle exists.
// Conceptual C++ for Cycle Detection in Directed Graph using Kahn's Algorithm
bool detect_cycle_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);
}
}
int count_visited_nodes = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
count_visited_nodes++;
for (int v : adj[u]) {
in_degree[v]--;
if (in_degree[v] == 0) {
q.push(v);
}
}
}
return (count_visited_nodes != V); // If not all nodes visited, a cycle exists
}
Time Complexity: $O(V + E)$.
Space Complexity: $O(V)$.
Cycle detection is a crucial validation step in many systems, especially those dealing with dependencies:
Continuing our deep dive into graph connectivity, our next chapter will uncover critical structural elements: Bridges and Articulation Points (or Cut Vertices). These are single points of failure in a graph – edges or vertices whose removal would increase the number of connected components. Understanding them is vital for designing robust networks and identifying vulnerabilities.