🕸️ Graphs
Learn the language of Graphs first: vertices, edges, degree, paths, cycles, connectivity, directed and undirected Graphs, and the three standard representation methods.
📖 What is a Graph?
A Graph is a non-linear data structure made of a set of vertices and a set of edges that connect pairs of vertices. Graphs are useful when the relationships between items are as important as the items themselves.
💡 Mathematical Form
A Graph is commonly written as G = (V, E), where V is the set of vertices and E is the set of edges.
🌍 Real-Life Examples
Road networks, social networks, computer networks, flight routes, web links, course prerequisites, recommendation systems, and dependency graphs can all be modeled as Graphs.
🧠 Essential Graph Terms
📌 Important Distinction
A path describes a route through the Graph. A cycle is a route that returns to its starting point. Connectivity asks whether a route exists between vertices.
↔️ Common Types of Graphs
🧮 How Do We Store a Graph?
The three standard representations are Edge List, Adjacency Matrix, and Adjacency List. The best choice depends on how many vertices/edges the Graph has and which operations are frequent.
(u, v).
Simple and memory-efficient for listing edges.
[u][v] records whether an edge exists.
Fast edge lookup, but requires O(V²) memory.
| Representation | Space | Check Edge (u,v) | Iterate Neighbors of u | Best Fit |
|---|---|---|---|---|
| Edge List | O(E) | O(E) | O(E) without extra indexing | Simple edge storage, edge-centric algorithms |
| Adjacency Matrix | O(V²) | O(1) | O(V) | Dense Graphs, very frequent edge checks |
| Adjacency List | O(V + E) | O(deg(u)) normally | O(deg(u)) | Sparse Graphs, BFS/DFS |
🎯 What We Will Do Next
This page gives the representation overview. The next Graph parts will study Adjacency Matrix and Adjacency List individually with complete C programs, visualizers, and Program Tracing.
🧩 One Graph, Three Representations
Use the undirected Graph:
V = {0, 1, 2, 3, 4}
E = {
(0,1),
(0,2),
(1,3),
(2,3),
(3,4),
(1,4)
}
Edge List
0 1
0 2
1 3
2 3
3 4
1 4
Adjacency Matrix
0 1 2 3 4
0 : 0 1 1 0 0
1 : 1 0 0 1 1
2 : 1 0 0 1 0
3 : 0 1 1 0 1
4 : 0 1 0 1 0
Adjacency List
0 : 1 2
1 : 0 3 4
2 : 0 3
3 : 1 2 4
4 : 1 3
🎬 Graph Representation Visualizer
—
💻 Store a Graph using an Edge List
For the first Graph program, we use the simplest representation: each row stores the two endpoint vertices of one edge.
#include <stdio.h>
#define MAX_EDGES 100
int main()
{
int vertices;
int edges;
int edgeList[MAX_EDGES][2];
int i;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d",
&edgeList[i][0],
&edgeList[i][1]);
}
printf("Vertices: %d\n", vertices);
printf("Edges:\n");
for(i = 0; i < edges; i++)
{
printf("%d -> %d\n",
edgeList[i][0],
edgeList[i][1]);
}
return 0;
}
Input
5 6
0 1
0 2
1 3
2 3
3 4
1 4
Output
Vertices: 5
Edges:
0 -> 1
0 -> 2
1 -> 3
2 -> 3
3 -> 4
1 -> 4
🔎 Trace Edge List Representation
💻 Program
🧠 What is happening?
📊 Live Variables
🔗 Live Edge List
⚙️ Current Operation
—
⚡ Representation Complexity
🎯 Interview Note
Representation choice changes algorithm cost. For example, iterating all neighbors of one vertex takes O(V) with an adjacency matrix but only O(deg(v)) with an adjacency list.
🧮 Adjacency Matrix Representation
An Adjacency Matrix stores a Graph in a two-dimensional array. For V vertices, we create a V × V matrix. The row represents the starting vertex and the column represents the ending vertex.
📌 Core Meaning
For an unweighted Graph, matrix[u][v] = 1 means the edge exists and
0 means it does not. For a weighted Graph, the cell stores the edge weight.
↔️ Directed, Undirected, Weighted & Unweighted
Undirected Graph
If an edge connects u and v, store both directions:
matrix[u][v] = value;
matrix[v][u] = value;
The matrix is therefore symmetric across the main diagonal.
Directed Graph
For edge u → v, store only:
matrix[u][v] = value;
The reverse cell changes only if the reverse edge also exists.
Unweighted Graph
0 = no edge
1 = edge exists
This is the simplest adjacency-matrix form.
Weighted Graph
matrix[u][v] = weight
If zero is a valid edge weight, use another sentinel or a separate edge-existence structure instead of using zero for both meanings.
matrix[u][u], on the main diagonal.
⚙️ Insert Edge, Check Edge & Iterate Neighbors
1. Insert Edge
// directed
matrix[u][v] = value;
// undirected
matrix[u][v] = value;
matrix[v][u] = value;
2. Check Edge
if(matrix[u][v] != 0)
// edge exists
3. Visit Neighbors of u
for(v = 0; v < vertices; v++)
{
if(matrix[u][v] != 0)
// v is adjacent to u
}
🎬 Adjacency Matrix Visualizer
💻 Adjacency Matrix in C
This program supports directed/undirected and weighted/unweighted construction. The sample demonstrates an undirected weighted Graph.
#include <stdio.h>
#define MAX 10
int main()
{
int matrix[MAX][MAX] = {0};
int vertices;
int edges;
int directed;
int weighted;
int i;
int j;
int u;
int v;
int w;
int value;
scanf("%d %d %d %d",
&vertices,
&edges,
&directed,
&weighted);
for(i = 0; i < edges; i++)
{
scanf("%d %d %d", &u, &v, &w);
value = weighted ? w : 1;
matrix[u][v] = value;
if(!directed)
matrix[v][u] = value;
}
printf("Adjacency Matrix:\n");
for(i = 0; i < vertices; i++)
{
for(j = 0; j < vertices; j++)
printf("%d ", matrix[i][j]);
printf("\n");
}
u = 1;
v = 4;
if(matrix[u][v] != 0)
printf("Edge %d -> %d exists with value %d\n",
u, v, matrix[u][v]);
else
printf("Edge %d -> %d does not exist\n", u, v);
return 0;
}
Input
5 6 0 1
0 1 4
0 2 2
1 3 5
2 3 1
3 4 3
1 4 6
Output
Adjacency Matrix:
0 4 2 0 0
4 0 0 5 6
2 0 0 1 0
0 5 1 0 3
0 6 0 3 0
Edge 1 -> 4 exists with value 6
🔎 Trace Adjacency Matrix Construction
💻 Program
🧠 What is happening?
📊 Live Variables
🧮 Live Adjacency Matrix
⚙️ Current Operation
—
⚡ Adjacency Matrix Complexity
🎯 Interview Note
An adjacency matrix trades memory for constant-time edge lookup. It is attractive for dense Graphs, but sparse Graphs may leave most of the V² cells unused.
🔗 Adjacency List Representation
An Adjacency List stores, for every vertex, only the neighbors that are actually connected to it. This makes it especially efficient for sparse Graphs, where the number of edges is much smaller than V².
📌 Core Meaning
For vertex u, adj[u] points to a list of vertices directly reachable from u.
In a weighted Graph, each list node can also store the edge weight.
🧱 How an Adjacency List is Stored
In C, a common implementation uses an array of linked-list head pointers. Each linked-list node stores the neighbor vertex, optional edge weight, and a pointer to the next neighbor.
struct Node
{
int vertex;
int weight;
struct Node *next;
};
struct Node *adj[MAX] = {NULL};
↔️ Adjacency List Rules
Directed Graph
For edge u → v, insert v only into u's list:
addEdge(adj, u, v, value);
Undirected Graph
For edge u — v, insert both directions:
addEdge(adj, u, v, value);
addEdge(adj, v, u, value);
🎯 Weighted Lists
Instead of storing only the neighbor vertex, store a pair such as (neighbor, weight).
That is the representation used in this Part 3 program and visualizer.
⚙️ Insert Edge, Check Edge & Visit Neighbors
1. Insert at Head
newNode->next = adj[u];
adj[u] = newNode;
2. Check Whether Edge u → v Exists
current = adj[u];
while(current != NULL)
{
if(current->vertex == v)
// edge exists
current = current->next;
}
3. Iterate All Neighbors of u
current = adj[u];
while(current != NULL)
{
// process current->vertex
current = current->next;
}
🎬 Adjacency List Visualizer
💻 Weighted Adjacency List in C
This implementation uses dynamic memory and linked lists. The sample uses an undirected weighted Graph and inserts new neighbor nodes at the head.
#include <stdio.h>
#include <stdlib.h>
#define MAX 10
struct Node
{
int vertex;
int weight;
struct Node *next;
};
struct Node *createNode(int vertex, int weight)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->vertex = vertex;
newNode->weight = weight;
newNode->next = NULL;
return newNode;
}
void addEdge(struct Node *adj[],
int u,
int v,
int weight)
{
struct Node *newNode =
createNode(v, weight);
newNode->next = adj[u];
adj[u] = newNode;
}
void printGraph(struct Node *adj[], int vertices)
{
int i;
struct Node *current;
for(i = 0; i < vertices; i++)
{
printf("%d:", i);
current = adj[i];
while(current != NULL)
{
printf(" (%d,%d)",
current->vertex,
current->weight);
current = current->next;
}
printf("\n");
}
}
int main()
{
struct Node *adj[MAX] = {NULL};
int vertices;
int edges;
int directed;
int weighted;
int i;
int u;
int v;
int w;
int value;
scanf("%d %d %d %d",
&vertices,
&edges,
&directed,
&weighted);
for(i = 0; i < edges; i++)
{
scanf("%d %d %d", &u, &v, &w);
value = weighted ? w : 1;
addEdge(adj, u, v, value);
if(!directed)
addEdge(adj, v, u, value);
}
printf("Adjacency List:\n");
printGraph(adj, vertices);
return 0;
}
Input
5 6 0 1
0 1 4
0 2 2
1 3 5
2 3 1
3 4 3
1 4 6
Output
Adjacency List:
0: (2,2) (1,4)
1: (4,6) (3,5) (0,4)
2: (3,1) (0,2)
3: (4,3) (2,1) (1,5)
4: (1,6) (3,3)
🔎 Trace Adjacency List Construction
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ Adjacency List Complexity
🎯 Interview Note
For sparse Graphs, adjacency lists are usually more space-efficient than matrices and are the standard choice for BFS and DFS, because a full traversal can process all vertices and edges in O(V + E).
🌊 Breadth-First Search (BFS)
Breadth-First Search explores a Graph level by level. Starting from one vertex, it first visits all immediately reachable neighbors, then their unvisited neighbors, and continues outward.
visited[] array prevents the same vertex from being enqueued again.
📌 BFS from One Start Vertex
BFS visits every vertex that is reachable from the chosen start vertex. If the Graph is disconnected, one BFS call does not automatically visit the other components.
🚶 Why BFS Uses a Queue
When a vertex is discovered, it is added at the rear of the Queue. The vertex that has been waiting the longest is removed from the front. This FIFO behavior creates the level-by-level traversal order.
🧠 BFS Algorithm
BFS(adj, start)
1. Mark every vertex unvisited.
2. Mark start as visited.
3. Enqueue start.
4. While Queue is not empty:
current = Dequeue()
Visit current
For each neighbor of current:
If neighbor is not visited:
Mark neighbor visited
Enqueue neighbor
🎯 Key Invariant
Every vertex enters the Queue at most once because it is marked visited immediately when discovered.
🧩 BFS from Vertex 0
Use the same undirected Graph from the representation lessons:
Edges:
0 - 1
0 - 2
1 - 3
2 - 3
3 - 4
1 - 4
Adjacency Lists
0 : 1 2
1 : 0 3 4
2 : 0 3
3 : 1 2 4
4 : 3 1
Traversal
Start at vertex 0.
🎬 BFS Visualizer
💻 BFS using Queue in C
This program stores the undirected Graph as adjacency lists inside arrays:
adj[u][0 ... degree[u]-1]. BFS then uses visited[] and an array-based Queue.
#include <stdio.h>
#define MAX 10
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int visited[MAX] = {0};
int queue[MAX];
int vertices;
int edges;
int start;
int front = 0;
int rear = 0;
int i;
int u;
int v;
int current;
int neighbor;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
adj[v][degree[v]] = u;
degree[v]++;
}
scanf("%d", &start);
visited[start] = 1;
queue[rear] = start;
rear++;
printf("BFS: ");
while(front < rear)
{
current = queue[front];
front++;
printf("%d ", current);
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(!visited[neighbor])
{
visited[neighbor] = 1;
queue[rear] = neighbor;
rear++;
}
}
}
printf("\n");
return 0;
}
Input
5 6
0 1
0 2
1 3
2 3
3 4
1 4
0
Output
BFS: 0 1 2 3 4
🔎 Trace BFS with Queue + visited[]
💻 Program
🧠 What is happening?
📊 Live Variables
🚶 Live Queue
✅ visited[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ BFS Complexity
🎯 Interview Note
If the same BFS is implemented over an adjacency matrix, scanning a row for every visited vertex can make the traversal O(V²). The O(V + E) bound assumes an adjacency-list-style representation.
🌍 Where BFS is Used
🧭 Depth-First Search (DFS)
Depth-First Search explores one path as deeply as possible before returning to the most recent vertex that still has an unvisited neighbor.
visited[] array prevents infinite recursion around cycles.
📌 DFS from One Start Vertex
Like BFS, one DFS call visits only vertices reachable from the chosen start vertex. For a disconnected Graph, start DFS again from each still-unvisited vertex.
📚 Why Recursive DFS Uses the Call Stack
When DFS moves from a vertex to one unvisited neighbor, the current function call is not discarded. It waits on the Call Stack while the recursive call explores deeper vertices. When the deeper call finishes, execution returns to the waiting call and continues with its next neighbor.
🧠 Recursive DFS Algorithm
DFS(adj, current)
1. Mark current as visited.
2. Visit current.
3. For each neighbor of current:
If neighbor is not visited:
DFS(adj, neighbor)
🎯 Backtracking Point
When the current vertex has no remaining unvisited neighbors, its DFS call returns. That return is the backtracking step.
🧩 DFS from Vertex 0
Use the same undirected Graph:
Edges:
0 - 1
0 - 2
1 - 3
2 - 3
3 - 4
1 - 4
Adjacency Lists
0 : 1 2
1 : 0 3 4
2 : 0 3
3 : 1 2 4
4 : 3 1
Recursive Traversal
Starting from 0 and following neighbors in stored order:
🎬 DFS Visualizer
💻 Recursive DFS in C
This program stores the Graph using adjacency-list arrays and performs recursive DFS.
Each recursive call has its own local i, neighbor, and current.
#include <stdio.h>
#define MAX 10
void dfs(int adj[][MAX],
int degree[],
int visited[],
int current)
{
int i;
int neighbor;
visited[current] = 1;
printf("%d ", current);
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(!visited[neighbor])
dfs(adj,
degree,
visited,
neighbor);
}
}
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int visited[MAX] = {0};
int vertices;
int edges;
int start;
int i;
int u;
int v;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
adj[v][degree[v]] = u;
degree[v]++;
}
scanf("%d", &start);
printf("DFS: ");
dfs(adj, degree, visited, start);
printf("\n");
return 0;
}
Input
5 6
0 1
0 2
1 3
2 3
3 4
1 4
0
Output
DFS: 0 1 3 2 4
🔎 Trace Recursive DFS
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Recursion Call Stack
✅ visited[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ DFS Complexity
🎯 Interview Note
For adjacency-list DFS, the standard time complexity is O(V + E). With an adjacency matrix, checking all possible neighbors for every visited vertex can make traversal O(V²).
🌍 Where DFS is Used
🔁 Detecting Cycles in Graphs
A cycle exists when a traversal can leave a vertex, follow Graph edges, and eventually return to a previously active part of the traversal without simply reversing the same undirected parent edge.
📌 Why the Rules Differ
In an undirected Graph, each edge is stored in both directions. In a directed Graph, edge direction matters, so reaching a vertex in the current recursive path is the key cycle signal.
↔️ Cycle Detection using Parent Tracking
During DFS, suppose we are at vertex current and inspect neighbor.
If the neighbor is unvisited, recurse normally.
If it is already visited, it forms a cycle only when it is not the parent of the current vertex.
No Cycle Signal
neighbor == parent
This is the normal reverse copy of the undirected edge used to reach the current vertex.
Cycle Signal
visited[neighbor] == 1
AND
neighbor != parent
The traversal reached a previously visited vertex through a different undirected connection.
➡️ Cycle Detection using a Recursion Stack
For a directed Graph, a normal visited[] array is not enough.
A visited vertex may belong to an already completed DFS branch and therefore may not create a cycle.
We additionally maintain recStack[].
Enter Vertex
visited[current] = 1;
recStack[current] = 1;
The vertex is both discovered and active on the current DFS path.
Leave Vertex
recStack[current] = 0;
When all outgoing neighbors are finished, remove the vertex from the active recursive path.
recStack[] value is still 1.
That edge points back into the current DFS path.
🧠 Cycle Detection Algorithms
Undirected DFS
DFS(current, parent)
mark current visited
for each neighbor:
if neighbor is unvisited:
if DFS(neighbor, current)
return cycle
else if neighbor != parent:
return cycle
return no cycle
Directed DFS
DFS(current)
visited[current] = 1
recStack[current] = 1
for each neighbor:
if neighbor is unvisited:
if DFS(neighbor)
return cycle
else if recStack[neighbor] == 1:
return cycle
recStack[current] = 0
return no cycle
🧩 Understanding the Two Cycle Signals
Undirected Example
0 - 1
| /
| /
2 - 3 - 4
Following 0 → 1 → 2, vertex 2 can see visited vertex 0, and 0 is not 2's parent. Therefore a cycle exists.
Directed Example
0 → 1 → 2
↑ |
|_______|
While DFS(2) is active, edge 2 → 0 reaches vertex 0, which is still in the current recursion stack.
🎬 Cycle Detection Visualizer
💻 Undirected Cycle Detection using DFS + Parent
The program checks every component and passes the parent vertex into each recursive DFS call.
#include <stdio.h>
#define MAX 10
int hasCycle(int adj[][MAX],
int degree[],
int visited[],
int current,
int parent)
{
int i;
int neighbor;
visited[current] = 1;
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(!visited[neighbor])
{
if(hasCycle(adj,
degree,
visited,
neighbor,
current))
return 1;
}
else if(neighbor != parent)
{
return 1;
}
}
return 0;
}
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int visited[MAX] = {0};
int vertices;
int edges;
int i;
int u;
int v;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
adj[v][degree[v]] = u;
degree[v]++;
}
for(i = 0; i < vertices; i++)
{
if(!visited[i])
{
if(hasCycle(adj,
degree,
visited,
i,
-1))
{
printf("Cycle Found\n");
return 0;
}
}
}
printf("No Cycle\n");
return 0;
}
Input
5 5
0 1
1 2
2 0
2 3
3 4
Output
Cycle Found
🔎 Trace Undirected Cycle Detection
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Recursion Call Stack
✅ visited[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
💻 Directed Cycle Detection using DFS + recStack[]
The program marks each active recursive vertex in recStack[].
Reaching another active vertex means a directed cycle exists.
#include <stdio.h>
#define MAX 10
int hasDirectedCycle(int adj[][MAX],
int degree[],
int visited[],
int recStack[],
int current)
{
int i;
int neighbor;
visited[current] = 1;
recStack[current] = 1;
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(!visited[neighbor])
{
if(hasDirectedCycle(adj,
degree,
visited,
recStack,
neighbor))
return 1;
}
else if(recStack[neighbor])
{
return 1;
}
}
recStack[current] = 0;
return 0;
}
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int visited[MAX] = {0};
int recStack[MAX] = {0};
int vertices;
int edges;
int i;
int u;
int v;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
}
for(i = 0; i < vertices; i++)
{
if(!visited[i])
{
if(hasDirectedCycle(adj,
degree,
visited,
recStack,
i))
{
printf("Cycle Found\n");
return 0;
}
}
}
printf("No Cycle\n");
return 0;
}
Input
5 5
0 1
1 2
2 0
2 3
3 4
Output
Cycle Found
🔎 Trace Directed Cycle Detection
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Recursion Call Stack
✅ visited[]
🔄 recStack[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ Cycle Detection Complexity
🎯 Interview Note
The two conditions are easy to confuse: Undirected: visited neighbor ≠ parent. Directed: visited neighbor must still be in the current recursion stack.
📋 Topological Ordering of a Directed Graph
A Topological Sort is a linear ordering of the vertices of a directed Graph
such that for every directed edge u → v, vertex u appears before vertex v.
📌 Example Interpretation
In course scheduling, an edge Course A → Course B can mean A is a prerequisite for B.
A topological order gives one valid sequence for completing all courses.
🚫 Why Cycles Make Topological Ordering Impossible
Suppose a directed cycle contains:
A → B → C → A
The dependencies demand A before B, B before C, and C before A. Those three requirements cannot all be true in one linear order. Therefore a directed Graph with a cycle has no valid topological ordering.
🚶 Topological Sort using indegree[] + Queue
Kahn's Algorithm repeatedly processes vertices with no remaining prerequisites.
The indegree[] array stores how many incoming edges each vertex currently has.
1. Compute indegree of every vertex.
2. Enqueue every vertex whose indegree is 0.
3. While Queue is not empty:
current = Dequeue()
append current to topological order
for each outgoing neighbor:
indegree[neighbor]--
if indegree[neighbor] becomes 0:
Enqueue neighbor
4. If processed vertex count != V:
cycle exists
else:
topological order is valid
🎯 Main Idea
Removing a vertex conceptually removes its outgoing edges. Each removed edge decreases the remaining prerequisite count of its destination.
📚 Topological Sort using DFS Finishing Order
DFS Method
DFS(u)
mark u visited
for each unvisited neighbor v:
DFS(v)
push u onto Stack
A vertex is pushed only after all vertices reachable from it have been processed. Popping the Stack gives a topological order for a DAG.
Cycle Awareness
For a general directed Graph, DFS-based topological sorting should also detect directed cycles,
for example using recursion-state coloring or recStack[].
If a cycle is present, the produced finishing order is not a valid topological ordering.
🧩 Kahn's Algorithm Step by Step
Vertices: 0, 1, 2, 3, 4, 5
Edges:
5 → 2
5 → 0
4 → 0
4 → 1
2 → 3
3 → 1
| Vertex | Initial indegree |
|---|---|
| 0 | 2 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
| 4 | 0 |
| 5 | 0 |
The initial Queue is [4, 5]. Processing those vertices gradually reduces other indegrees to zero.
🎬 Topological Sort Visualizer
💻 Kahn's Topological Sort in C
This program builds directed adjacency lists in arrays, computes indegree[],
uses an array-based Queue, stores the output in order[], and checks whether every vertex was processed.
#include <stdio.h>
#define MAX 10
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int indegree[MAX] = {0};
int queue[MAX];
int order[MAX];
int vertices;
int edges;
int front = 0;
int rear = 0;
int count = 0;
int i;
int u;
int v;
int current;
int neighbor;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
indegree[v]++;
}
for(i = 0; i < vertices; i++)
{
if(indegree[i] == 0)
{
queue[rear] = i;
rear++;
}
}
while(front < rear)
{
current = queue[front];
front++;
order[count] = current;
count++;
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
indegree[neighbor]--;
if(indegree[neighbor] == 0)
{
queue[rear] = neighbor;
rear++;
}
}
}
if(count != vertices)
{
printf("Topological ordering not possible\n");
}
else
{
printf("Topological Order: ");
for(i = 0; i < count; i++)
printf("%d ", order[i]);
printf("\n");
}
return 0;
}
Input
6 6
5 2
5 0
4 0
4 1
2 3
3 1
Output
Topological Order: 4 5 2 0 3 1
If the Graph Contains a Directed Cycle
Input:
3 3
0 1
1 2
2 0
Output:
Topological ordering not possible
🔎 Trace Kahn's Algorithm
💻 Program
🧠 What is happening?
📊 Live Variables
🚶 Queue
⬅️ indegree[]
📋 order[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ Topological Sort Complexity
🎯 Interview Note
Topological Sort is defined for a DAG. If a cycle exists, there is no linear ordering that can place every source of a directed edge before its destination.
🧩 Connected Components in an Undirected Graph
A connected component is a maximal group of vertices in an undirected Graph where every vertex in that group is reachable from every other vertex in the same group.
📌 Maximal Means
A component cannot be enlarged by adding another vertex from the Graph while keeping all vertices mutually reachable.
🔗 Connected and Disconnected Graphs
Connected Graph
A Graph is connected when one DFS/BFS from any vertex can reach every vertex.
Number of connected components = 1
Disconnected Graph
A Graph is disconnected when at least one pair of vertices has no path between them.
Number of connected components > 1
📚 Finding Components using DFS
components = 0
for every vertex v:
if v is unvisited:
components++
DFS(v)
during DFS:
mark each reached vertex visited
componentId[vertex] = components
The outer loop is what makes the algorithm work for disconnected Graphs. DFS completely explores one component before the loop searches for the next unvisited vertex.
🚶 Finding Components using BFS
BFS can be used instead of DFS without changing the final grouping. The difference is only the traversal order inside each component.
for each unvisited vertex v:
components++
mark v visited
enqueue v
while Queue is not empty:
current = dequeue
componentId[current] = components
for each neighbor:
if neighbor is unvisited:
mark visited
enqueue neighbor
🧠 Counting Components
Vertices: 0 1 2 3 4 5 6 7
Edges:
0 - 1
1 - 2
3 - 4
5 - 6
6 - 7
The Graph therefore contains 3 connected components.
🎬 Connected Components Visualizer
💻 Connected Components using DFS in C
This program counts connected components and assigns a componentId[] to every vertex.
#include <stdio.h>
#define MAX 10
void dfsComponent(int adj[][MAX],
int degree[],
int visited[],
int componentId[],
int current,
int component)
{
int i;
int neighbor;
visited[current] = 1;
componentId[current] = component;
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(!visited[neighbor])
dfsComponent(adj,
degree,
visited,
componentId,
neighbor,
component);
}
}
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int visited[MAX] = {0};
int componentId[MAX] = {0};
int vertices;
int edges;
int components = 0;
int i;
int u;
int v;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
adj[v][degree[v]] = u;
degree[v]++;
}
for(i = 0; i < vertices; i++)
{
if(!visited[i])
{
components++;
dfsComponent(adj,
degree,
visited,
componentId,
i,
components);
}
}
printf("Components: %d\n", components);
for(i = 0; i < vertices; i++)
printf("Vertex %d -> Component %d\n",
i,
componentId[i]);
return 0;
}
Input
8 5
0 1
1 2
3 4
5 6
6 7
Output
Components: 3
Vertex 0 -> Component 1
Vertex 1 -> Component 1
Vertex 2 -> Component 1
Vertex 3 -> Component 2
Vertex 4 -> Component 2
Vertex 5 -> Component 3
Vertex 6 -> Component 3
Vertex 7 -> Component 3
🔎 Trace Connected Components using DFS
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Recursion Call Stack
✅ visited[]
🧩 componentId[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ Connected Components Complexity
🎯 Interview Note
The standard connected-components problem applies to undirected Graphs. For directed Graphs, the stronger concepts are strongly connected components and weakly connected components, which are treated separately in advanced Graph algorithms.
🛣️ Shortest Path in an Unweighted Graph
In an unweighted Graph, the shortest path from a source to a target is the path that uses the minimum number of edges. Because every edge has equal cost, Breadth-First Search can find these minimum edge distances.
parent[].
📌 Important
Ordinary BFS is correct for unweighted Graphs, or for Graphs where every edge has the same cost. It is not the general solution for arbitrary positive edge weights.
🌊 Why BFS Gives the Minimum Edge Distance
BFS processes vertices in layers:
Level 0 : source
Level 1 : vertices one edge away
Level 2 : vertices two edges away
Level 3 : vertices three edges away
...
Therefore, the first time BFS discovers a vertex, it has reached that vertex using the smallest possible number of edges.
📊 Using distance[] and parent[]
distance[]
distance[start] = 0
distance[neighbor]
= distance[current] + 1
The first assigned value is the shortest number of edges from the source.
parent[]
parent[neighbor] = current
This remembers the previous vertex on one shortest path from the source.
To reconstruct the path, start at the target and repeatedly follow parent[] until -1:
Because reconstruction runs backward, reverse the collected sequence before printing it.
🧠 Unweighted Shortest Path Algorithm
Initialize:
visited[] = 0
distance[] = -1
parent[] = -1
visited[start] = 1
distance[start] = 0
enqueue start
while Queue is not empty:
current = dequeue
for each neighbor of current:
if neighbor is unvisited:
visited[neighbor] = 1
distance[neighbor]
= distance[current] + 1
parent[neighbor] = current
enqueue neighbor
If distance[target] == -1:
target is unreachable
Else:
follow parent[] backward
to reconstruct the path
🧩 Shortest Path from 0 to 5
Edges:
0 - 1
0 - 2
1 - 3
2 - 3
3 - 4
4 - 5
2 - 6
Starting from vertex 0, BFS discovers the vertices by increasing edge distance. For target 5:
distance[5] = 4
parent[5] = 4
parent[4] = 3
parent[3] = 1
parent[1] = 0
parent[0] = -1
🎬 Unweighted Shortest Path Visualizer
💻 Shortest Path in an Unweighted Graph using BFS
This program computes minimum edge distance, records parent[],
and reconstructs one shortest path from start to target.
#include <stdio.h>
#define MAX 10
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int visited[MAX] = {0};
int distance[MAX];
int parent[MAX];
int queue[MAX];
int path[MAX];
int vertices;
int edges;
int start;
int target;
int front = 0;
int rear = 0;
int pathLength = 0;
int i;
int u;
int v;
int current;
int neighbor;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < vertices; i++)
{
distance[i] = -1;
parent[i] = -1;
}
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
adj[v][degree[v]] = u;
degree[v]++;
}
scanf("%d %d", &start, &target);
visited[start] = 1;
distance[start] = 0;
queue[rear] = start;
rear++;
while(front < rear)
{
current = queue[front];
front++;
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(!visited[neighbor])
{
visited[neighbor] = 1;
distance[neighbor] = distance[current] + 1;
parent[neighbor] = current;
queue[rear] = neighbor;
rear++;
}
}
}
if(distance[target] == -1)
{
printf("Target unreachable\n");
}
else
{
current = target;
while(current != -1)
{
path[pathLength] = current;
pathLength++;
current = parent[current];
}
printf("Distance: %d\n", distance[target]);
printf("Path: ");
for(i = pathLength - 1; i >= 0; i--)
printf("%d ", path[i]);
printf("\n");
}
return 0;
}
Input
7 7
0 1
0 2
1 3
2 3
3 4
4 5
2 6
0 5
Output
Distance: 4
Path: 0 1 3 4 5
Unreachable Target
If distance[target] remains -1:
Target unreachable
🔎 Trace BFS Shortest Path
💻 Program
🧠 What is happening?
📊 Live Variables
🚶 Queue
📏 distance[]
👣 parent[]
🧭 path[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ Unweighted Shortest Path Complexity
🎯 Interview Note
BFS gives shortest paths only when all edges have equal cost. For weighted shortest paths, algorithms such as Dijkstra or Bellman-Ford are used depending on edge-weight conditions.
🎨 What is a Bipartite Graph?
A Graph is bipartite if its vertices can be divided into two disjoint sets so that every edge connects a vertex from one set to a vertex from the other set. No edge may connect two vertices inside the same set.
📌 Common Applications
Bipartite Graphs model relationships between two different types of objects, such as students and courses, applicants and jobs, or users and products.
🟦🟨 Bipartite Graph = Valid 2-Coloring
Use two colors, represented here as 0 and 1.
An uncolored vertex stores -1.
color[v] = -1 → not colored yet
color[v] = 0 → Set A
color[v] = 1 → Set B
Whenever an uncolored neighbor is discovered:
color[neighbor] = 1 - color[current]
If an already colored neighbor has the same color as the current vertex, the Graph cannot be bipartite.
🚶 Bipartite Check using BFS
Initialize color[] = -1
for every vertex start:
if start is already colored:
continue
color[start] = 0
enqueue start
while Queue is not empty:
current = dequeue
for each neighbor:
if neighbor is uncolored:
color[neighbor]
= 1 - color[current]
enqueue neighbor
else if color[neighbor]
== color[current]:
Graph is not bipartite
📚 Bipartite Check using DFS
Recursive Rule
DFS(current)
for each neighbor:
if neighbor is uncolored:
assign opposite color
DFS(neighbor)
else if same color:
conflict
BFS vs DFS
Both methods apply the same coloring rule. BFS uses a Queue and naturally explores level by level. DFS uses recursion or an explicit Stack and explores deeply before backtracking.
🔺 Why an Odd-Length Cycle is Not Bipartite
Along a cycle, colors must alternate:
A → B → A → B → ...
For an even cycle, this alternating pattern returns consistently to the starting vertex. For an odd cycle, the final edge forces two same-colored vertices to become adjacent.
Triangle:
0 - 1
\ /
2
Try:
0 = A
1 = B
2 must be A because of edge 1-2
but edge 2-0 connects A to A → conflict
🎯 Fundamental Result
An undirected Graph is bipartite if and only if it contains no odd-length cycle.
🧩 Two-Coloring a Bipartite Graph
Edges:
0 - 1
1 - 2
2 - 3
3 - 0
3 - 4
4 - 5
5 - 2
Every listed edge crosses between Set A and Set B, so the Graph is bipartite.
🎬 Bipartite Graph Visualizer
💻 Bipartite Graph Check using BFS in C
This program checks every connected component.
The value -1 means uncolored, while colors 0 and 1 represent Set A and Set B.
#include <stdio.h>
#define MAX 10
int main()
{
int adj[MAX][MAX];
int degree[MAX] = {0};
int color[MAX];
int queue[MAX];
int vertices;
int edges;
int start;
int front;
int rear;
int i;
int u;
int v;
int current;
int neighbor;
scanf("%d %d", &vertices, &edges);
for(i = 0; i < vertices; i++)
color[i] = -1;
for(i = 0; i < edges; i++)
{
scanf("%d %d", &u, &v);
adj[u][degree[u]] = v;
degree[u]++;
adj[v][degree[v]] = u;
degree[v]++;
}
for(start = 0; start < vertices; start++)
{
if(color[start] != -1)
continue;
front = 0;
rear = 0;
color[start] = 0;
queue[rear] = start;
rear++;
while(front < rear)
{
current = queue[front];
front++;
for(i = 0; i < degree[current]; i++)
{
neighbor = adj[current][i];
if(color[neighbor] == -1)
{
color[neighbor] = 1 - color[current];
queue[rear] = neighbor;
rear++;
}
else if(color[neighbor] == color[current])
{
printf("Graph is Not Bipartite\n");
return 0;
}
}
}
}
printf("Graph is Bipartite\n");
for(i = 0; i < vertices; i++)
printf("Vertex %d -> Set %c\n",
i,
color[i] == 0 ? 'A' : 'B');
return 0;
}
Input
6 7
0 1
1 2
2 3
3 0
3 4
4 5
5 2
Output
Graph is Bipartite
Vertex 0 -> Set A
Vertex 1 -> Set B
Vertex 2 -> Set A
Vertex 3 -> Set B
Vertex 4 -> Set A
Vertex 5 -> Set B
Odd-Cycle Input
3 3
0 1
1 2
2 0
Output:
Graph is Not Bipartite
🔎 Trace Bipartite Checking using BFS
💻 Program
🧠 What is happening?
📊 Live Variables
🚶 Queue
🎨 color[]
🔗 Live Adjacency Lists
⚙️ Current Operation
—
⚡ Bipartite Checking Complexity
🎯 Interview Note
For an undirected Graph, these statements are equivalent: the Graph is bipartite, the Graph is 2-colorable, and the Graph contains no odd-length cycle.
📊 Graph Representations & Core Algorithms — Comparison
| Representation | Space | Check Edge u→v | Visit Neighbors of u | Best Use |
|---|---|---|---|---|
| Edge List | O(E) | O(E) | O(E) without an index | Simple edge storage, edge-centric processing |
| Adjacency Matrix | O(V²) | O(1) | O(V) | Dense Graphs, constant-time edge lookup |
| Adjacency List | O(V + E) | O(deg(u)) normally | O(deg(u)) | Sparse Graphs, BFS/DFS, most traversal algorithms |
| Algorithm | Main Structure | Main Purpose | Typical Time | Key State |
|---|---|---|---|---|
| BFS | Queue | Level-order exploration; shortest path in equal-cost Graphs | O(V + E) | visited[], Queue |
| DFS | Call Stack / Stack | Deep exploration, backtracking, structural analysis | O(V + E) | visited[], recursion Stack |
| Undirected Cycle Detection | DFS + parent | Detect a visited neighbor that is not the parent | O(V + E) | visited[], parent |
| Directed Cycle Detection | DFS + recursion state | Detect an edge back to the active DFS path | O(V + E) | visited[], recStack[] |
| Topological Sort | Queue + indegree[] / DFS Stack | Order DAG dependencies | O(V + E) | indegree[] or finishing order |
| Connected Components | DFS / BFS | Group mutually reachable vertices in an undirected Graph | O(V + E) | visited[], componentId[] |
| Unweighted Shortest Path | BFS | Minimum number of edges from a source | O(V + E) | distance[], parent[] |
| Bipartite Check | BFS / DFS | Determine whether a valid 2-coloring exists | O(V + E) | color[] |
🧭 Which Graph Algorithm Should I Choose?
⚡ Core Graph Complexity Summary
| Task | Adjacency List | Adjacency Matrix | Auxiliary Space |
|---|---|---|---|
| BFS | O(V + E) | O(V²) | O(V) |
| DFS | O(V + E) | O(V²) | O(V) |
| Connected Components | O(V + E) | O(V²) | O(V) |
| Cycle Detection | O(V + E) | O(V²) | O(V) |
| Topological Sort | O(V + E) | O(V²) if matrix-scanning | O(V) |
| Unweighted Shortest Path | O(V + E) | O(V²) | O(V) |
| Bipartite Check | O(V + E) | O(V²) | O(V) |
🎯 Placement Shortcut
For most sparse Graph interview problems, adjacency lists are the default choice because they keep traversal at O(V + E). Always identify whether the Graph is directed/undirected, weighted/unweighted, connected/disconnected, and cyclic/acyclic before selecting an algorithm.
❓ Graph Interview Questions — Parts 1–10
🎯 20 Graph Practice Problems
Try every problem yourself first. Use Solve It Yourself to write and test your C program. Open Hint only when necessary, and use Show Program when you want to study the complete official solution.
🏆 Scoring
Each problem contains 5 tests × 20 points = 100 points. Opening a Hint caps the competitive score for that problem at 90. After opening Show Program, you can still complete the problem, but it is recorded as Completed rather than a new competitive solve.
🧠 Practice Coverage
The 20 problems cover BFS, DFS, connected components, connectivity, cycle detection, topological sorting, bipartite checking, unweighted shortest paths, Graph degrees, adjacency matrix/list construction, reachability, distance levels, isolated vertices, edge counting, and checking whether an undirected Graph is a tree.