Day 25: Advanced Graphs

Day 25: Advanced Graphs

Welcome to Day 25 of my 100 Days of DSA challenge! Today, I explored advanced graph algorithms, including Dijkstra’s for shortest paths, Kruskal’s and Prim’s for minimum spanning trees, and topological sorting. These algorithms are essential for solving weighted graph problems and designing efficient networks. 🤖✨

Check out my GitHub repository for all the solutions and progress updates at: 100 Days of DSA

Let’s keep learning! 🚀✨


1. Dijkstra's Algorithm for Finding the Shortest Path in a Weighted Graph

This program implements Dijkstra's algorithm for finding the shortest path in a weighted graph. The graph is represented using an adjacency matrix, and the algorithm uses an array to track distances and visited vertices. It repeatedly selects the vertex with the smallest distance and updates the distances of its neighbors.

Code:

#include <iostream>
#include <cstring>
#include <climits>
using namespace std;

#define MAX 100     // Maximum number of vertices in the graph

class Graph {
    private:
        int adj_matrix[MAX][MAX];   // Adjacency matrix to store edge weights
        int num_vertices;           // Number of vertices in the graph

    public:
        // Constructor to initialize the graph
        Graph(int vertices) {
            num_vertices = vertices;
            for (int i = 0; i < MAX; i++) {
                for (int j = 0; j < MAX; j++) {
                    adj_matrix[i][j] = (i == j) ? 0 : INT_MAX;  // No edge between nodes initially
                }
            }
        }

        // Add edge to the graph
        void add_edge(int src, int dest, int weight) {
            adj_matrix[src][dest] = weight;
        }

        // Function to find the vertex with the minimum distance that is not yet processed
        int find_min_distance(int distance[], bool visited[]) {
            int min_distance = INT_MAX;
            int min_index = -1;

            for (int i = 0; i < num_vertices; i++) {
                if (!visited[i] && distance[i] < min_distance) {
                    min_distance = distance[i];
                    min_index = i;
                }
            }

            return min_index;
        }

        // Dijkstra's algorithm to find the shortest path from a source vertex
        void dijkstra(int source) {
            int distance[MAX];      // Array to store shortest distance from source to each vertex
            bool visited[MAX];      // Array to mark visited vertices

            // Initialize distances to infinity and visited array to false
            for (int i = 0; i < num_vertices; i++) {
                distance[i] = INT_MAX;
                visited[i] = false;
            }

            distance[source] = 0;   // Distance from source to itself is 0

            // Process each vertex
            for (int count = 0; count < num_vertices - 1; count++) {
                int u = find_min_distance(distance, visited);
                if (u == -1) break; // If no vertex is reachable, break

                visited[u] = true;

                // Update distance for all adjacent vertices of the chosen vertex
                for (int v = 0; v < num_vertices; v++) {
                    if (!visited[v] && adj_matrix[u][v] != INT_MAX && distance[u] + adj_matrix[u][v] < distance[v]) {
                        distance[v] = distance[u] + adj_matrix[u][v];
                    }
                }
            }

            // Print the shortest distances
            cout << "Vertex\tDistance from Source" << endl;
            for (int i = 0; i < num_vertices; i++) {
                cout << i << "\t" << (distance[i] == INT_MAX ? -1 : distance[i]) << endl;
            }
        }
};

int main() {
    int vertices = 6;
    Graph graph(vertices);
    graph.add_edge(0, 1, 4);
    graph.add_edge(0, 2, 2);
    graph.add_edge(1, 2, 5);
    graph.add_edge(1, 3, 10);
    graph.add_edge(2, 4, 3);
    graph.add_edge(4, 3, 4);
    graph.add_edge(3, 5, 11);
    int source = 0;
    cout << "Dijkstra's Algorithm (Source: " << source << ")" << endl;
    graph.dijkstra(source);
    return 0;
}

Output:


2. Network Delay Time Problem

This program implements the "Network Delay Time" problem using Dijkstra's algorithm. It calculates the minimum time required for a signal to travel from the source node to all other nodes in a weighted graph. If some nodes are unreachable, it reports so; otherwise, it outputs the maximum delay. The graph is represented with an adjacency matrix, and the algorithm finds the shortest path iteratively.

Code:

#include <iostream>
#include <cstring>
#include <climits>
using namespace std;

#define MAX 100     // Maximum number of vertices in the graph

class NetworkDelay {
    private:
        int adj_matrix[MAX][MAX];   // Adjacency matrix to store edge weights
        int num_vertices;           // Number of vertices in the graph

    public:
        // Constructor to initialize the graph
        NetworkDelay(int vertices) {
            num_vertices = vertices;
            for (int i = 0; i < MAX; i++) {
                for (int j = 0; j < MAX; j++) {
                    adj_matrix[i][j] = (i == j) ? 0 : INT_MAX;  // No edge between nodes initially
                }
            }
        }

        // Add edge to the graph
        void add_edge(int src, int dest, int weight) {
            adj_matrix[src][dest] = weight;
        }

        // Function to find the vertex with the minimum distance that is not yet processed
        int find_min_distance(int distance[], bool visited[]) {
            int min_distance = INT_MAX;
            int min_index = -1;

            for (int i = 0; i < num_vertices; i++) {
                if (!visited[i] && distance[i] < min_distance) {
                    min_distance = distance[i];
                    min_index = i;
                }
            }

            return min_index;
        }

        // Dijkstra's algorithm to calculate network delay time
        int network_delay(int source) {
            int distance[MAX];      // Array to store shortest distance from source to each vertex
            bool visited[MAX];      // Array to mark visited vertices

            // Initialize distances to infinity and visited array to false
            for (int i = 0; i < num_vertices; i++) {
                distance[i] = INT_MAX;
                visited[i] = false;
            }

            distance[source] = 0;   // Distance from source to itself is 0

            // Process each vertex
            for (int count = 0; count < num_vertices - 1; count++) {
                int u = find_min_distance(distance, visited);
                if (u == -1) break; // If no vertex is reachable, break

                visited[u] = true;

                // Update distance for all adjacent vertices of the chosen vertex
                for (int v = 0; v < num_vertices; v++) {
                    if (!visited[v] && adj_matrix[u][v] != INT_MAX && distance[u] + adj_matrix[u][v] < distance[v]) {
                        distance[v] = distance[u] + adj_matrix[u][v];
                    }
                }
            }

            // Find the maximum distance from the source
            int max_distance = 0;
            for (int i = 0; i < num_vertices; i++) {
                if (distance[i] == INT_MAX) {
                    return -1;      // If any vertex is unreachable
                }
                max_distance = max(max_distance, distance[i]);
            }
            return max_distance;
        }
};

int main() {
    int vertices = 4; 
    NetworkDelay network(vertices);
    network.add_edge(0, 1, 1);
    network.add_edge(0, 2, 3);
    network.add_edge(1, 2, 1);
    network.add_edge(2, 3, 1);
    int source = 0;
    int result = network.network_delay(source);
    if (result == -1) {
        cout << "Not all nodes are reachable from the source." << endl;
    } else {
        cout << "The network delay time is: " << result << " units." << endl;
    }
    return 0;
}

Output:


3. Kruskal's Algorithm to Find the Minimum Spanning Tree of a Graph

This program implements Kruskal's algorithm to find the Minimum Spanning Tree (MST) of a weighted, undirected graph. It stores the graph's edges and sorts them by weight. A disjoint set (union-find) is used to detect cycles and ensure edges added to the MST do not form a cycle. Each edge is processed in order of increasing weight, and if it connects two disjoint sets, it is added to the MST. The program outputs the edges of the MST and its total weight.

Code:

#include <iostream>
#include <cstring>
using namespace std;

#define MAX 100     // Maximum number of vertices in the graph

class Kruskal {
    private:
        int parent[MAX];    // Array to represent the disjoint set
        int edges[MAX][3];  // Array to store the edges (u, v, weight)
        int num_edges;      // Number of edges in the graph
        int num_vertices;   // Number of vertices in the graph

    public:
        // Constructor to initialize the graph
        Kruskal(int vertices) {
            num_vertices = vertices;
            num_edges = 0;
        }

        // Add an edge to the graph
        void add_edge(int u, int v, int weight) {
            edges[num_edges][0] = u;
            edges[num_edges][1] = v;
            edges[num_edges][2] = weight;
            num_edges++;
        }

        // Function to find the parent of a node in the disjoint set
        int find_parent(int node) {
            if (parent[node] == -1) {
                return node;
            }
            return parent[node] = find_parent(parent[node]);    // Path compression
        }

        // Function to perform union of two sets
        void union_sets(int u, int v) {
            int parent_u = find_parent(u);
            int parent_v = find_parent(v);
            parent[parent_u] = parent_v;
        }

        // Manual sorting function for the edges based on weight
        void sort_edges() {
            for (int i = 0; i < num_edges - 1; i++) {
                for (int j = 0; j < num_edges - i - 1; j++) {
                    if (edges[j][2] > edges[j + 1][2]) {
                        // Swap edges[j] and edges[j + 1]
                        for (int k = 0; k < 3; k++) {
                            int temp = edges[j][k];
                            edges[j][k] = edges[j + 1][k];
                            edges[j + 1][k] = temp;
                        }
                    }
                }
            }
        }

        // Function to find the MST using Kruskal's algorithm
        void find_mst() {
            sort_edges();       // Sort edges by weight

            memset(parent, -1, sizeof(parent)); // Initialize disjoint set
            int mst_weight = 0; // Total weight of MST

            cout << "Edges in the Minimum Spanning Tree:" << endl;

            for (int i = 0; i < num_edges; i++) {
                int u = edges[i][0];
                int v = edges[i][1];
                int weight = edges[i][2];

                // Check if adding this edge forms a cycle
                if (find_parent(u) != find_parent(v)) {
                    cout << u << " -- " << v << " == " << weight << endl;
                    mst_weight += weight;
                    union_sets(u, v);
                }
            }

            cout << "Total weight of MST: " << mst_weight << endl;
        }
};

int main() {
    int vertices = 5; 
    Kruskal graph(vertices);
    graph.add_edge(0, 1, 10);
    graph.add_edge(0, 2, 6);
    graph.add_edge(0, 3, 5);
    graph.add_edge(1, 3, 15);
    graph.add_edge(2, 3, 4);
    graph.find_mst();
    return 0;
}

Output:


4. Prim’s Algorithm to Find Minimum Cost to Connect All Points

This program implements Prim's algorithm to find the minimum cost to connect all points given their coordinates. It first initializes an adjacency matrix to store the distances (Manhattan distances) between each pair of points. Then, it uses Prim's algorithm to compute the minimum spanning tree (MST) by selecting edges with the smallest weight that connect unvisited points, ensuring all points are connected with the least total cost. The final result is the minimum cost to connect all points, which is printed at the end.

Code:

#include <iostream>
#include <climits>
#include <cmath>
using namespace std;

#define MAX 100  // Maximum number of points

class MinCostConnectPoints {
    private:
        int graph[MAX][MAX];  // Adjacency matrix for storing distances
        int num_points;       // Number of points

    public:
        // Constructor to initialize the graph
        MinCostConnectPoints(int points) {
            num_points = points;
            for (int i = 0; i < MAX; i++) {
                for (int j = 0; j < MAX; j++) {
                    graph[i][j] = 0;    // No connections initially
                }
            }
        }

        // Function to add edges between points with weights
        void add_edge(int src, int dest, int weight) {
            graph[src][dest] = weight;
            graph[dest][src] = weight;  // Undirected graph
        }

        // Find the point with the minimum key value not included in MST
        int find_min_key(int key[], bool in_mst[]) {
            int min_key = INT_MAX;
            int min_index = -1;

            for (int i = 0; i < num_points; i++) {
                if (!in_mst[i] && key[i] < min_key) {
                    min_key = key[i];
                    min_index = i;
                }
            }

            return min_index;
        }

        // Prim's algorithm to find the minimum cost to connect all points
        int find_min_cost() {
            int key[MAX];       // Minimum weight edge to include each vertex
            bool in_mst[MAX];   // True if vertex is included in MST
            int total_cost = 0;

            // Initialize keys as infinite and in_mst[] as false
            for (int i = 0; i < num_points; i++) {
                key[i] = INT_MAX;
                in_mst[i] = false;
            }

            key[0] = 0;  // Start with the first point

            // Iterate to form the MST
            for (int count = 0; count < num_points; count++) {
                int u = find_min_key(key, in_mst);
                if (u == -1) break;     // No more reachable points

                in_mst[u] = true;       // Include this point in MST
                total_cost += key[u];

                // Update the key values of the adjacent points
                for (int v = 0; v < num_points; v++) {
                    if (!in_mst[v] && graph[u][v] != 0 && graph[u][v] < key[v]) {
                        key[v] = graph[u][v];
                    }
                }
            }

            return total_cost;
        }
};

int main() {
    int points = 4;
    MinCostConnectPoints min_cost(points);
    // Define the points and calculate the Manhattan distances
    int coordinates[4][2] = {{0, 0}, {2, 2}, {3, 10}, {5, 2}};
    for (int i = 0; i < points; i++) {
        for (int j = i + 1; j < points; j++) {
            int weight = abs(coordinates[i][0] - coordinates[j][0]) + abs(coordinates[i][1] - coordinates[j][1]);
            min_cost.add_edge(i, j, weight);
        }
    }
    int result = min_cost.find_min_cost();
    cout << "The minimum cost to connect all points is: " << result << endl;
    return 0;
}

Output:


5. Solve the "alien dictionary" problem using topological sorting.

This program solves the "Alien Dictionary" problem by determining the order of characters in an alien language based on the given list of words. It constructs a directed graph where each character is a node, and edges represent the precedence of characters as inferred from consecutive words. The graph is then processed using topological sorting to determine a valid character order. If a cycle is detected in the graph (indicating no valid order), it returns an empty string. Otherwise, it outputs the topologically sorted order of characters. The solution uses BFS for topological sorting via Kahn's algorithm.

Code:

#include <iostream>
#include <climits>
#include <queue>
#include <cstring>
using namespace std;

#define MAX 26  // As there are only 26 lowercase English letters

class AlienDictionary {
    private:
        int in_degree[MAX];      // In-degree of each node
        char graph[MAX][MAX];    // Adjacency matrix for the graph
        int num_chars;           // Number of characters in the alphabet

    public:
        // Constructor to initialize graph and in-degree
        AlienDictionary(int n) {
            num_chars = n;
            memset(in_degree, 0, sizeof(in_degree));  // Initialize in-degree to 0
            memset(graph, 0, sizeof(graph));          // Initialize graph with no edges
        }

        // Function to add an edge from 'u' to 'v'
        void add_edge(char u, char v) {
            int u_idx = u - 'a';
            int v_idx = v - 'a';
            if (graph[u_idx][v_idx] == 0) {
                graph[u_idx][v_idx] = 1;    // There is a directed edge from u to v
                in_degree[v_idx]++;         // Increase the in-degree of 'v'
            }
        }

        // Function to perform topological sort and return the result
        string topological_sort() {
            queue<int> q;
            string result = "";

            // Add nodes with 0 in-degree to the queue
            for (int i = 0; i < num_chars; i++) {
                if (in_degree[i] == 0) {
                    q.push(i);
                }
            }

            while (!q.empty()) {
                int u = q.front();
                q.pop();
                result += (char)(u + 'a');

                // Decrease in-degree of neighbors and add them to the queue if in-degree becomes 0
                for (int v = 0; v < num_chars; v++) {
                    if (graph[u][v] == 1) {
                        in_degree[v]--;
                        if (in_degree[v] == 0) {
                            q.push(v);
                        }
                    }
                }
            }

            // If all characters are included in the result, return the result
            if (result.length() == num_chars) {
                return result;
            } else {
                return "";  // Return an empty string if there is a cycle
            }
        }

        // Function to build the graph from the list of words
        void build_graph(string words[], int word_count) {
            for (int i = 0; i < word_count - 1; i++) {
                string word1 = words[i];
                string word2 = words[i + 1];

                // Find the first different character between word1 and word2
                int len = min(word1.length(), word2.length());
                for (int j = 0; j < len; j++) {
                    if (word1[j] != word2[j]) {
                        add_edge(word1[j], word2[j]);
                        break;
                    }
                }
            }
        }
};

int main() {
    string words[] = {"z", "x", "z", "z", "x"};
    int word_count = 5;
    // There are 26 possible lowercase characters
    AlienDictionary alienDict(26);
    // Build the graph from the list of words
    alienDict.build_graph(words, word_count);
    // Get the topological order of characters
    string order = alienDict.topological_sort();
    if (order == "") {
        cout << "There is a cycle in the graph, no valid order possible." << endl;
    } else {
        cout << "The order of characters in the alien language is: " << order << endl;
    }
    return 0;
}

Output:


Day 25 highlighted the power of advanced graph algorithms in solving complex, real-world problems like optimizing networks, calculating shortest paths, and handling dynamic systems. Understanding these algorithms is crucial for building scalable and efficient solutions in various domains, from telecommunications to AI. 🚀