Graph Algorithms in C++: A Comprehensive Guide

Graph Algorithms in C++

Circular, Doubly, and Doubly Circular Linked Lists

Let’s explore various linked list structures and their C++ implementations:

Node Class

The foundation of our linked lists is the Node class, representing individual elements:

#include <iostream>
using namespace std;

// Node class represents each element in the linked lists
class Node {
public:
    int data;
    Node* prev;
    Node* next;

    Node(int data) {
        this->data = data;
        this->prev = nullptr;
        this->next = nullptr;
    }
};

Circular Linked List

In a circular linked list, the last node points back to the head, creating a continuous loop:

// CircularLinkedList class represents the circular linked list
class CircularLinkedList {
private:
    Node* head;

public:
    CircularLinkedList() { head = nullptr; }

    // Appends a new node with the given data to the end of the list
    void append(int data) {
        Node* newNode = new Node(data);
        if (head == nullptr) {
            head = newNode;
            newNode->next = head;
        } else {
            Node* current = head;
            while (current->next != head) {
                current = current->next;
            }
            current->next = newNode;
            newNode->next = head;
        }
    }

    // Prints the elements of the list starting from the head
    void printList() {
        if (head == nullptr) {
            cout << "List is empty" << endl;
            return;
        }
        Node* current = head;
        do {
            cout << current->data << " -> ";
            current = current->next;
        } while (current != head);
        cout << " (head)" << endl;
    }
};

Doubly Linked List

Doubly linked lists provide bi-directional traversal with both prev and next pointers:

// DoublyLinkedList class represents the doubly linked list
class DoublyLinkedList {
private:
    Node* head;
    Node* tail;

public:
    DoublyLinkedList() { head = nullptr; tail = nullptr; }

    // Appends a new node with the given data to the end of the list
    void append(int data) {
        Node* newNode = new Node(data);
        if (head == nullptr) {
            head = newNode;
            tail = newNode;
        } else {
            tail->next = newNode;
            newNode->prev = tail;
            tail = newNode;
        }
    }

    // Prints the elements of the list starting from the head
    void printList() {
        Node* current = head;
        while (current != nullptr) {
            cout << current->data << " <-> ";
            current = current->next;
        }
        cout << "NULL" << endl;
    }
};

Doubly Circular Linked List

Combining the features of doubly linked lists and circular lists, we get a doubly circular linked list:

// DoublyCircularLinkedList class represents the doubly circular linked list
class DoublyCircularLinkedList {
private:
    Node* head;

public:
    DoublyCircularLinkedList() { head = nullptr; }

    // Appends a new node with the given data to the end of the list
    void append(int data) {
        Node* newNode = new Node(data);
        if (head == nullptr) {
            head = newNode;
            newNode->next = head;
            newNode->prev = head;
        } else {
            Node* tail = head->prev;
            tail->next = newNode;
            newNode->prev = tail;
            newNode->next = head;
            head->prev = newNode;
        }
    }

    // Prints the elements of the list starting from the head
    void printList() {
        if (head == nullptr) {
            cout << "List is empty" << endl;
            return;
        }
        Node* current = head;
        do {
            cout << current->data << " <-> ";
            current = current->next;
        } while (current != head);
        cout << "(head)" << endl;
    }
};

Depth First Search (DFS) and Topological Sorting

#include <bits/stdc++.h>
using namespace std;

// Function to perform DFS and topological sorting
void topologicalSortUtil(int v, vector<vector<int> >& adj, vector<bool>& visited, stack<int>& Stack) {
    // Mark the current node as visited
    visited[v] = true;

    // Recur for all adjacent vertices
    for (int i : adj[v]) {
        if (!visited[i])
            topologicalSortUtil(i, adj, visited, Stack);
    }

    // Push current vertex to stack which stores the result
    Stack.push(v);
}

// Function to perform Topological Sort
void topologicalSort(vector<vector<int> >& adj, int V) {
    stack<int> Stack;
    // Stack to store the result

    vector<bool> visited(V, false);

    // Call the recursive helper function to store
    // Topological Sort starting from all vertices one by
    // one
    for (int i = 0; i < V; i++) {
        if (!visited[i])
            topologicalSortUtil(i, adj, visited, Stack);
    }

    // Print contents of stack
    cout << "Topological sorting of the graph: ";
    while (!Stack.empty()) {
        cout << Stack.top() << " ";
        Stack.pop();
    }
    cout << endl;
}

int main() {
    // Number of nodes
    int V = 4;

    // Edges
    vector<vector<int> > edges = {{0, 1}, {1, 2}, {3, 1}, {3, 2}};

    // Graph represented as an adjacency list
    vector<vector<int> > adj(V);
    for (auto i : edges) {
        adj[i[0]].push_back(i[1]);
    }

    topologicalSort(adj, V);
    return 0;
}

Disjoint Set Data Structure

// C++ implementation of disjoint set
#include <bits/stdc++.h>
using namespace std;

class DisjSet {
    int *rank, *parent, n;

public:
    // Constructor to create and
    // initialize sets of n items
    DisjSet(int n)
    {
        rank = new int[n];
        parent = new int[n];
        this->n = n;
        makeSet();
    }

    // Creates n single item sets
    void makeSet()
    {
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    // Finds set of given item x
    int find(int x)
    {
        // Finds the representative of the set
        // that x is an element of
        if (parent[x] != x) {
            // if x is not the parent of itself
            // Then x is not the representative of
            // his set,
            parent[x] = find(parent[x]);
            // so we recursively call Find on its parent
            // and move i's node directly under the
            // representative of this set
        }
        return parent[x];
    }

    // Do union of two sets by rank represented
    // by x and y.
    void Union(int x, int y)
    {
        // Find current sets of x and y
        int xset = find(x);
        int yset = find(y);

        // If they are already in same set
        if (xset == yset)
            return;

        // Put smaller ranked item under
        // bigger ranked item if ranks are
        // different
        if (rank[xset] < rank[yset]) {
            parent[xset] = yset;
        }
        else if (rank[xset] > rank[yset]) {
            parent[yset] = xset;
        }

        // If ranks are same, then increment
        // rank.
        else {
            parent[yset] = xset;
            rank[xset] = rank[xset] + 1;
        }
    }
};

// Driver Code
int main()
{
    // Function Call
    DisjSet obj(5);
    obj.Union(0, 2);
    obj.Union(4, 2);
    obj.Union(3, 1);
    if (obj.find(4) == obj.find(0))
        cout << "Yes\n";
    else
        cout << "No\n";
    if (obj.find(1) == obj.find(0))
        cout << "Yes\n";
    else
        cout << "No\n";
    return 0;
}

Bellman-Ford Algorithm

// A C++ program for Bellman-Ford's single source
// shortest path algorithm.
#include <bits/stdc++.h>
using namespace std;

// a structure to represent a weighted edge in graph
struct Edge {
    int src, dest, weight;
};

// a structure to represent a connected, directed and
// weighted graph
struct Graph {
    // V-> Number of vertices, E-> Number of edges
    int V, E;

    // graph is represented as an array of edges.
    struct Edge* edge;
};

// Creates a graph with V vertices and E edges
struct Graph* createGraph(int V, int E)
{
    struct Graph* graph = new Graph;
    graph->V = V;
    graph->E = E;
    graph->edge = new Edge[E];
    return graph;
}

// A utility function used to print the solution
void printArr(int dist[], int n)
{
    printf("Vertex Distance from Source\n");
    for (int i = 0; i < n; ++i)
        printf("%d \t\t %d\n", i, dist[i]);
}

// The main function that finds shortest distances from src
// to all other vertices using Bellman-Ford algorithm. The
// function also detects negative weight cycle
void BellmanFord(struct Graph* graph, int src)
{
    int V = graph->V;
    int E = graph->E;
    int dist[V];

    // Step 1: Initialize distances from src to all other
    // vertices as INFINITE
    for (int i = 0; i < V; i++)
        dist[i] = INT_MAX;
    dist[src] = 0;

    // Step 2: Relax all edges |V| - 1 times. A simple
    // shortest path from src to any other vertex can have
    // at-most |V| - 1 edges
    for (int i = 1; i <= V - 1; i++) {
        for (int j = 0; j < E; j++) {
            int u = graph->edge[j].src;
            int v = graph->edge[j].dest;
            int weight = graph->edge[j].weight;
            if (dist[u] != INT_MAX && dist[u] + weight < dist[v])
                dist[v] = dist[u] + weight;
        }
    }

    // Step 3: check for negative-weight cycles. The above
    // step guarantees shortest distances if graph doesn't
    // contain negative weight cycle. If we get a shorter
    // path, then there is a cycle.
    for (int i = 0; i < E; i++) {
        int u = graph->edge[i].src;
        int v = graph->edge[i].dest;
        int weight = graph->edge[i].weight;
        if (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {
            printf("Graph contains negative weight cycle");
            return; // If negative cycle is detected, simply
                   // return
        }
    }

    printArr(dist, V);
    return;
}

// Driver's code
int main()
{
    /* Let us create the graph given in above example */
    int V = 5; // Number of vertices in graph
    int E = 8; // Number of edges in graph
    struct Graph* graph = createGraph(V, E);

    // add edge 0-1 (or A-B in above figure)
    graph->edge[0].src = 0;
    graph->edge[0].dest = 1;
    graph->edge[0].weight = -1;

    // add edge 0-2 (or A-C in above figure)
    graph->edge[1].src = 0;
    graph->edge[1].dest = 2;
    graph->edge[1].weight = 4;

    // add edge 1-2 (or B-C in above figure)
    graph->edge[2].src = 1;
    graph->edge[2].dest = 2;
    graph->edge[2].weight = 3;

    // add edge 1-3 (or B-D in above figure)
    graph->edge[3].src = 1;
    graph->edge[3].dest = 3;
    graph->edge[3].weight = 2;

    // add edge 1-4 (or B-E in above figure)
    graph->edge[4].src = 1;
    graph->edge[4].dest = 4;
    graph->edge[4].weight = 2;

    // add edge 3-2 (or D-C in above figure)
    graph->edge[5].src = 3;
    graph->edge[5].dest = 2;
    graph->edge[5].weight = 5;

    // add edge 3-1 (or D-B in above figure)
    graph->edge[6].src = 3;
    graph->edge[6].dest = 1;
    graph->edge[6].weight = 1;

    // add edge 4-3 (or E-D in above figure)
    graph->edge[7].src = 4;
    graph->edge[7].dest = 3;
    graph->edge[7].weight = -3;

    // Function call
    BellmanFord(graph, 0);
    return 0;
}

Union Find

class UnionFind {
public:
    int * root, * size;

    UnionFind(int NumElements) {
        root = new int[NumElements];
        size = new int[NumElements];
        for (int i = 0; i < NumElements; i++) {
            root[i] = i;
            size[i] = 1;
        }
    }

    void Union(int left, int right) {
        int l = find(left), r = find(right);
        if (size[l] <= size[r]) {
            root[l] = r;
            size[r] += size[l];
        } else {
            root[r] = l;
            size[l] += size[r];
        }
    }

    int find(int element) {
        while (element != root[element]) {
            element = root[element];
        }
        return element;
    }
};

Iterative Binary Search

// C++ program to implement iterative Binary Search
#include <bits/stdc++.h>
using namespace std;

// An iterative binary search function.
int binarySearch(int arr[], int l, int r, int x)
{
    while (l <= r) {
        int m = l + (r - l) / 2;

        // Check if x is present at mid
        if (arr[m] == x)
            return m;

        // If x greater, ignore left half
        if (arr[m] < x)
            l = m + 1;

        // If x is smaller, ignore right half
        else
            r = m - 1;
    }

    // If we reach here, then element was not present
    return -1;
}

// Driver code
int main(void)
{
    int arr[] = {2, 3, 4, 10, 40};
    int x = 10;
    int n = sizeof(arr) / sizeof(arr[0]);
    int result = binarySearch(arr, 0, n - 1, x);
    (result == -1) ? cout << "Element is not present in array" : cout << "Element is present at index " << result;
    return 0;
}

Maximum Subarray Sum

// C++ program to print largest contiguous array sum
#include <bits/stdc++.h>
using namespace std;

int maxSubArraySum(int a[], int size)
{
    int max_so_far = INT_MIN, max_ending_here = 0;
    for (int i = 0; i < size; i++) {
        max_ending_here = max_ending_here + a[i];
        if (max_so_far < max_ending_here)
            max_so_far = max_ending_here;
        if (max_ending_here < 0)
            max_ending_here = 0;
    }
    return max_so_far;
}

// Driver Code
int main()
{
    int a[] = {-2, -3, 4, -1, -2, 1, 5, -3};
    int n = sizeof(a) / sizeof(a[0]);
    // Function Call
    int max_sum = maxSubArraySum(a, n);
    cout << "Maximum contiguous sum is " << max_sum;
    return 0;
}

Heap Sort

// C++ program for implementation of Heap Sort
#include <iostream>
using namespace std;

// To heapify a subtree rooted with node i
// which is an index in arr[].
// n is size of heap
void heapify(int arr[], int N, int i)
{
    // Initialize largest as root
    int largest = i;
    // left = 2*i + 1
    int l = 2 * i + 1;
    // right = 2*i + 2
    int r = 2 * i + 2;

    // If left child is larger than root
    if (l < N && arr[l] > arr[largest])
        largest = l;

    // If right child is larger than largest
    // so far
    if (r < N && arr[r] > arr[largest])
        largest = r;

    // If largest is not root
    if (largest != i) {
        swap(arr[i], arr[largest]);

        // Recursively heapify the affected
        // sub-tree
        heapify(arr, N, largest);
    }
}

// Main function to do heap sort
void heapSort(int arr[], int N)
{
    // Build heap (rearrange array)
    for (int i = N / 2 - 1; i >= 0; i--)
        heapify(arr, N, i);

    // One by one extract an element
    // from heap
    for (int i = N - 1; i > 0; i--) {
        // Move current root to end
        swap(arr[0], arr[i]);

        // call max heapify on the reduced heap
        heapify(arr, i, 0);
    }
}

// A utility function to print array of size n
void printArray(int arr[], int N)
{
    for (int i = 0; i < N; ++i)
        cout << arr[i] << " ";
    cout << "\n";
}

// Driver's code
int main()
{
    int arr[] = {12, 11, 13, 5, 6, 7};
    int N = sizeof(arr) / sizeof(arr[0]);

    // Function call
    heapSort(arr, N);
    cout << "Sorted array is \n";
    printArray(arr, N);
}

Merge Sort

#include using namespace std; // Merges two subarrays of array[]. // First subarray is arr[begin..mid] // Second subarray is arr[mid+1..end] void merge(int array[], int const left, int const mid, int const right) { auto const subArrayOne = mid – left + 1; auto const subArrayTwo = right – mid; // Create temp arrays auto *leftArray = new int[subArrayOne], *rightArray = new int[subArrayTwo]; // Copy data to temp arrays leftArray[] // and rightArray[] for (auto i = 0; i = end) return; auto mid = begin + (end – begin) / 2; mergeSort(array, begin, mid); mergeSort(array, mid + 1, end); merge(array, begin, mid, end); } // UTILITY FUNCTIONS // Function to print an array void printArray(int A[], int size) { for (auto i = 0; i

// A C++ program for Prim’s Minimum // Spanning Tree (MST) algorithm. The program is // for adjacency matrix representation of the graph #include using namespace std; // Number of vertices in the graph #define V 5 // A utility function to find the vertex with // minimum key value, from the set of vertices // not yet included in MST int minKey(int key[], bool mstSet[]) { // Initialize min value int min = INT_MAX, min_index; for (int v = 0; v

// C++ program for the above approach #include using namespace std; // DSU data structure // path compression + rank by union class DSU { int* parent; int* rank; public: DSU(int n) { parent = new int[n]; rank = new int[n]; for (int i = 0; i rank[s2]) { parent[s2] = s1; } else { parent[s2] = s1; rank[s1] += 1; } } } }; class Graph { vector > edgelist; int V; public: Graph(int V) { this->V = V; } // Function to add edge in a graph void addEdge(int x, int y, int w) { edgelist.push_back({ w, x, y }); } void kruskals_mst() { // Sort all edges sort(edgelist.begin(), edgelist.end()); // Initialize the DSU DSU s(V); int ans = 0; cout

// Boruvka’s algorithm to find Minimum Spanning // Tree of a given connected, undirected and weighted graph #include using namespace std; // Class to represent a graph class Graph { int V; // No. of vertices vector >graph; // default dictionary to store graph // A utility function to find set of an element i // (uses path compression technique) int find(vector& parent, int i) { if (parent[i] == i) { return i; } return find(parent, parent[i]); } // A function that does union of two sets of x and y // (uses union by rank) void unionSet(vector& parent, vector& rank, int x, int y) { int xroot = find(parent, x); int yroot = find(parent, y); // Attach smaller rank tree under root of high rank // tree (Union by Rank) if (rank[xroot] rank[yroot]) { parent[yroot] = xroot; } // If ranks are same, then make one as root and // increment its rank by one else { parent[yroot] = xroot; rank[xroot]++; } } public: Graph(int vertices) { V = vertices; graph = vector >(); } // function to add an edge to graph void addEdge(int u, int v, int w) { graph.push_back({ u, v, w }); } // The main function to construct MST using Kruskal’s // algorithm void boruvkaMST() { vector parent(V); // An array to store index of the cheapest edge of // subset. It store [u,v,w] for each component vector rank(V); vector > cheapest(V, vector(3, -1)); // Initially there are V different trees. // Finally there will be one tree that will be MST int numTrees = V; int MSTweight = 0; // Create V subsets with single elements for (int node = 0; node 1) { // Traverse through all edges and update // cheapest of every component for (int i = 0; i w) { cheapest[set1] = { u, v, w }; } if (cheapest[set2][2] == -1 || cheapest[set2][2] > w) { cheapest[set2] = { u, v, w }; } } } // Consider the above picked cheapest edges and // add them to MST for (int node = 0; node