CODEBHAVYA • DATA STRUCTURES

🕸️ 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

Vertex / Node An individual object in the Graph. Example: a city, user, computer, or course.
Edge A connection between two vertices.
Adjacent Vertices Two vertices are adjacent when an edge directly connects them.
Degree In an undirected Graph, the degree of a vertex is the number of incident edges.
Indegree In a directed Graph, the number of edges entering a vertex.
Outdegree In a directed Graph, the number of edges leaving a vertex.
Path A sequence of vertices connected by edges.
Cycle A path that returns to its starting vertex without repeating edges in the cycle.
Connected Component A maximal group of vertices that can reach one another in an undirected Graph.

📌 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

Undirected Graph An edge {u, v} has no direction. The connection works both ways.
Directed Graph An edge (u, v) points from u to v. Direction matters.
Weighted Graph Each edge carries a value such as distance, cost, time, or capacity.
Unweighted Graph Edges express only whether a connection exists.
Connected / Disconnected A connected undirected Graph has a path between every pair of vertices.
Cyclic / Acyclic A cyclic Graph contains at least one cycle; an acyclic Graph contains none.

🧮 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.

1. Edge List Store each edge as a pair such as (u, v). Simple and memory-efficient for listing edges.
2. Adjacency Matrix Use a V × V table. Cell [u][v] records whether an edge exists. Fast edge lookup, but requires O(V²) memory.
3. Adjacency List For every vertex, store a list of its neighboring vertices. Excellent for sparse Graphs and traversal algorithms.
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

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Graph Representation Visualizer Build edges one by one and switch between Edge List, Adjacency Matrix, and Adjacency List views.
Graph
Edge List
Press Next to add the first edge.

💻 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

⚡ Representation Complexity

Edge List — O(E) space Stores one record per edge.
Adjacency Matrix — O(V²) space Fast O(1) edge lookup, even when many cells are zero.
Adjacency List — O(V + E) space Usually the preferred representation for sparse Graph traversal.

🎯 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.

Matrix Size For V vertices, the representation always contains V² cells.
Fast Edge Check To test whether edge (u,v) exists, directly inspect matrix[u][v].
Dense Graph Friendly The matrix is especially useful when many possible edges actually exist.

📌 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.

Self-loop: An edge from vertex u back to itself is stored at 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
}
Trade-off: edge lookup is O(1), but iterating all neighbors of one vertex requires scanning its full row, so it takes O(V).

🎬 Adjacency Matrix Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Adjacency Matrix Visualizer Watch each edge update its exact matrix cell, then perform an O(1) edge check.
Graph
Adjacency Matrix
Current Edge
Cell
Value
Operation
Result
Details: Press Next to begin.

💻 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

⚡ Adjacency Matrix Complexity

Space — O(V²)Every possible vertex pair has a matrix cell.
Insert Edge — O(1)Directly write matrix[u][v]; mirror once for an undirected Graph.
Check Edge — O(1)Directly read matrix[u][v].
Iterate Neighbors — O(V)Scan the full row for vertex u.

🎯 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².

One List per Vertex Each vertex has a head pointer or container for its neighboring vertices.
Space Efficient The standard representation uses O(V + E) space.
Traversal Friendly BFS and DFS can iterate only the neighbors that actually exist.

📌 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};
1
(4,6)
(3,5)
(0,4)
NULL
The C program on this page inserts new neighbors at the head of each linked list, so the latest inserted neighbor appears first when the list is printed.

↔️ 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;
}
With simple linked lists, checking one specific edge takes O(deg(u)) in the worst case, while visiting every neighbor of u naturally takes O(deg(u)).

🎬 Adjacency List Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Adjacency List Visualizer Watch each edge create linked-list nodes and inspect the final neighbors of any vertex.
Graph
Adjacency Lists
Current Edge
List Head
Inserted Node
Operation
Result
Details: Press Next to begin.

💻 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

⚡ Adjacency List Complexity

Space — O(V + E) Store one head per vertex plus list nodes for existing edges.
Insert at Head — O(1) Create a node, link it to the old head, then update adj[u].
Check Edge — O(deg(u)) Scan only u's neighbor list.
Iterate Neighbors — O(deg(u)) Visit exactly the stored neighbors of u.

🎯 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.

Traversal Style Explore the nearest undiscovered vertices before moving farther away.
Main Data Structure BFS uses a Queue to preserve first-discovered, first-processed order.
Repeated Visits A 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.

Discover Vertex
Mark Visited
Enqueue
Dequeue Later
Explore Neighbors
Important: Mark a vertex visited when it is enqueued, not later when it is dequeued. This prevents multiple already-discovered vertices from placing the same neighbor into the Queue.

🧠 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.

0 1 2 3 4
BFS order depends on the order in which neighbors are stored. With the adjacency lists above, the traversal is 0 1 2 3 4.

🎬 BFS Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Breadth-First Search Visualizer Watch visited vertices, the Queue, active neighbors, and BFS output change together.
Graph
BFS State
Queue
visited[]
Adjacency Lists
Traversal Output
Current
Neighbor
Front 0
Rear 0
Operation
Details: Press Next to begin.

💻 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[]

⚡ BFS Complexity

Time — O(V + E) With adjacency lists, every reachable vertex and edge is processed only a constant number of times.
visited[] — O(V) Stores whether each vertex has already been discovered.
Queue — O(V) In the worst case, many vertices can wait in the Queue at once.
Total Auxiliary Space — O(V) Ignoring the Graph representation itself, BFS needs visited information and a Queue.

🎯 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

Shortest Path in Unweighted Graphs BFS discovers vertices in increasing number of edges from the source.
Connected Components Repeated BFS calls can discover each disconnected component.
Level / Distance Problems Find how many edge-hops separate a source from other vertices.
Network Broadcasting Model information spreading outward one hop at a time.
Web / Social Exploration Explore nearby links, friends, or relationships before going deeper.
Bipartite Checking BFS levels can be used to assign alternating colors to vertices.
For weighted shortest paths with arbitrary positive weights, ordinary BFS is not enough; a later advanced Graph topic will use Dijkstra's algorithm.

🧭 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.

Traversal Style Go deeper along one branch before exploring alternative branches.
Main Data Structure Recursive DFS naturally uses the program's Call Stack. An iterative DFS can use an explicit Stack.
Repeated Visits A 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.

Visit Current
Choose Unvisited Neighbor
Recursive Call
Explore Deeper
Backtrack
Important: Mark a vertex visited before recursively exploring its neighbors. Without visited tracking, an undirected edge such as 0 — 1 can make DFS repeatedly call 0 → 1 → 0 → 1.

🧠 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:

0 1 3 2 4
DFS order depends on adjacency-list order. With the lists above, DFS is 0 1 3 2 4. Another valid neighbor order may produce a different DFS traversal.

🎬 DFS Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Depth-First Search Visualizer Watch recursion, backtracking, visited vertices, active neighbors, and DFS output together.
Graph
DFS State
Recursion Call Stack
visited[]
Adjacency Lists
Traversal Output
Current
Neighbor
Depth 0
Stack Top
Operation
Details: Press Next to begin.

💻 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

⚡ DFS Complexity

Time — O(V + E) With adjacency lists, each reachable vertex and edge is processed only a constant number of times.
visited[] — O(V) Records whether each vertex has already been discovered.
Call Stack — O(V) In the worst case, the recursion path can contain all vertices.
Total Auxiliary Space — O(V) Ignoring the Graph representation itself, recursive DFS needs visited information and recursion frames.

🎯 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

Connected Components Start DFS from each unvisited vertex to discover components.
Cycle Detection DFS state can detect back edges and cycles.
Topological Sorting DFS finishing order is one standard approach for DAGs.
Path Existence Explore whether one vertex can reach another.
Maze / Backtracking Problems Explore one route deeply, then return when the route fails.
Strongly Connected Components Advanced DFS-based algorithms use traversal and finishing information.
DFS is especially useful when the problem depends on recursion, backtracking, finishing order, or deep structural exploration rather than shortest unweighted distance.

🔁 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.

Undirected Graph DFS needs a parent value so the edge back to the immediate parent is not mistaken for a cycle.
Directed Graph DFS must know whether a visited vertex is still in the current recursion stack.
Disconnected Graph Run DFS from every still-unvisited vertex so all components are checked.

📌 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.

For the starting DFS call, use parent = -1 because the start vertex has no parent.

➡️ 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.

A directed cycle is found when an edge reaches a vertex whose 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.

🔁 Cycle Found

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 Found
A visited directed neighbor does not automatically mean a cycle. It must still be active in the current recursion path.

🎬 Cycle Detection Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Graph Cycle Detection Visualizer Switch between Undirected Parent Tracking and Directed Recursion-Stack detection.
Graph
DFS Cycle State
Recursion Call Stack
visited[]
parent[]
Adjacency Lists
Current
Neighbor
Parent
Operation
Result Checking
Details: Press Next to begin.

💻 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

💻 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

⚡ Cycle Detection Complexity

Undirected Time — O(V + E) DFS visits each vertex and scans each adjacency-list edge entry only a constant number of times.
Directed Time — O(V + E) visited[] and recStack[] allow each directed edge to be examined once during DFS.
Auxiliary Space — O(V) visited[], parent/recStack information, and recursive Call Stack are all linear in the worst case.
Disconnected Graphs The outer loop starts DFS from every still-unvisited vertex, preserving the O(V + E) total bound.

🎯 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.

Dependency Meaning If u → v means “u must happen before v,” then every valid topological order respects that dependency.
Not Usually Unique A DAG may have several valid topological orders when multiple vertices are independent.
Directed Acyclic Graph A complete topological ordering exists only when the Graph is a DAG.

📌 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.

Kahn's cycle test: if fewer than V vertices can be removed by repeatedly selecting indegree-0 vertices, some cycle is preventing the remaining vertices from ever reaching indegree 0.

🚶 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 exposes cycle detection naturally through processed count < V. DFS exposes it through a back edge to an active recursion state.

🧩 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
02
12
21
31
40
50

The initial Queue is [4, 5]. Processing those vertices gradually reduces other indegrees to zero.

4 5 2 0 3 1
This is one valid topological order. Another valid order may exist if multiple indegree-0 vertices are available at the same time.

🎬 Topological Sort Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Kahn's Topological Sort Visualizer Watch indegree[], the Queue, processed vertices, and topological order change together.
Directed Graph
Kahn's Algorithm State
Queue of indegree-0 Vertices
indegree[]
Adjacency Lists
Topological Order
Current
Neighbor
Processed 0
Operation
Result Checking
Details: Press Next to begin.

💻 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

⚡ Topological Sort Complexity

Kahn Time — O(V + E) Each vertex enters the Queue at most once, and each directed edge reduces one indegree once.
DFS Time — O(V + E) DFS visits every vertex and edge once with adjacency lists.
Auxiliary Space — O(V) Kahn uses indegree[] + Queue + order[]. DFS uses visited state + recursion/Stack.
Cycle Detection Kahn detects a cycle when processed count is less than V after the Queue becomes empty.

🎯 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.

One Traversal = One Component Start DFS or BFS from an unvisited vertex; every vertex reached belongs to the same component.
Count Components Every time the outer loop finds a still-unvisited vertex, a new connected component begins.
Assign Component IDs Store the current component number for each vertex as it is visited.

📌 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
An isolated vertex with no edges is itself a connected component of size 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
DFS uses recursion/Stack; BFS uses a Queue. Both find the same connected components in an undirected Graph.

🧠 Counting Components

Vertices: 0 1 2 3 4 5 6 7

Edges:
0 - 1
1 - 2
3 - 4
5 - 6
6 - 7
Component 1 = {0, 1, 2}
Component 2 = {3, 4}
Component 3 = {5, 6, 7}

The Graph therefore contains 3 connected components.

🎬 Connected Components Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Connected Components Visualizer Switch between DFS and BFS and watch each component receive its own ID.
Undirected Graph
Component Traversal State
DFS Call Stack
visited[]
componentId[]
Adjacency Lists
Current
Neighbor
Component 0
Operation
Count 0
Details: Press Next to begin.

💻 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

⚡ Connected Components Complexity

DFS Time — O(V + E) Every vertex is visited once and each undirected adjacency entry is scanned a constant number of times.
BFS Time — O(V + E) The same bound holds when Queue-based BFS is used instead of DFS.
Auxiliary Space — O(V) visited[], componentId[], and recursion Stack / Queue are linear in the worst case.
Component Counting The outer loop is O(V); all traversals together still process the Graph only once.

🎯 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.

Distance The distance from source to a vertex is the minimum number of edges needed to reach it.
Shortest-Path Tree When BFS first discovers a vertex, remember which vertex discovered it using parent[].
Unreachable Vertex If a vertex is never discovered, its distance stays at the sentinel value -1.

📌 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.

Mark a vertex visited when it is enqueued. That guarantees its first recorded distance and parent define a shortest path.

📊 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:

target parent[target] ... source

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
0 1 3 4 5
Another shortest path can exist when multiple parents could reach a vertex at the same minimum distance. The path stored depends on neighbor-processing order.

🎬 Unweighted Shortest Path Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Unweighted Shortest Path Visualizer Watch BFS build distance[] and parent[], then reconstruct the final shortest path.
Undirected Graph
BFS Shortest-Path State
Queue
distance[]
parent[]
Adjacency Lists
Reconstructed Path
Current
Neighbor
Distance
Operation
Result Checking
Details: Press Next to begin.

💻 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

⚡ Unweighted Shortest Path Complexity

BFS Time — O(V + E) Every reachable vertex is discovered once and adjacency-list edges are scanned a constant number of times.
Path Reconstruction — O(V) In the worst case, the shortest path can contain all vertices.
Auxiliary Space — O(V) visited[], distance[], parent[], Queue, and path[] are all linear in the worst case.
Adjacency Matrix Version If every visited vertex scans all V possible neighbors, traversal can become O(V²).

🎯 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.

Two Sets Every vertex belongs to exactly one of two groups, which we can call Set A and Set B.
Every Edge Crosses For every edge u — v, u and v must receive opposite sets/colors.
Disconnected Graphs Every disconnected component must also satisfy the two-coloring rule.

📌 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.

Set A — color 0
024
Set B — color 1
135

🚶 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
The outer loop is necessary because one BFS only checks the connected component containing its start vertex.

📚 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.

BFS and DFS may color vertices in different discovery orders, but any valid result still separates every edge across the two sets.

🔺 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
Set A
024
Set B
135

Every listed edge crosses between Set A and Set B, so the Graph is bipartite.

🎬 Bipartite Graph Visualizer

INTERACTIVE ALGORITHM VISUALIZATION
CodeBhavya CodeBhavya
🎬 Premium Bipartite Graph Checking Visualizer Switch between BFS and DFS while color[] separates vertices into two sets.
Undirected Graph
2-Coloring State
BFS Queue
color[]
Adjacency Lists
Current Sets
Set A
Set B
Current
Neighbor
Color
Operation
Result Checking
Details: Press Next to begin.

💻 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

⚡ Bipartite Checking Complexity

BFS Time — O(V + E) Every vertex is colored once and each adjacency-list edge entry is examined a constant number of times.
DFS Time — O(V + E) Recursive 2-coloring has the same traversal complexity with adjacency lists.
Auxiliary Space — O(V) color[] plus Queue or recursion/Stack requires linear space in the worst case.
Disconnected Graphs The outer loop starts a new BFS/DFS for every still-uncolored component without changing the O(V + E) total bound.

🎯 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?

Need level-by-level traversal? Use BFS.
Need deep exploration/backtracking? Use DFS.
Need shortest path with equal edge cost? Use BFS + distance[] + parent[].
Need to count disconnected groups? Run DFS/BFS from every unvisited vertex.
Need dependency ordering? Use Topological Sort, but only for a DAG.
Need to detect a cycle? Undirected: DFS + parent. Directed: DFS + recursion state.
Need two compatible groups? Use Bipartite / 2-coloring.
Need fast edge lookup in a dense Graph? Consider an Adjacency Matrix.
Need efficient traversal in a sparse Graph? Prefer an Adjacency List.
Level 7 boundary: weighted shortest paths, minimum spanning trees, strongly connected components, bridges, articulation points, and deeper Graph algorithms belong to the later Advanced Graph Algorithms level.

⚡ Core Graph Complexity Summary

Task Adjacency List Adjacency Matrix Auxiliary Space
BFSO(V + E)O(V²)O(V)
DFSO(V + E)O(V²)O(V)
Connected ComponentsO(V + E)O(V²)O(V)
Cycle DetectionO(V + E)O(V²)O(V)
Topological SortO(V + E)O(V²) if matrix-scanningO(V)
Unweighted Shortest PathO(V + E)O(V²)O(V)
Bipartite CheckO(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

1. What is a Graph?
A Graph G = (V, E) consists of a set of vertices V and a set of edges E connecting vertices.
Interview answer: Vertices represent objects; edges represent relationships.
2. What is the difference between degree, indegree, and outdegree?
Degree is used naturally for undirected Graphs. In a directed Graph, indegree counts incoming edges and outdegree counts outgoing edges.
Interview answer: Degree counts incident undirected edges; indegree/outdegree split incoming/outgoing directed edges.
3. When is an adjacency matrix a good choice?
It is useful for dense Graphs or when checking whether a particular edge exists must be very fast.
Interview answer: Dense Graphs and O(1) edge-existence checks.
4. Why is an adjacency list preferred for many sparse Graphs?
It uses O(V + E) space and lets traversal algorithms iterate only the neighbors that actually exist.
Interview answer: It avoids storing absent edges.
5. What is a connected component?
In an undirected Graph, a connected component is a maximal set of vertices in which every pair is connected by some path.
Interview answer: A maximal mutually reachable region of an undirected Graph.
6. Why is an undirected adjacency matrix symmetric?
An undirected edge between u and v is stored in both matrix[u][v] and matrix[v][u].
Interview answer: Undirected edges are stored in both directions.
7. Is a directed Graph's adjacency matrix necessarily symmetric?
No. Edge u → v does not automatically imply edge v → u, so opposite cells may differ.
Interview answer: No—direction can make opposite matrix cells different.
8. What problem occurs if zero is a valid edge weight?
If zero also means “no edge,” a zero-weight edge becomes ambiguous. Use another sentinel such as INF or maintain a separate edge-existence structure.
Interview answer: Zero-weight edges become indistinguishable from absent edges.
9. Where is a self-loop stored in an adjacency matrix?
A self-loop u → u is stored at matrix[u][u], on the main diagonal.
Interview answer: At diagonal cell matrix[u][u].
10. What are the main adjacency-matrix complexity trade-offs?
It uses O(V²) space, supports O(1) edge insertion/checking, and requires O(V) to scan all possible neighbors of one vertex.
Interview answer: O(V²) space for O(1) edge access.
11. Why is an adjacency list efficient for sparse Graphs?
It stores only edges that actually exist, so the standard space requirement is O(V + E) instead of O(V²).
Interview answer: It avoids storing absent edges.
12. How is an undirected edge stored in an adjacency list?
For edge u — v, insert v into u's list and u into v's list.
Interview answer: Two list entries, one in each direction.
13. What is the complexity of iterating all neighbors of vertex u?
It is O(deg(u)) because the algorithm visits exactly the stored neighbor nodes for u.
Interview answer: O(deg(u)).
14. What extra information is stored for a weighted adjacency list?
Each adjacency entry stores both the neighbor vertex and its edge weight, often as a pair such as (v, weight).
Interview answer: Neighbor vertex + edge weight.
15. Why are adjacency lists natural for BFS and DFS?
BFS and DFS repeatedly need the neighbors of the current vertex. An adjacency list provides those neighbors directly without scanning all V possible vertices.
Interview answer: They can process actual neighbors in O(deg(u)) time.
16. Which data structure does BFS use?
BFS uses a FIFO Queue so vertices are processed in the same order in which they are discovered.
Interview answer: Queue.
17. When should a vertex be marked visited in BFS?
Mark it visited when it is discovered and enqueued. Waiting until dequeue time can allow multiple vertices to enqueue the same undiscovered neighbor.
Interview answer: Mark visited when enqueuing.
18. What is BFS complexity with an adjacency list?
The traversal takes O(V + E) time because each vertex is discovered at most once and adjacency lists collectively contain O(E) edge entries.
Interview answer: O(V + E) time and O(V) auxiliary space.
19. Why can BFS find shortest paths in an unweighted Graph?
BFS explores vertices by increasing number of edges from the source, so the first time a vertex is reached uses the minimum number of edges.
Interview answer: BFS processes the Graph level by level.
20. What happens if BFS starts in one component of a disconnected Graph?
A single BFS visits only the vertices reachable from that start vertex. To visit the whole disconnected Graph, start another BFS from every still-unvisited vertex.
Interview answer: One BFS visits one reachable component.
21. Which data structure does recursive DFS use?
Recursive DFS uses the program's Call Stack. An iterative DFS can explicitly use a Stack data structure.
Interview answer: Stack — implicit Call Stack for recursive DFS.
22. Why is visited[] necessary in DFS?
Graphs can contain cycles. Without visited tracking, DFS may repeatedly revisit the same vertices and recurse forever.
Interview answer: It prevents repeated processing and infinite recursion around cycles.
23. What is DFS complexity with an adjacency list?
DFS takes O(V + E) time with adjacency lists and O(V) auxiliary space in the worst case for visited[] plus recursion depth.
Interview answer: O(V + E) time and O(V) auxiliary space.
24. What is backtracking in recursive DFS?
When a DFS call has no remaining unvisited neighbors, it returns to its caller. The caller then continues from the next neighbor in its own adjacency list.
Interview answer: Return from a completed branch to the previous DFS call.
25. What is the main traversal difference between BFS and DFS?
BFS explores level by level using a Queue, while DFS explores one branch deeply before backtracking using a Stack or recursion.
Interview answer: BFS = breadth with Queue; DFS = depth with Stack/recursion.
26. Why does undirected DFS need a parent parameter for cycle detection?
Because every undirected edge appears in both directions. The immediate edge back to the parent is expected and must not be reported as a cycle.
Interview answer: Ignore the normal reverse edge to the parent.
27. What is the undirected DFS condition that confirms a cycle?
While processing current, if a neighbor is already visited and neighbor != parent, a cycle exists.
Interview answer: visited[neighbor] && neighbor != parent.
28. Why is visited[] alone insufficient for directed cycle detection?
A directed edge may point to a vertex whose DFS branch has already completed. That visited edge does not necessarily form a cycle. We must know whether the vertex is still active on the current recursive path.
Interview answer: We need recursion-stack state, not only historical visitation.
29. What directed DFS condition confirms a cycle?
A cycle exists when an outgoing edge reaches a vertex whose recStack[] value is still 1.
Interview answer: recStack[neighbor] == 1.
30. What is the time complexity of DFS-based cycle detection with adjacency lists?
Both the undirected parent-tracking method and directed recursion-stack method run in O(V + E) time with adjacency lists.
Interview answer: O(V + E) time and O(V) auxiliary space.
31. What is a topological ordering?
It is a linear ordering of a directed Graph's vertices such that for every edge u → v, u appears before v.
Interview answer: Every directed edge points from an earlier vertex to a later vertex in the order.
32. Why must a Graph be acyclic to have a valid topological order?
A directed cycle creates circular prerequisites: each vertex in the cycle would need to appear before another and eventually before itself.
Interview answer: A directed cycle creates contradictory ordering constraints.
33. What does Kahn's Algorithm place in its Queue?
It places vertices whose current indegree is 0, meaning they have no remaining unprocessed prerequisites.
Interview answer: Vertices with indegree 0.
34. How does Kahn's Algorithm detect a directed cycle?
If the Queue becomes empty before all V vertices are processed, the remaining vertices are blocked by a cycle.
Interview answer: processed count < V.
35. How does DFS produce a topological order?
After DFS finishes exploring all outgoing neighbors of a vertex, it pushes that vertex onto a Stack. Popping vertices in reverse finishing order gives a topological ordering for a DAG.
Interview answer: Reverse DFS finishing order.
36. What is a connected component?
It is a maximal set of vertices in an undirected Graph such that every pair of vertices in the set is connected by a path.
Interview answer: A maximal mutually reachable group of vertices.
37. How do you count connected components using DFS or BFS?
Scan every vertex. Whenever an unvisited vertex is found, increment the component count and run DFS/BFS from it to mark that whole component.
Interview answer: Number of traversal starts from previously unvisited vertices.
38. What component does an isolated vertex belong to?
An isolated vertex has no edges, so it forms a connected component by itself.
Interview answer: A size-1 connected component.
39. Do DFS and BFS produce different connected components?
No. They may visit vertices in different orders, but both discover exactly the same reachable set from each start vertex.
Interview answer: Same components, possibly different traversal order.
40. What is the complexity of finding all connected components with adjacency lists?
The complete algorithm takes O(V + E) time and O(V) auxiliary space in the worst case.
Interview answer: O(V + E) time, O(V) auxiliary space.
41. Why does BFS find shortest paths in an unweighted Graph?
BFS explores vertices in increasing number of edges from the source. Therefore, the first discovery of a vertex uses the minimum possible number of edges.
Interview answer: BFS processes the Graph level by level.
42. What does distance[v] store in BFS shortest path?
It stores the minimum number of edges from the source to vertex v.
Interview answer: Minimum edge count from the source.
43. Why is parent[] needed if distance[] already gives the shortest distance?
distance[] tells how far the target is, but parent[] records the predecessor chain required to reconstruct an actual shortest path.
Interview answer: parent[] reconstructs the path itself.
44. How can you identify that a target is unreachable?
Initialize distances to a sentinel such as -1. If distance[target] is still -1 after BFS, the source cannot reach the target.
Interview answer: distance[target] remains -1.
45. Can ordinary BFS solve shortest paths with arbitrary positive edge weights?
No. Ordinary BFS assumes every edge contributes the same cost. With arbitrary positive weights, a different algorithm such as Dijkstra's algorithm is needed.
Interview answer: No; BFS is for equal-cost edges.
46. What is a bipartite Graph?
It is a Graph whose vertices can be split into two disjoint sets so every edge connects one set to the other.
Interview answer: A Graph that can be validly divided into two edge-crossing sets.
47. How does 2-coloring test whether a Graph is bipartite?
Assign opposite colors to adjacent vertices during BFS or DFS. If any edge connects two vertices with the same color, the Graph is not bipartite.
Interview answer: Every edge must connect opposite colors.
48. Why must a disconnected Graph be checked from every uncolored vertex?
One BFS or DFS reaches only its own connected component. Another disconnected component may contain a coloring conflict or odd cycle.
Interview answer: Every connected component must satisfy the bipartite condition.
49. What is the relation between bipartite Graphs and odd cycles?
An undirected Graph is bipartite if and only if it contains no odd-length cycle.
Interview answer: Bipartite ⇔ no odd cycle.
50. What is the time complexity of bipartite checking with BFS and adjacency lists?
The algorithm runs in O(V + E) time and needs O(V) auxiliary space for color[] and the Queue.
Interview answer: O(V + E) time, O(V) auxiliary space.

🎯 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.

📈 Graph Practice Progress
Solved 0 / 20
Completed with Solution 0
Total Score 0 / 2000
Completion 0%
Practice Badge 🌱 Starter

🏆 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.

← Previous Topic: Trees Next Topic: DSA Practice →