C Programming data structures Detecting Cycles in Graphs Step by step Implementation and Top 10 Questions and Answers
 .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    Last Update: April 01, 2025      16 mins read      Difficulty-Level: beginner

Detecting Cycles in Graphs Using Data Structures in C

Introduction

Graphs are a fundamental data structure used to represent networks of items or entities, where the items or entities are represented as vertices (or nodes), and the connections between them are represented as edges. Detecting cycles in graphs is an important problem with numerous applications, such as deadlock detection, garbage collection, compiling language parsers, and analyzing network packets. In this article, we will explore how to detect cycles in graphs using various data structures and algorithms implemented in C.

Representation of Graph

Before diving into cycle detection, it's crucial to understand how graphs can be represented. There are two primary ways to represent graphs:

  1. Adjacency Matrix: An N x N matrix where each element indicates whether there is an edge between the nodes.

    int adjMatrix[N][N];
    
  2. Adjacency List: An array of linked lists where each index points to a list of corresponding connected vertices.

    typedef struct listNode {
        int vertex;
        struct listNode* next;
    } ListNode;
    
    typedef struct adjacencyList {
        ListNode* head;
    } AdjList;
    
    typedef struct graph {
        int numVertices;
        AdjList* lists;
        int* visited;
    } Graph;
    

Cycle Detection Algorithms

The most common algorithms for detecting cycles in graphs are Depth-First Search (DFS) and Disjoint Set Union (DSU) with Union-Find.

Depth-First Search (DFS)

DFS is generally used to detect cycles in directed graphs. Here's the approach:

  1. Create a recursive DFS function that traverses the graph.
  2. Maintain a visited array to keep track of visited nodes.
  3. Use a recStack (recursion stack) to keep track of nodes currently in the recursion stack.
  4. Mark the current node as visited and push it to the recStack.
  5. If an adjacent vertex to the current node is already in the recStack, then there is a cycle.
  6. If no cycles are found in all nodes using DFS, then the graph is acyclic.
int isCyclicUtil(int v, int visited[], int recStack[], Graph* graph) {
    if(visited[v] == 0) {  // Mark the current node as visited and part of recursion stack
        visited[v] = 1;
        recStack[v] = 1;

        // Recur for all the vertices adjacent to this vertex
        ListNode* listNode = graph->lists[v].head;
        while(listNode) {
            int adjVertex = listNode->vertex;
            if ( !visited[adjVertex] && isCyclicUtil(adjVertex, visited, recStack, graph) )
                return 1;
            else if (recStack[adjVertex])
                return 1;
            listNode = listNode->next;
        }
    }

    recStack[v] = 0;  // Remove the vertex from recursion stack
    return 0;
}

int isCyclic(Graph* graph) {
    int* visited = (int*)malloc(graph->numVertices * sizeof(int));
    int* recStack = (int*)malloc(graph->numVertices * sizeof(int));

    for(int i = 0; i < graph->numVertices; i++) {
        visited[i] = recStack[i] = 0;
    }

    for(int i = 0; i < graph->numVertices; i++)
        if ( !visited[i] && isCyclicUtil(i, visited, recStack, graph))
            return 1;

    free(visited);
    free(recStack);
    return 0;
}
Disjoint Set Union (DSU) with Union-Find for Undirected Graphs

For undirected graphs, Union-Find is a suitable method to detect cycles using DSU:

  1. Use the Union-Find algorithm to unite the vertices.
  2. If two vertices are part of the same set before uniting, then merging them will create a cycle.

Here's how you could implement it in C:

typedef struct subset {
    int parent;
    int rank;
} Subset;

int find(Subset subsets[], int i) { // Recursive path compression
    if (subsets[i].parent != i)
        subsets[i].parent = find(subsets, subsets[i].parent);
    return subsets[i].parent;
}

void Union(Subset subsets[], int x, int y) {
    int xset = find(subsets, x);
    int yset = find(subsets, y);

    if (xset == yset) {
        printf("Cycle detected\n");
    } else {
        if (subsets[xset].rank < subsets[yset].rank)
            subsets[xset].parent = yset;
        else if (subsets[xset].rank > subsets[yset].rank)
            subsets[yset].parent = xset;
        else {
            subsets[yset].parent = xset;
            subsets[xset].rank++;
        }
    }
}

int isCyclicUndirected(Graph* graph) {
    Subset* subsets = (Subset*)malloc(graph->numVertices * sizeof(Subset));

    for (int v = 0; v < graph->numVertices; v++) {
        subsets[v].parent = v;
        subsets[v].rank = 0;
    }

    // Iterate through all edges of graph, find sets of both vertices of every edge.
    // If sets are the same, then there is cycle in graph.
    for (int i = 0; i < graph->numVertices; i++) {
        ListNode* pCrawl = graph->lists[i].head;
        while (pCrawl) {
            if (find(subsets, i) == find(subsets, pCrawl->vertex)) {
                return 1;
            }
            Union(subsets, i, pCrawl->vertex);
            pCrawl = pCrawl->next;
        }
    }

    free(subsets);
    return 0;
}

Conclusion

Determining whether a graph contains a cycle is a fundamental problem in computer science. The choice of algorithm depends on the type of graph (directed or undirected). For directed graphs, DFS is preferred due to its simplicity, while DSU/Union-Find technique is more efficient for undirected graphs. Both methods are vital tools in many practical applications dealing with graph structures, making the understanding of these concepts invaluable for any programmer.

By implementing these algorithms in C, one gets a deeper insight into the inner workings of these data structures and algorithms, which can further help in optimizing code and designing new solutions.

Examples with Frontend, Backend, Data Flow, and Application Running Step-by-Step for Beginners

Topic: C Programming and Data Structures - Detecting Cycles in Graphs

Detecting cycles in graphs is a common problem in computer science, and it can be efficiently solved using depth-first search (DFS). In this tutorial, we'll create a simple C application that detects cycles in an undirected graph using DFS. We'll also build a basic web interface as a frontend to interact with this application, and set up a backend server to handle requests from the frontend. This example is meant to give you a good sense of how these components can work together.

Prerequisites

  1. Basic Knowledge of C Programming.
  2. Understanding of Graph Data Structures.
  3. Familiarity with Web Development: HTML, CSS, JavaScript.
  4. Experience with a Server-Side Language: Node.js, Python, PHP, etc.
  5. Command Line Interface for Backend Commands.

Part 1: Backend (C Program)

Step 1: Define Graph Structure

A graph can be represented using an adjacency list. Let’s define a graph structure with the necessary fields:

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

// Define a linked list node
typedef struct AdjListNode {
    int dest;
    struct AdjListNode* next;
} AdjListNode;

// Define a linked list 
typedef struct AdjList {
    AdjListNode *head;
} AdjList;

// Define a Graph
typedef struct Graph {
    int V;   // Number of vertices
    AdjList* array;
} Graph;

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

// Creates a graph of V vertices
Graph* createGraph(int V) {
    Graph* graph = (Graph*) malloc(sizeof(Graph));
    graph->V = V;
    // Create an array of adjacency lists. Size of array will be V
    graph->array = (AdjList*) malloc(V * sizeof(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(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.  
    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;
}

Step 2: Implement Cycle Detection Using DFS

Here, we implement a helper function for DFS and main cycle detection logic.

// A utility function to do DFS on a graph. It uses recursion based approach
bool isCyclicUtil(int v, bool visited[], bool recStack[], Graph* graph) {
    if (!visited[v]) {
        // Mark the current node as visited and part of recursion stack
        visited[v] = true;
        recStack[v] = true;

        // Recur for all the vertices adjacent to this vertex
        AdjListNode* temp = graph->array[v].head;
        while (temp) {
            int adj_vertex = temp->dest;
            if (!visited[adj_vertex] && isCyclicUtil(adj_vertex, visited, recStack, graph))
                return true;
            else if (recStack[adj_vertex])
                return true;
            temp = temp->next;
        }
    }
    recStack[v] = false;
    return false;
}

// Returns true if the graph contains a cycle, else false. 
bool isCyclic(Graph* graph) {
    // Mark all the vertices as not visited and not part of recursion stack
    bool* visited = (bool*) malloc(graph->V * sizeof(bool));
    bool* recStack = (bool*) malloc(graph->V * sizeof(bool));

    for (int i = 0; i < graph->V; i++) {
        visited[i] = false;
        recStack[i] = false;
    }

    // Call the recursive helper function to detect cycle in different DFS trees
    for (int i = 0; i < graph->V; i++)
        if (!visited[i])
            if (isCyclicUtil(i, visited, recStack, graph))
                return true;

    free(visited);
    free(recStack);
    return false;
}

// Utility to print graph
void printGraph(Graph* graph) {
    for (int v = 0; v < graph->V; ++v) {
        AdjListNode* pCrawl = graph->array[v].head;
        printf("\n Adjacency list of vertex %d\n head ", v);
        while (pCrawl) {
            printf("-> %d", pCrawl->dest);
            pCrawl = pCrawl->next;
        }
        printf("\n");
    }
}

// Main function to demonstrate graph creation and cycle check
int main() {
    int V = 4;
    Graph* graph = createGraph(V);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 2);
    addEdge(graph, 1, 2);
    addEdge(graph, 2, 3);

    printGraph(graph);
    
    if(isCyclic(graph)) 
        printf("Graph contains cycle\n"); 
    else
        printf("Graph doesn't contain cycle\n");

    free(graph->array);
    free(graph);

    return 0;
}

To compile and run the program, use these commands:

gcc -o cycle_detection cycle_detection.c
./cycle_detection

This would print the adjacency list and whether the graph has a cycle or not.

Part 2: Backend (Server-Side Code)

Since we are interacting with the frontend via the web, let's set up a basic HTTP server using JavaScript's http module or any other server-side language of your choice. We'll use Node.js here:

Step A: Setting Up Node Server

First, install Node.js then create a project by executing the command below in terminal.

mkdir cycle_detection_web_project
cd cycle_detection_web_project
npm init -y

Next, install Express package, a minimal and flexible Node.js web application framework.

npm install express --save

Step B: Creating Server and Endpoint

Create a JavaScript file (app.js) for your Node server, and write this code:

const express = require('express');
const { execFile } = require('child_process');

const app = express();
const port = 3000;

app.use(express.json());

// Endpoint to run cycle detection
app.post('/detect_cycle', (req, res) => {
    const { vertices, edges } = req.body;
    const args = [vertices, edges];

    execFile('./cycle_detection', args, (error, stdout, stderr) => {
        if (error) {
            console.error(`Cycle detection failed with error: ${stderr}`);
            return res.status(500).send({ message: 'Internal Server Error' });
        }

        res.send({ message: stdout.trim() });
    });
});

app.listen(port, () => {
    console.log(`Cycle Detection Web Project running at http://localhost:${port}/`);
});

Now, we need to compile the C code again making sure to use arguments passed from the server. Modify main() in cycle_detection.c:

int main(int argc, char const *argv[]) {
    if(argc != 3){
        printf("Invalid number of parameters passed\n");
        return 1;
    }

    int V = atoi(argv[1]);
    int E = atoi(argv[2]);
    Graph* graph = createGraph(V);

    // Here, you would read edges from request body and addEdges
    // But for simplicity, let’s consider some hard-coded values here,
    // Replace this by dynamically reading edges sent by web server.
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 2);
    addEdge(graph, 1, 2);
    addEdge(graph, 2, 3);

    if(isCyclic(graph)) 
        printf("Graph contains cycle\n");
    else
        printf("Graph doesn't contain cycle\n");

    free(graph->array);
    free(graph);

    return 0;
}

Compile the C code using gcc:

gcc -o cycle_detection cycle_detection.c

Start the server:

node app.js

Part 3: Frontend (Web Interface)

We’ll use HTML and JavaScript to make an interactive interface that allows input of vertices and edges as well as sends them to the backend for cycle detection.

File A: index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cycle Detection</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        .container { max-width: 500px; margin: auto; }
        input { width: 100%; padding: 8px; margin-top: 8px; margin-bottom: 8px; }
        button { padding: 10px 20px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #45a049; }
       textarea { width: 100%; padding: 8px; height: 100px; }
        p { font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <h2>Detect Cycle in Graph</h2>
        
        <input type="number" id="vertices" placeholder="Enter number of vertices...">
        <textarea id="edges" placeholder="Enter edges in format 'source destination' with each line as one edge"></textarea>
        
        <button onclick="detectCycle()">Detect Cycle</button>
        
        <p id="result"></p>
    </div>

    <script>
        async function detectCycle() {
            const verticesInput = document.getElementById('vertices').value;
            const edgesInput = document.getElementById('edges').value;

            const edgesArray = [];
            for(const edgeLine of edgesInput.split('\n')){
                const [src, dest] = edgeLine.trim().split(' ').map(Number);
                edgesArray.push(src, dest);
            }

            const dataToSend = JSON.stringify({
                vertices: verticesInput,
                edges: edgesArray.join(',')
            });

            try{
                const response = await fetch('http://localhost:3000/detect_cycle', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: dataToSend
                });

                const result = await response.json();
                document.getElementById('result').innerText = result.message || 'No response';
            }
            catch(error){
                document.getElementById('result').innerText = `Error: ${error}`;
            }
        }
    </script>
</body>
</html>

Part 4: Connecting the Frontend and Backend

You've already made your C backend executable (cycle_detection). This server expects two pieces of information in the POST request: the number of vertices and edges. For our frontend, the user inputs these into corresponding textboxes and clicks the "Detect Cycle" button, which triggers the detectCycle() JavaScript function.

Part 5: Running the Application

  1. Open three terminal sessions.

  2. In the first session, navigate to your C source code directory. Run C application by using gcc and creating executable:

    gcc -o cycle_detection cycle_detection.c
    ./cycle_detection
    

    Make sure it’s running as expected.

  3. Start the Node.js Server in the second terminal:

    node app.js
    

    Ensure the server is listening on http://localhost:3000.

  4. In the third session, start a simple HTTP server to serve the frontend files. You can use any available method (Python's Simple HTTP Server, Node's http static file service etc.). With Python3, you can do it like this:

    python3 -m http.server 8080
    
  5. Open your browser and navigate to http://localhost:8080/index.html. You'll find the frontend where you can input vertices, edges, and check for cycles.

  6. Example Input:

    • Vertices: 4
    • Edges: 0 1, 0 2, 1 2, 2 3
  7. Click "Detect Cycle", and you should see the output showing if your graph contains a cycle.

Conclusion

In this example, you walked through an end-to-end flow starting from implementing a graph data structure and a cycle detection algorithm in C, setting up a basic HTTP server to interact with this C application, and finally creating a web frontend to input graph details and receive results. The key takeaway is not just to learn about cycle detection in graphs, but also understanding how various components in a web application might interact with each other.