C Programming Data Structures Dfs Bfs Traversals Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    10 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of C Programming data structures DFS, BFS Traversals

Data Structures DFS and BFS Traversals in C

Overview

Depth-First Search (DFS) and Breadth-First Search (BFS) are fundamental algorithms used to traverse data structures like graphs and trees. Both traversals have unique properties and applications, crucial for solving problems in computer science.

Graph Representation in C

In C, graphs can be represented using:

  1. Adjacency Matrix
  2. Adjacency List

We'll use the adjacency list representation here, which is space-efficient for sparse graphs.

Adjacency List Structure

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// Node structure for linked list representing adjacent vertices
struct AdjListNode {
    int dest;
    struct AdjListNode* next;
};

// Structure to represent an adjacency list
struct AdjList {
    struct AdjListNode *head;
};

// Structure to represent a graph
struct Graph {
    int V; // Number of vertices
    struct AdjList* array;
};

// Utility function to create a new adjacency list node
struct AdjListNode* newAdjListNode(int dest) {
    struct AdjListNode* newNode = (struct AdjListNode*) malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

// Utility function that creates a graph of V vertices
struct Graph* createGraph(int V) {
    struct Graph* graph = (struct Graph*) malloc(sizeof(struct Graph));
    graph->V = V;

    // Create an array of adjacency lists. Size of array will be V
    graph->array = (struct AdjList*) malloc(V * sizeof(struct AdjList));

    // Initialize each adjacency list as empty by making head as NULL
    for (int i = 0; i < V; ++i)
        graph->array[i].head = NULL;

    return graph;
}

// Adds an edge to an undirected graph
void addEdge(struct Graph* graph, int src, int dest) {
    // Add an edge from src to dest. A new node is added to the adjacency list of src.
    // The node is added at the beginning
    struct AdjListNode* newNode = newAdjListNode(dest);
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;

    // Since graph is undirected, add an edge from dest to src also
    newNode = newAdjListNode(src);
    newNode->next = graph->array[dest].head;
    graph->array[dest].head = newNode;
}

Depth-First Search (DFS)

DFS explores as far as possible along each branch before backtracking. It can be implemented recursively or iteratively.

Recursive DFS Approach

// A utility function to do DFS traversal. It uses recursion.
void DFSUtil(struct Graph* graph, int v, bool visited[]) {
    // Mark the current node as visited and print it
    visited[v] = true;
    printf("%d ", v);

    // Recur for all the vertices adjacent to this vertex
    struct AdjListNode* pCrawl = graph->array[v].head;
    while (pCrawl) {
        int neighbor = pCrawl->dest;
        if (!visited[neighbor])
            DFSUtil(graph, neighbor, visited);
        pCrawl = pCrawl->next;
    }
}

// DFS traversal of the vertices reachable from v (where v is the starting vertex)
void DFS(struct Graph* graph, int v) {
    // Mark all the vertices as not visited (false)
    bool* visited = (bool *)malloc(graph->V * sizeof(bool));
    for (int i = 0; i < graph->V; i++)
        visited[i] = false;

    // Call the recursive helper function to print DFS traversal
    DFSUtil(graph, v, visited);
}

Iterative DFS Approach

#include <limits.h> 
#include <stack>

void iterativeDFS(struct Graph* graph, int start) {
    bool visited[graph->V];
    memset(visited, false, sizeof(visited));

    std::stack<int> stack;
    stack.push(start);

    while (!stack.empty()) {
        start = stack.top();
        stack.pop();

        if (!visited[start]) {
            printf("%d ", start);
            visited[start] = true;
        }

        struct AdjListNode* pCrawl = graph->array[start].head;
        while (pCrawl) {
            if (!visited[pCrawl->dest])
                stack.push(pCrawl->dest);
            pCrawl = pCrawl->next;
        }
    }
}
  • Visited Array: Tracks visited nodes to avoid cycles.
  • Stack: Recursive DFS uses an implicit stack via the call stack, while iterative DFS explicitly uses a stack data structure.
  • Applications: Pathfinding, Cycle Detection in Graphs.

Breadth-First Search (BFS)

BFS visits all the neighbors at the present depth prior to moving on to nodes at the next depth level, using a queue.

BFS Approach

#include<queue>

void BFS(struct Graph* graph, int s) {
    // Mark all the vertices as not visited
    bool visited[graph->V];
    memset(visited, false, sizeof(visited));

    std::queue<int> queue;

    // Mark the current node as visited and enqueue it
    visited[s] = true;
    queue.push(s);

    while(!queue.empty()) {
        // Dequeue a vertex from queue and print it
        s = queue.front();
        printf("%d ", s);
        queue.pop();

        // Get all adjacent vertices of the dequeued vertex s
        // If an adjacent has not been visited, mark it visited and enqueue it
        struct AdjListNode* pCrawl = graph->array[s].head;
        while (pCrawl != NULL) {
            int neighbor = pCrawl->dest;
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                queue.push(neighbor);
            }
            pCrawl = pCrawl->next;
        }
    }
}
  • Queue: Implements FIFO logic essential for visiting nodes level by level.
  • Visited Array: Tracks already visited nodes.
  • Applications: Shortest paths in Unweighted Graphs, Peer-to-Peer Networks, Social Networking.

Example Usage

Let's see how we can put this all together.

int main() {
    // Create the following graph 
    //       0 
    //      / \ 
    //     1---3 
    //     |   |
    //     2---4 

    int V = 5; // Total vertices
    struct Graph* graph = createGraph(V);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 3); 
    addEdge(graph, 1, 2);
    addEdge(graph, 3, 4);
    addEdge(graph, 2, 4);

    printf("\nDepth First Traversal (starting from vertex 0): ");
    DFS(graph, 0);

    printf("\nBreadth First Traversal (starting from vertex 0): "); 
    BFS(graph, 0);

    return 0;
}

Key Points and Import Details

  1. Graph Representation: Efficiently using adjacency lists reduces memory usage for sparse graphs.
  2. Recursive vs. Iterative:
    • DFS: Can lead to stack overflow if recursion depth is high.
    • BFS: Uses more memory because it stores all neighbors first but guarantees shortest paths in unweighted graphs.
  3. Use Cases: DFS is optimal for applications needing to explore all paths, whereas BFS is suitable for finding shortest paths and related level-based tasks.
  4. Time Complexity: Both DFS and BFS have ( O(V + E) ) time complexity where ( V ) is the number of vertices, and ( E ) is the number of edges.
  5. Space Complexity:
    • DFS: ( O(V) ) due to call stack in recursion.
    • BFS: ( O(V) ) due to queue storage in levels.
  6. Data Structures Used:
    • DFS: Stack (either explicit or implicit).
    • BFS: Queue.
  7. Visited Array: Essential for preventing infinite loops by marking nodes once visited.

Summary

Understanding DFS and BFS in terms of how they explore data structures, their implementation in C, and their applications equips you with powerful tools for tackling graph-related problems efficiently. Choosing between them depends on the specific requirements and constraints of your problem.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement C Programming data structures DFS, BFS Traversals

Graph Representation:

We will represent the graph as an array of linked lists to form an adjacency list. Each element in the adjList array corresponds to a vertex (node), and its value points to a linked list of adjacent vertices.

Adjacency List Node Structure:

#include <stdio.h>
#include <stdlib.h>

// Define the structure for each node in the adjacency list
struct AdjListNode {
    int dest;
    struct AdjListNode* next;
};

// Define the structure for the adjacency list
struct AdjList {
    struct AdjListNode* head;
};

// Define the structure for the graph
struct Graph {
    int V;
    struct AdjList* array;
};

DFS Algorithm:

Let's first implement DFS traversal. DFS is an algorithm for traversing or searching tree or graph data structures. One starts at the root (selecting some arbitrary node as the root in the case of a graph) and explores as far as possible along each branch before backtracking.

Functions Required:

  1. createNode: Create a new adjacency list node.
  2. createGraph: Create a graph with the given number of vertices.
  3. addEdge: Add an edge to an undirected graph.
  4. DFSUtil: A recursive function used by DFS.
  5. DFS: Perform DFS traversal from a given source node.
// Function to create a new adjacency list node
struct AdjListNode* createNode(int dest) {
    struct AdjListNode* newNode = (struct AdjListNode*) malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

// Function to create a graph with V vertices
struct Graph* createGraph(int V) {
    struct Graph* graph = (struct Graph*) malloc(sizeof(struct Graph));
    graph->V = V;

    // Create an array of adjacency lists. Size of array will be V
    graph->array = (struct AdjList*) malloc(V * sizeof(struct AdjList));

    // Initialize each adjacency list as empty by making head as NULL
    for (int i = 0; i < V; ++i)
        graph->array[i].head = NULL;

    return graph;
}

// Function to add an edge to an undirected graph
void addEdge(struct Graph* graph, int src, int dest) {
    // Add an edge from src to dest
    struct AdjListNode* newNode = createNode(dest);
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;

    // Since graph is undirected, add an edge from dest to src also
    newNode = createNode(src);
    newNode->next = graph->array[dest].head;
    graph->array[dest].head = newNode;
}

// Recursive function used by DFS
void DFSUtil(struct Graph* graph, int vertex, int visited[]) {
    // Mark the current node as visited and print it
    visited[vertex] = 1;
    printf("Visited %d \n", vertex);

    // Recur for all the vertices adjacent to this vertex
    struct AdjListNode* adjacent = graph->array[vertex].head;
    while (adjacent) {
        int adjVertex = adjacent->dest;
        if (!visited[adjVertex])
            DFSUtil(graph, adjVertex, visited);
        adjacent = adjacent->next;
    }
}

// The function to do DFS traversal. It uses recursive DFSUtil()
void DFS(struct Graph* graph, int startVertex) {
    // Mark all the vertices as not visited
    int* visited = (int*) malloc(graph->V * sizeof(int));
    for(int i = 0; i < graph->V; i++)
        visited[i] = 0;

    // Call the recursive helper function to print DFS traversal
    DFSUtil(graph, startVertex, visited);
}

Example Usage:

int main() {
    int V = 5;

    struct Graph* graph = createGraph(V);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 4);
    addEdge(graph, 1, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 1, 4);
    addEdge(graph, 2, 3);
    addEdge(graph, 3, 4);

    printf("Depth First Traversal starting from vertex 0: \n");
    DFS(graph, 0);

    return 0;
}

Output for DFS:

Depth First Traversal starting from vertex 0: 
Visited 0 
Visited 1 
Visited 2 
Visited 3 
Visited 4 

BFS Algorithm:

BFS explores nodes level by level, starting from the root (or any selected starting node). It uses a queue data structure to achieve this.

Functions Required:

  1. enqueue: Add a new node to the queue.
  2. dequeue: Remove the front node from the queue.
  3. isQueueEmpty: Check if the queue is empty.
  4. BFS: Perform BFS traversal from a given source node.

First, we need to define our queue:

// Queue node for BFS
struct QueueNode {
    int vertex;
    struct QueueNode* next;
};

// Create a queue node
struct QueueNode* createQueueNode(int v) {
    struct QueueNode* newNode = (struct QueueNode*) malloc(sizeof(struct QueueNode));
    newNode->vertex = v;
    newNode->next = NULL;
    return newNode;
}

// Structure for queue implementing in array
struct Queue {
    struct QueueNode* front;
    struct QueueNode* rear;
};

// Create a queue
struct Queue* createQueue() {
    struct Queue* q = (struct Queue*) malloc(sizeof(struct Queue));
    q->front = NULL;
    q->rear = NULL;
    return q;
}

// Enqueue a vertex
void enqueue(struct Queue* q, int v) {
    struct QueueNode* newNode = createQueueNode(v);
    if (q->rear == NULL) {
        q->front = newNode;
        q->rear = newNode;
        return;
    }
    q->rear->next = newNode;
    q->rear      = newNode;
}

// Dequeue a vertex from queue
int dequeue(struct Queue* q) {
    if(q->front == NULL) {
        return -1;  // Indicates underflow
    }
    struct QueueNode* temp = q->front;
    int v = temp->vertex;
    q->front = q->front->next;
    if(q->front == NULL) {
        q->rear = NULL;
    }
    free(temp);
    return v;
}

// Check if the queue is empty
int isQueueEmpty(struct Queue* q) {
    return q->front == NULL;
}

// The function to do BFS traversal from a given source vertex v
void BFS(struct Graph* graph, int startVertex) {
    int* visited = (int*) malloc(graph->V * sizeof(int));
    for (int i = 0; i < graph->V; i++) // Mark all the vertices as not visited
        visited[i] = 0;

    // Create a queue for BFS
    struct Queue* q = createQueue();
    visited[startVertex] = 1;           // Mark the startVertex as visited and enqueue it
    enqueue(q, startVertex);

    while(!isQueueEmpty(q)) {
        // Dequeue a vertex from queue and print it
        int currVertex = dequeue(q);
        printf("Visited %d \n", currVertex);

        // Get all adjacent vertices of the dequeued vertex. If an adjacent
        // has not been visited, mark it visited and enqueue it
        struct AdjListNode* node       = graph->array[currVertex].head;
        while(node) {
            int adjVertex = node->dest;
            if (!visited[adjVertex]) {
                visited[adjVertex] = 1;
                enqueue(q, adjVertex);
            }
            node = node->next;
        }
    }
}

Example Usage:

int main() {
    int V = 5;

    struct Graph* graph = createGraph(V);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 4);
    addEdge(graph, 1, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 1, 4);
    addEdge(graph, 2, 3);
    addEdge(graph, 3, 4);

    printf("Breadth First Traversal starting from vertex 0: \n");
    BFS(graph, 0);

    return 0;
}

Output for BFS:

Breadth First Traversal starting from vertex 0: 
Visited 0 
Visited 1 
Visited 4 
Visited 2 
Visited 3 

Note that the adjacency list representation in the example allows for easy addition and removal of edges but requires additional memory overhead compared to adjacency matrix. Additionally, the output order of the DFS traversal can vary slightly (since it is depth-first, it can depend on the order of the edges in the adjacency list).

Top 10 Interview Questions & Answers on C Programming data structures DFS, BFS Traversals

Top 10 Questions and Answers on C Programming Data Structures: DFS and BFS Traversals

1. What are Depth First Search (DFS) and Breadth First Search (BFS) in the context of graph traversal?

  • DFS starts at a root node (or an arbitrary node in case of a graph) and explores as far as possible along each branch before backtracking.

  • BFS also begins at a root node but explores all neighbors at the present depth prior to moving on to the nodes at the next depth level, effectively traversing the graph level by level.

2. How can DFS be implemented in C?

Answer: DFS can be implemented using either recursion or explicit stacks. Here is a recursive approach:

#include <stdio.h>
#include <stdlib.h>

// Adjacency list node
struct AdjListNode {
    int dest;
    struct AdjListNode *next;
};

// Adjacency list
struct AdjList {
    struct AdjListNode *head;
};

// Graph
struct Graph {
    int V;
    struct AdjList *array;
};

struct AdjListNode* newAdjListNode(int dest) {
    struct AdjListNode *newNode = (struct AdjListNode *)malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

struct Graph* createGraph(int V) {
    struct Graph *graph = (struct Graph *)malloc(sizeof(struct Graph));
    graph->V = V;
    graph->array = (struct AdjList *)malloc(V * sizeof(struct AdjList));

    for (int i = 0; i < V; i++)
        graph->array[i].head = NULL;

    return graph;
}

void addEdge(struct Graph *graph, int src, int dest) {
    struct AdjListNode *newNode = newAdjListNode(dest);
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;

    newNode = newAdjListNode(src);
    newNode->next = graph->array[dest].head;
    graph->array[dest].head = newNode;
}

void DFSUtil(struct Graph *graph, int v, int visited[]) {
    printf("%d ", v);
    visited[v] = 1;

    struct AdjListNode *adjNode = graph->array[v].head;
    while (adjNode) {
        if (!visited[adjNode->dest])
            DFSUtil(graph, adjNode->dest, visited);
        adjNode = adjNode->next;
    }
}

void DFS(struct Graph *graph, int v) {
    int *visited = (int *)malloc(graph->V * sizeof(int));
    for (int i = 0; i < graph->V; i++)
        visited[i] = 0;

    DFSUtil(graph, v, visited);
}

int main() {
    int V = 5;
    struct Graph *graph = createGraph(V);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 4);
    addEdge(graph, 1, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 1, 4);
    addEdge(graph, 2, 3);
    addEdge(graph, 3, 4);

    printf("Depth First Traversal starting from vertex 0: \n");
    DFS(graph, 0);

    return 0;
}

3. How does the BFS algorithm work in C?

Answer: BFS can be implemented using a queue data structure:

#include <stdio.h>
#include <stdlib.h>
#define MAX 5

struct Queue {
    int items[MAX];
    int front;
    int rear;
};

struct Queue* createQueue() {
    struct Queue *queue = (struct Queue *)malloc(sizeof(struct Queue));
    queue->front = -1;
    queue->rear = -1;
    return queue;
}

int isEmpty(struct Queue *queue) {
    return queue->rear == -1;
}

void enqueue(struct Queue *queue, int value) {
    if (queue->rear == MAX - 1)
        printf("\nQueue is Full!!\n");
    else {
        if (queue->front == -1)
            queue->front = 0;
        queue->rear++;
        queue->items[queue->rear] = value;
    }
}

int dequeue(struct Queue *queue) {
    int item;
    if (isEmpty(queue)) {
        printf("Queue is empty");
        item = -1;
    } else {
        item = queue->items[queue->front];
        queue->front++;
        if (queue->front > queue->rear) {
            queue->front = queue->rear = -1;
        }
    }
    return item;
}

struct Node {
    int vertex;
    struct Node *next;
};

struct Node* createNode(int v) {
    struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
    newNode->vertex = v;
    newNode->next = NULL;
    return newNode;
}

struct Graph {
    int numVertices;
    int *visited;
    struct Node **adjLists;
};

struct Graph* createGraph(int vertices) {
    struct Graph *graph = (struct Graph *)malloc(sizeof(struct Graph));
    graph->numVertices = vertices;

    graph->adjLists = (struct Node **)malloc(vertices * sizeof(struct Node *));
    graph->visited = (int *)malloc(vertices * sizeof(int));

    for (int i = 0; i < vertices; ++i) {
        graph->adjLists[i] = NULL;
        graph->visited[i] = 0;
    }

    return graph;
}

void addEdge(struct Graph *graph, int src, int dest) {
    struct Node *newNode = createNode(dest);
    newNode->next = graph->adjLists[src];
    graph->adjLists[src] = newNode;

    newNode = createNode(src);
    newNode->next = graph->adjLists[dest];
    graph->adjLists[dest] = newNode;
}

void bfs(struct Graph *graph, int startVertex) {
    struct Queue *queue = createQueue();

    graph->visited[startVertex] = 1;
    enqueue(queue, startVertex);

    int currentVertex;
    struct Node *temp;

    while (!isEmpty(queue)) {
        currentVertex = dequeue(queue);
        printf("%d ", currentVertex);

        temp = graph->adjLists[currentVertex];

        while (temp != NULL) {
            int adjVertex = temp->vertex;

            if (graph->visited[adjVertex] == 0) {
                graph->visited[adjVertex] = 1;
                enqueue(queue, adjVertex);
            }
            temp = temp->next;
        }
    }
}

int main() {
    struct Graph *graph = createGraph(6);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 2);
    addEdge(graph, 1, 2);
    addEdge(graph, 1, 4);
    addEdge(graph, 1, 3);
    addEdge(graph, 2, 4);
    addEdge(graph, 3, 4);

    bfs(graph, 0);

    return 0;
}

4. Explain the difference between stack-based DFS and recursive DFS.

Answer: Both approaches are valid for implementing DFS, but they have slight differences:

  • Recursive DFS uses the call stack of the program to keep track of the vertices to be explored. It is simpler to implement and understand but can suffer from stack overflow for large graphs due to deep recursion.

  • Stack-based DFS explicitly utilizes a stack data structure to manage the vertices. This approach avoids the risk of stack overflow and is more memory-efficient for deep searches but can be more complex to implement.

5. How does the implementation of BFS differ from DFS in terms of data structures?

Answer: BFS uses a queue to remember which nodes are next to explore, whereas DFS uses a stack (implicitly through recursion or explicitly through a stack data structure).

  • The queue operates on a FIFO (First In First Out) principle, which is essential for exploring the neighbors level by level.

  • The stack operates on a LIFO (Last In First Out) principle, useful for exploring as deep as possible into branches of a graph.

6. When should you use DFS over BFS?

Answer:

  • Use DFS when you want to visit a single path deeply, such as in finding paths in a maze (backtracking), determining connectivity, or for topological sorting.

  • Use BFS when you need the shortest path in an unweighted graph, checking if a graph is bipartite, detecting cycles, or performing level-wise tasks.

7. Can DFS and BFS be used with both directed and undirected graphs?

Answer: Yes, both DFS and BFS can be used to traverse directed and undirected graphs. The algorithms themselves do not differentiate between them, but care must be taken in the adjacency list to correctly represent edges.

8. What is time and space complexity of DFS and BFS?

Answer: For both DFS and BFS:

  • Time Complexity: O(V + E), where V is the number of vertices and E is the number of edges.

  • Space Complexity: O(V) because in the worst case, we need to store all vertices in the visited array or auxiliary data structure (stack/queue).

9. Can a graph contain multiple DFS and BFS traversals?

Answer: Yes, a graph can have different DFS and BFS traversals depending on the starting vertex and the order in which neighbors are visited. The result of these traversals will indeed vary based on these factors.

10. Which traversal method, DFS or BFS, is better for detecting cycles in undirected graphs?

Answer: Both DFS and BFS can detect cycles in undirected graphs, but each has its own approach:

  • DFS Approach: A cycle is detected if a node is visited again that is already in the recursion stack.

  • BFS Approach: A cycle is found if two nodes at the same level point to each other, excluding the parent node.

You May Like This Related .NET Topic

Login to post a comment.