Count Unreachable Pairs of Nodes in an Undirected Graph

Updated on 16 May, 2025
Count Unreachable Pairs of Nodes in an Undirected Graph header image

Problem Statement

The task involves determining the number of pairs of nodes in an undirected graph that are unreachable from one another. An integer n represents the total number of nodes in the graph, which are numerically labeled from 0 to n-1. The graph's edges are provided as a list of pairs, each pair [ai, bi] indicating an undirected edge between nodes ai and bi. The primary goal is to compute how many distinct pairs of nodes do not have a path connecting them.

Examples

Example 1

Input:

n = 3, edges = [[0,1],[0,2],[1,2]]

Output:

0

Explanation:

There are no pairs of nodes that are unreachable from each other. Therefore, we return 0.

Example 2

Input:

n = 7, edges = [[0,2],[0,5],[2,4],[1,6],[5,4]]

Output:

14

Explanation:

There are 14 pairs of nodes that are unreachable from each other:
[[0,1],[0,3],[0,6],[1,2],[1,3],[1,4],[1,5],[2,3],[2,6],[3,4],[3,5],[3,6],[4,6],[5,6]].
Therefore, we return 14.

Constraints

  • 1 <= n <= 105
  • 0 <= edges.length <= 2 * 105
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • ai != bi
  • There are no repeated edges.

Approach and Intuition

Given the problem's nature, let's break down the method to solve it through the following steps:

  1. Interpret the graph structure from the edges.

    • Construct an adjacency list to represent the graph, which will efficiently store all nodes and their respective connections.
  2. Identify connected components.

    • Use Depth First Search (DFS) or Breadth First Search (BFS) to explore the graph.
    • Start from any unvisited node and visit all reachable nodes to form a connected component.
    • Keep track of the nodes involved in each component.
  3. Calculate the unreachable node pairs.

    • First, for each component, calculate the possible pairs within the component itself, ensuring that these pairs are reachable.
    • Compute the total possible pairs of nodes which is given by the formula: C(n, 2) = n * (n - 1) / 2, where n is the number of nodes.
    • The number of unreachable pairs can be found by subtracting the reachable pairs in all components from the total pairs.
  4. Consider different scenarios:

    • All nodes connected: If all nodes are part of a single connected component, then every node is reachable from every other node, resulting in zero unreachable pairs.
    • Multiple connected components: If the graph contains multiple disjoint components, then nodes from different components will form unreachable pairs.

By reviewing the examples provided:

  • In Example 1, all three nodes are connected hence there are no unreachable pairs.
  • In Example 2, the graph is fragmented into different disjoint components, leading to 14 unreachable pairs calculated by checking inter-component connections.

Solutions

  • C++
  • Java
cpp
class DisjointSetUnion {
private:
    vector<int> parent, size;

public:
    DisjointSetUnion(int count) {
        parent.resize(count);
        size.resize(count, 0);
        for (int i = 0; i < count; i++) {
            parent[i] = i;
        }
    }
    int findParent(int node) {
        if (parent[node] != node) parent[node] = findParent(parent[node]);
        return parent[node];
    }
    void unite(int x, int y) {
        int rootX = findParent(x), rootY = findParent(y);
        if (rootX == rootY) {
            return;
        } else if (size[rootX] < size[rootY]) {
            parent[rootX] = rootY;
        } else if (size[rootX] > size[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            size[rootX]++;
        }
    }
};

class Solution {
public:
    long long calculatePairs(int m, vector<vector<int>>& relations) {
        DisjointSetUnion dsu(m);
        for (auto relation : relations) {
            dsu.unite(relation[0], relation[1]);
        }
        unordered_map<int, int> clusterCount;
        for (int i = 0; i < m; i++) {
            clusterCount[dsu.findParent(i)]++;
        }

        long long paths = 0;
        long long leftoverNodes = m;
        for (auto cluster : clusterCount) {
            int countInCluster = cluster.second;
            paths += countInCluster * (leftoverNodes - countInCluster);
            leftoverNodes -= countInCluster;
        }
        return paths;
    }
};

This solution addresses the problem of counting unreachable pairs of nodes in an undirected graph using C++. The primary approach involves utilizing a Disjoint Set Union (DSU) data structure to manage and analyze node connectivity effectively.

  • Start by defining a DisjointSetUnion class to encapsulate the union-find algorithm functionality:

    • Initialize each node to be its own parent and set an initial size.
    • Provide a method to find the representative parent for a given node, implementing path compression for efficiency.
    • Include a method to unite two nodes. This method links nodes by size, ensuring that smaller trees get attached under larger ones, maintaining a balanced tree structure.
  • Implement the primary Solution class:

    • Define the calculatePairs function which uses instances of DisjointSetUnion.
    • Loop through given node relations and unite nodes according to provided edges.
    • Count the size of each cluster formed in the DSU by finding the parent for each node and accumulating the count in a hash map.
    • Calculate the number of unreachable pairs by examining the sizes of these clusters:
      • For each cluster, compute potential pair combinations with nodes not in the current cluster.
      • Subtract the nodes in the current cluster from the total to avoid recounting.

This approach ensures that each pair is considered exactly once, yielding an efficient solution to the problem. The DSU helps manage connected components and makes it feasible to total the unreachable pairs by systematically considering the contribution of each cluster independently.

java
class DisjointSet {
    int[] leader;
    int[] treeSize;
    public DisjointSet(int capacity) {
        leader = new int[capacity];
        for (int i = 0; i < capacity; i++)
            leader[i] = i;
        treeSize = new int[capacity];
    }
    public int findRoot(int node) {
        if (leader[node] != node)
            leader[node] = findRoot(leader[node]);
        return leader[node];
    }
    public void unite(int node1, int node2) {
        int root1 = findRoot(node1), root2 = findRoot(node2);
        if (root1 == root2) {
            return;
        } else if (treeSize[root1] < treeSize[root2]) {
            leader[root1] = root2;
        } else if (treeSize[root1] > treeSize[root2]) {
            leader[root2] = root1;
        } else {
            leader[root2] = root1;
            treeSize[root1]++;
        }
    }
}

class Solution {
    public long countPairs(int n, int[][] edges) {
        DisjointSet dsu = new DisjointSet(n);
        for(int[] edge: edges) {
            dsu.unite(edge[0], edge[1]);
        }

        Map<Integer, Integer> sizeMap = new HashMap<>();
        for(int i = 0; i < n; i++) {
            int leader = dsu.findRoot(i);
            if(sizeMap.containsKey(leader)) {
                sizeMap.put(leader, sizeMap.get(leader) + 1);
            } else {
                sizeMap.put(leader, 1);
            }
        }
        
        long result = 0;
        long remainingNodes = n;
        for (int size : sizeMap.values()) {
            result += size * (remainingNodes - size);
            remainingNodes -= size;
        }
        return result;
    }
}

The provided Java code efficiently calculates the number of pairs of nodes in an undirected graph that are not reachable from each other. This method utilizes a DisjointSet (Union-Find) data structure to manage and merge sets of nodes, allowing it to keep track of which nodes are connected.

  • The DisjointSet class manages the connectivity information:

    • It contains two arrays, leader and treeSize. The leader array keeps track of the root of each node, facilitating the path compression technique during the find operation, which helps in optimizing the union operation by attaching smaller trees under the root of the larger trees, stored in treeSize.
    • The findRoot function recursively finds the representative leader of a node, employing path compression for efficiency.
    • The unite function merges the sets containing two nodes. If the nodes are already in the same set, it does nothing; otherwise, it attaches the smaller tree under the larger tree, updating the tree size as necessary.
  • The Solution class contains the method countPairs responsible for computing the count of unreachable pairs.

    • It initializes the DisjointSet with n nodes.
    • For each edge given, it merges the sets of the two connected nodes.
    • After all unions, it calculates the size of each connected component using a HashMap sizeMap.
    • To find the number of unreachable pairs, iterate through each component size. For each component, calculate the pairs that can be formed with nodes not in the current component and subtract these from the total count.

This approach minimizes direct combinatorial calculations for each node pair, leading to an efficient solution to the problem using graph theory and the Disjoint Set data structure. The result from the function represents the count of pairs that cannot communicate directly or indirectly through given edges.

Comments

No comments yet.