Given a graph with n vertices and m edges. The graph is represented by an array Edges, where Edge[i] = [a, b] indicates an edge between vertices a and b. One edge can be removed from anywhere and added between any two vertices in one operation. Find the minimum number of operations that will be required to make the graph connected. If it is not possible to make the graph connected, return -1.
Input : n = 4, Edge =[ [0, 1], [ 0, 2], [1, 2]]
Output: 1
Explanation: We need a minimum of 1 operation to make the two components connected. We can remove the edge (1,2) and add the edge between node 2 and node 3 like the following:
Input: n = 9, Edge = [[0,1],[0,2],[0,3],[1,2],[2,3],[4,5],[5,6],[7,8]]
Output: 2
Explanation: We need a minimum of 2 operations to make the two components connected. We can remove the edge (0,2) and add the edge between node 3 and node 4 and we can remove the edge (0,3) and add it between nodes 6 and 8 like the following:
Input: n = 4, Edge =[[0, 1]]
Pre Requisite: Number of Provinces
The problem involves removing an edge and adding it between two other nodes, indicating that the graph is updating continuously. This gives the idea of using the Disjoint Set Data Structure.
Now, to make the graph connected, all the different components of the graphs must be connected to each other (directly or indirectly). The minimum number of edges required to connect all the components is always one lesser than the number of components. Hence, the problem boils down to finding the number of components in the given graph. Once found, the minimum number of edges to connect the graph can be found. If the number of edges present in the graph is less than that required to connect the graph, it is impossible to connect the graph and thus -1 can be returned.
#include <bits/stdc++.h>
using namespace std;
class DisjointSet {
public:
/* To store the ranks, parents and
sizes of different set of vertices */
vector<int> rank, parent, size;
// Constructor
DisjointSet(int n) {
rank.resize(n + 1, 0);
parent.resize(n + 1);
size.resize(n + 1);
for (int i = 0; i <= n; i++) {
parent[i] = i;
size[i] = 1;
}
}
// Function to find ultimate parent
int findUPar(int node) {
if (node == parent[node])
return node;
return parent[node] = findUPar(parent[node]);
}
// Function to implement union by rank
void unionByRank(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (rank[ulp_u] < rank[ulp_v]) {
parent[ulp_u] = ulp_v;
}
else if (rank[ulp_v] < rank[ulp_u]) {
parent[ulp_v] = ulp_u;
}
else {
parent[ulp_v] = ulp_u;
rank[ulp_u]++;
}
}
// Function to implement union by size
void unionBySize(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (size[ulp_u] < size[ulp_v]) {
parent[ulp_u] = ulp_v;
size[ulp_v] += size[ulp_u];
}
else {
parent[ulp_v] = ulp_u;
size[ulp_u] += size[ulp_v];
}
}
};
// Solution class
class Solution{
public:
/* Function to get the number of
operations to make network connected */
int solve(int n, vector<vector<int>> &Edge){
// Get the number of edges
int size = Edge.size();
/* Return -1 if connecting all
vertices is not possible */
if(size < n-1) return -1;
// Disjoint Set data structure
DisjointSet ds(n);
// Add all the edges in the set
for(int i=0; i<size; i++) {
ds.unionByRank(Edge[i][0], Edge[i][1]);
}
// To store count of required edges
int count = 0;
// Finding the number of components
for(int i=0; i<n; i++) {
if(ds.parent[i] == i) count++;
}
// Return the result
return count-1;
}
};
int main() {
int n = 4;
vector<vector<int>> Edge = {
{0, 1},
{0, 2},
{1, 2}
};
// Creating instance of Solution class
Solution sol;
/* Function call to get the number of
operations to make network connected */
int ans = sol.solve(n, Edge);
cout << "The number of operations to make network connected is: " << ans;
return 0;
}
import java.util.*;
class DisjointSet {
/* To store the ranks, parents and
sizes of different set of vertices */
int[] rank, parent, size;
// Constructor
DisjointSet(int n) {
rank = new int[n + 1];
parent = new int[n + 1];
size = new int[n + 1];
for (int i = 0; i <= n; i++) {
parent[i] = i;
size[i] = 1;
}
}
// Function to find ultimate parent
int findUPar(int node) {
if (node == parent[node])
return node;
return parent[node] = findUPar(parent[node]);
}
// Function to implement union by rank
void unionByRank(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (rank[ulp_u] < rank[ulp_v]) {
parent[ulp_u] = ulp_v;
}
else if (rank[ulp_v] < rank[ulp_u]) {
parent[ulp_v] = ulp_u;
}
else {
parent[ulp_v] = ulp_u;
rank[ulp_u]++;
}
}
// Function to implement union by size
void unionBySize(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (size[ulp_u] < size[ulp_v]) {
parent[ulp_u] = ulp_v;
size[ulp_v] += size[ulp_u];
}
else {
parent[ulp_v] = ulp_u;
size[ulp_u] += size[ulp_v];
}
}
}
// Solution class
class Solution{
/* Function to get the number of
operations to make network connected */
public int solve(int n, int[][] Edge){
// Get the number of edges
int size = Edge.length;
/* Return -1 if connecting all
vertices is not possible */
if(size < n-1) return -1;
// Disjoint Set data structure
DisjointSet ds = new DisjointSet(n);
// Add all the edges in the set
for(int i=0; i<size; i++) {
ds.unionByRank(Edge[i][0], Edge[i][1]);
}
// To store count of required edges
int count = 0;
// Finding the number of components
for(int i=0; i<n; i++) {
if(ds.parent[i] == i) count++;
}
// Return the result
return count-1;
}
public static void main(String[] args) {
int n = 4;
int[][] Edge = {
{0, 1},
{0, 2},
{1, 2}
};
// Creating instance of Solution class
Solution sol = new Solution();
/* Function call to get the number of
operations to make network connected */
int ans = sol.Solve(n, Edge);
System.out.println("The number of operations to make network connected is: " + ans);
}
}
class DisjointSet:
# To store the ranks, parents and
# sizes of different set of vertices
def __init__(self, n):
self.rank = [0] * (n + 1)
self.parent = [i for i in range(n + 1)]
self.size = [1] * (n + 1)
# Function to find ultimate parent
def findUPar(self, node):
if node == self.parent[node]:
return node
self.parent[node] = self.findUPar(self.parent[node])
return self.parent[node]
# Function to implement union by rank
def unionByRank(self, u, v):
ulp_u = self.findUPar(u)
ulp_v = self.findUPar(v)
if ulp_u == ulp_v:
return
if self.rank[ulp_u] < self.rank[ulp_v]:
self.parent[ulp_u] = ulp_v
elif self.rank[ulp_v] < self.rank[ulp_u]:
self.parent[ulp_v] = ulp_u
else:
self.parent[ulp_v] = ulp_u
self.rank[ulp_u] += 1
# Function to implement union by size
def unionBySize(self, u, v):
ulp_u = self.findUPar(u)
ulp_v = self.findUPar(v)
if ulp_u == ulp_v:
return
if self.size[ulp_u] < self.size[ulp_v]:
self.parent[ulp_u] = ulp_v
self.size[ulp_v] += self.size[ulp_u]
else:
self.parent[ulp_v] = ulp_u
self.size[ulp_u] += self.size[ulp_v]
# Solution class
class Solution:
# Function to get the number of
# operations to make network connected
def solve(self, n, Edge):
# Get the number of edges
size = len(Edge)
# Return -1 if connecting all
# vertices is not possible
if size < n - 1:
return -1
# Disjoint Set data structure
ds = DisjointSet(n)
# Add all the edges in the set
for i in range(size):
ds.unionByRank(Edge[i][0], Edge[i][1])
# To store count of required edges
count = 0
# Finding the number of components
for i in range(n):
if ds.parent[i] == i:
count += 1
# Return the result
return count - 1
# Creating instance of Solution class
sol = Solution()
# Function call to get the number of
# operations to make network connected
n = 4
Edge = [
[0, 1],
[0, 2],
[1, 2]
]
ans = sol.solve(n, Edge)
print("The number of operations to make network connected is:", ans)
class DisjointSet {
/* To store the ranks, parents and
sizes of different set of vertices */
constructor(n) {
this.rank = new Array(n + 1).fill(0);
this.parent = new Array(n + 1);
this.size = new Array(n + 1).fill(1);
for (let i = 0; i <= n; i++) {
this.parent[i] = i;
}
}
// Function to find ultimate parent
findUPar(node) {
if (node === this.parent[node])
return node;
return this.parent[node] =
this.findUPar(this.parent[node]);
}
// Function to implement union by rank
unionByRank(u, v) {
let ulp_u = this.findUPar(u);
let ulp_v = this.findUPar(v);
if (ulp_u === ulp_v) return;
if (this.rank[ulp_u] < this.rank[ulp_v]) {
this.parent[ulp_u] = ulp_v;
}
else if (this.rank[ulp_v] < this.rank[ulp_u]) {
this.parent[ulp_v] = ulp_u;
}
else {
this.parent[ulp_v] = ulp_u;
this.rank[ulp_u]++;
}
}
// Function to implement union by size
unionBySize(u, v) {
let ulp_u = this.findUPar(u);
let ulp_v = this.findUPar(v);
if (ulp_u === ulp_v) return;
if (this.size[ulp_u] < this.size[ulp_v]) {
this.parent[ulp_u] = ulp_v;
this.size[ulp_v] += this.size[ulp_u];
}
else {
this.parent[ulp_v] = ulp_u;
this.size[ulp_u] += this.size[ulp_v];
}
}
}
// Solution class
class Solution {
/* Function to get the number of
operations to make network connected */
solve(n, Edge) {
// Get the number of edges
const size = Edge.length;
/* Return -1 if connecting all
vertices is not possible */
if (size < n - 1) return -1;
// Disjoint Set data structure
const ds = new DisjointSet(n);
// Add all the edges in the set
for (let i = 0; i < size; i++) {
ds.unionByRank(Edge[i][0], Edge[i][1]);
}
// To store count of required edges
let count = 0;
// Finding the number of components
for (let i = 0; i < n; i++) {
if (ds.parent[i] === i) count++;
}
// Return the result
return count - 1;
}
}
// Creating instance of Solution class
const sol = new Solution();
/* Function call to get the number of
operations to make network connected */
const n = 4;
const Edge = [
[0, 1],
[0, 2],
[1, 2]
];
const ans = sol.solve(n, Edge);
console.log("The number of operations to make network connected is:", ans);
Time Complexity: O(N+M) (where N and M represent the number of vertices and edges in the graph)
Adding all M edges to the disjoint set takes O(M) time, and finding the number of components in the graph by finding unique ultimate parent node takes O(N) time.
Space Complexity: O(N)
The Disjoint Set data structure uses the parent and size/rank arrays of O(N) size each to perform the Union operation and to find the Ultimate parent.
Q: Why must there be at least n-1 edges to connect the graph?
A: A tree with n nodes has exactly n-1 edges. Any graph with fewer than n-1 edges must be disconnected.
Q: How do we count "extra edges"?
A: An extra edge is one that does not contribute to merging components, meaning the two vertices it connects are already in the same set
Q: How does this compare to Kruskal’s Algorithm for Minimum Spanning Trees?
A: Kruskal’s also uses Union-Find, but instead of merging extra edges, it selects minimum weight edges.
Q: What if adding new edges had a cost associated with them?
A: This would turn into a Minimum Cost Spanning Tree (MCST) problem, solvable with Prim’s or Kruskal’s Algorithm.