Short Notes: Graph

Last Updated : 24 Jan, 2026

Graph Data Structure is a non-linear structure used to represent relationships between objects. It consists of a set of vertices (nodes) connected by edges (links). Unlike trees, graphs can have cycles and there is no strict parent-child hierarchy.


Basic Terminologies In Graph Data Structure:

  • Vertex (Node): A fundamental unit representing an object or point in the graph.
  • Edge: A connection between two vertices representing a relationship.
  • Adjacent Vertices: Two vertices connected directly by an edge.
  • Degree: Number of edges connected to a vertex.
    - In-degree: Number of incoming edges (for directed graphs).
    - Out-degree: Number of outgoing edges (for directed graphs).
  • Path: A sequence of vertices connected by edges.
  • Cycle: A path that starts and ends at the same vertex without repeating edges or vertices.
  • Subgraph: A graph formed from a subset of vertices and edges of the original graph.

Types of Graph Data Structures :

Based on Direction of Edges

I. Directed Graph:

  • In a directed graph, each edge has a specific direction, going from one vertex (the source) to another vertex (the destination).
  • If there is an edge from u to v, then you can only move from vertex u to vertex v and vice versa is not possible.
frame_3180

II. Undirected Graph:

  • In an undirected graph, edges have no direction. The connection between two vertices is mutual, meaning you can move freely in both directions.
  • If there is an edge between u and v, then you can move from vertex u to vertex v and vice versa.
frame_3183

Based on Weights

I. Weighted Graph

  • A graph in which each edge has a weight or cost associated with it.
  • Weights represent some measurable value, such as distance, time, price, or capacity.
frame_3182

II. Unweighted Graph

  • A graph in which all edges are considered equal; no edge has an associated weight or cost.
  • Only the presence or absence of an edge matters, not the “distance” or “cost” between vertices.
frame_3183

Based on Connectivity

I. Connected Graph

  • An undirected graph is connected if there is a path between every pair of vertices.
  • No vertex is isolated; the graph forms a single component.
frame_3183

II. Disconnected Graph

  • A graph is disconnected if at least one vertex cannot be reached from another vertex.
  • Such a graph has two or more components, each of which is a connected subgraph.
frame_3185

Based on Cycles

I. Cyclic Graph

  • A graph that contains at least one cycle, i.e., a path that starts and ends at the same vertex without repeating edges.
  • Cycles indicate loops or repeated paths in the network.
frame_3181

II. Acyclic Graph

  • A graph that contains no cycles.
  • Ensures a one-way flow or hierarchy without loops.
frame_3180

Applications of Graph:

applications_of_graphs

Representation of Graph:

1. Adjacency Matrix

  • A 2D array matrix[V][V] where V is the number of vertices.
  • matrix[i][j] = 1 (or weight) if there is an edge from vertex i to j; 0 if no edge.
frame_3164

2. Adjacency List

  • An array/list of lists: each vertex has a list of all adjacent vertices.
  • Efficient for sparse graphs, takes O(V + E) space.
frame_3165

3. Edge List

  • A simple list of all edges in the graph.
  • For unweighted graph - often represented as pairs (u, v), for weighted graphs, often represented as (u, v, weight).
frame_3166

Graph Traversals :

1. Depth-First Search (DFS)

Explore as far as possible along each branch before backtracking.

Approach:

  • Can be implemented recursively or using a stack.
  • Start at a source vertex, visit a neighbor, then recursively visit its neighbors until all reachable vertices are visited.

Let us understand the working of Depth First Search with the help of the following Illustration:

C++
void dfs(vector<vector<int>> &adj, 
vector<bool> &visited, int s, vector<int> &res) {

    visited[s] = true;

    res.push_back(s);

    // Recursively visit all adjacent vertices
    // that are not visited yet
    for (int i : adj[s])
        if (visited[i] == false)
            dfs(adj, visited, i, res);
}
C
void dfs(int adj[V][V], int visited[V], int s, int res[V], int *idx) {
    visited[s] = 1;
    res[(*idx)++] = s;

    // Recursively visit all adjacent vertices
    // that are not visited yet
    for (int i = 0; i < V; i++) {
        if (adj[s][i] && visited[i] == 0)
            dfs(adj, visited, i, res, idx);
    }
}
Java
static void dfs(ArrayList<ArrayList<Integer>> adj,
           boolean[] visited, int s, ArrayList<Integer> res) {
    visited[s] = true;
    res.add(s);

    // Recursively visit all adjacent vertices 
    // that are not visited yet
    for (int i : adj.get(s)) {
        if (!visited[i]) {
            dfs(adj, visited, i, res);
        }
    }
}
Python
def dfs(adj, visited, s, res):
    visited[s] = True
    res.append(s)

    # Recursively visit all adjacent vertices 
    # that are not visited yet
    for i in adj[s]:
        if not visited[i]:
            dfs(adj, visited, i, res)
C#
static void dfs(List<List<int>> adj, bool[] visited,
                   int s, List<int> res) {
    visited[s] = true;
    res.Add(s);

    // Recursively visit all adjacent vertices 
    // that are not visited yet
    foreach (int i in adj[s]) {
        if (!visited[i])
            dfs(adj, visited, i, res);
    }
}
JavaScript
function dfs(adj, visited, s, res) {
    visited[s] = true;
    res.push(s);

    // Recursively visit all adjacent vertices 
    // that are not visited yet
    for (let i of adj[s]) {
        if (!visited[i]) {
            dfs(adj, visited, i, res);
        }
    }
}

Time complexity: O(V + E), where V is the number of vertices and E is the number of edges in the graph.
Auxiliary Space: O(V + E), since an extra visited array of size V is used.

To read more about DFS, read this article.

2. Breadth-First Search (BFS)

Explore all neighbors at the current level before moving to the next level.

Approach:

  • Implemented using a queue.
  • Start at a source vertex, visit all its neighbors, then move to neighbors of neighbors, level by level.

Let us understand the working of Breadth First Search with the help of the following Illustration:

C++
vector<int> bfs(vector<vector<int>>& adj) {
    int V = adj.size();
    vector<bool> visited(V, false);
    vector<int> res;
    
    queue<int> q;
    
    int src = 0;
    visited[src] = true;
    q.push(src);

    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        res.push_back(curr);

        // visit all the unvisited
        // neighbours of current node
        for (int x : adj[curr]) {
            if (!visited[x]) {
                visited[x] = true;
                q.push(x);
            }
        }
    }
    
    return res;
}
C
void bfs(int adj[V][V], int res[V], int *resSize) {
    int visited[V] = {0};
    int q[MAXQ];
    int front = 0, rear = 0;
    int src = 0;
    visited[src] = 1;
    q[rear++] = src;

    while (front < rear) {
        int curr = q[front++];
        res[(*resSize)++] = curr;

        // visit all the unvisited
        // neighbours of current node
        for (int x = 0; x < V; x++) {
            if (adj[curr][x] && !visited[x]) {
                visited[x] = 1;
                q[rear++] = x;
            }
        }
    }
}
Java
static ArrayList<Integer> bfs(ArrayList<ArrayList<Integer>> adj) {
    int V = adj.size();
    boolean[] visited = new boolean[V];
    ArrayList<Integer> res = new ArrayList<>();
    
    int src = 0;
    Queue<Integer> q = new LinkedList<>();
    visited[src] = true;
    q.add(src);

    while (!q.isEmpty()) {
        int curr = q.poll();
        res.add(curr);

        // visit all the unvisited
        // neighbours of current node
        for (int x : adj.get(curr)) {
            if (!visited[x]) {
                visited[x] = true;
                q.add(x);
            }
        }
    }
    
    return res;
}
Python
def bfs(adj):
    V = len(adj)
    visited = [False] * V
    res = []
    
    src = 0
    q = deque()
    visited[src] = True
    q.append(src)

    while q:
        curr = q.popleft()
        res.append(curr)

        # visit all the unvisited
        # neighbours of current node
        for x in adj[curr]:
            if not visited[x]:
                visited[x] = True
                q.append(x)
                
    return res
C#
static List<int> bfs(List<List<int>> adj) {
    int V = adj.Count;
    bool[] visited = new bool[V];
    List<int> res = new List<int>();

    int src = 0;
    Queue<int> q = new Queue<int>();
    visited[src] = true;
    q.Enqueue(src);

    while (q.Count > 0) {
        int curr = q.Dequeue();
        res.Add(curr);
        
        // visit all the unvisited
        // neighbours of current node
        foreach (int x in adj[curr]) {
            if (!visited[x]) {
                visited[x] = true;
                q.Enqueue(x);
            }
        }
    }
    
    return res;
}
JavaScript
function bfs(adj) {
    const V = adj.length;
    const visited = new Array(V).fill(false);
    const res = [];

    const q = new Denque();

    let src = 0;
    visited[src] = true;
    q.push(src);

    while (!q.isEmpty()) {
        const curr = q.shift();
        res.push(curr);

        // visit all the unvisited
        // neighbours of current node
        for (const x of adj[curr]) {
            if (!visited[x]) {
                visited[x] = true;
                q.push(x);
            }
        }
    }

    return res;
}

Time Complexity: O(V + E), BFS explores all the vertices and edges in the graph. It visits every vertex and edge only once.
Auxiliary Space: O(V), Using a queue to keep track of the vertices that need to be visited.

To read more about BFS, read this article.

Cycles in Graph:

A cycle in a graph is a path that starts and ends at the same vertex, with all edges and vertices (except the starting/ending vertex) distinct.

1. In Undirected Graphs

frame_3184

[Approach - 1] : Using DFS

  • Method: Use DFS with a visited array.
  • Logic:
    1. Start DFS from any vertex.
    2. Keep track of the parent of the current vertex.
    3. If a neighbor is visited and is not the parent, a cycle exists.

Algorithm:

DFS(v, parent):
mark v as visited
for each neighbor u of v:
if u is not visited:
if DFS(u, v) returns true:
return true
else if u != parent:
return true
return false

[Approach - 2] : Using BFS

  • Method: Use BFS with a queue and parent tracking.
  • Logic:
    1. Start BFS from any unvisited vertex.
    2. Maintain a parent array (or map) to record the node from which each vertex is discovered.
    3. If a visited neighbor is found that is not the parent, a cycle exists.

Algorithm:

BFS(start):    
create a queue and push (start, -1)
mark start as visited
while queue is not empty:
(v, parent) = queue.pop()
for each neighbor u of v:
if u is not visited:
mark u as visited
queue.push((u, v))
else if u != parent:
return true
return false

2. In Directed Graphs

frame_3181

[Approach-1] Using DFS

  • Method: Use DFS with a recursion stack (or color method).
  • Logic:
    1. Maintain a recursion stack (or mark nodes as WHITE, GRAY, BLACK).
    2. During DFS, if you visit a vertex already in the recursion stack (or GRAY), a cycle exists.

Algorithm (DFS-based):

DFS(v):
mark v as visited and add to recursion stack
for each neighbor u of v:
if u is not visited:
if DFS(u) returns true:
return true
else if u is in recursion stack:
return true
remove v from recursion stack
return false

[Approach-2] Using BFS

  • Method: Use BFS with indegree calculation.
  • Logic:
    1. In a Directed Graph, a cycle exists if we cannot perform a valid topological sort.
    2. Compute the indegree (number of incoming edges) of each vertex.
    3. Push all vertices with indegree = 0 into a queue.
    4. Repeatedly remove nodes from the queue and decrease the indegree of their neighbors.
    5. If any neighbor’s indegree becomes 0, push it into the queue.
    6. After processing, if the number of nodes processed is less than total nodes, a cycle exists.

Algorithm:

BFS(V, adj):    
indegree[V] = 0
for each vertex u in V:
for each neighbor v of u:
indegree[v] += 1
create an empty queue
for each vertex u in V:
if indegree[u] == 0:
queue.push(u)

count = 0
while queue is not empty:
node = queue.pop()
count += 1
for each neighbor v of node:
indegree[v] -= 1
if indegree[v] == 0:
queue.push(v)

if count != V:
return true // cycle exists
else:
return false // no cycle

What is Topological Sort?

Topological Sort is a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge u → v, vertex u appears before v in the ordering.

  • Applies only to DAGs (Directed Acyclic Graphs), and is not possible for cyclic or undirected graphs.
  • It doesn’t “traverse” all vertices like BFS/DFS; instead, it represents dependency ordering between them.
  • Used in problems like task scheduling, build order, course prerequisite ordering, etc.

Topological Sorting Using DFS:

The main idea is to perform a Depth First Search (DFS) on the Directed Acyclic Graph (DAG) and, for each vertex, push it onto a stack only after visiting all its adjacent vertices. This ensures that every vertex appears after all its neighboring vertices.
Finally, reversing the stack (or popping elements from it) gives the topological ordering of the graph.

Refer to this article to read more.

Topological Sorting Using BFS:

Topological Sort using BFS (Kahn’s Algorithm) works by repeatedly selecting vertices with in-degree zero(no dependencies), adding them to the result, and reducing the in-degree of their adjacent vertices. This process continues until all vertices are processed, producing a valid linear ordering of a DAG.

Refer to this article to read more.

To know more about topological sort, read this article.

Comment