Skip to content

Commit 949edd4

Browse files
committed
Add :Cycle detection in undirected graph using Union–Find
1 parent b9c118f commit 949edd4

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* @file cycle_check_undirected_dsu.cpp
3+
* @brief Detect cycle in an undirected graph using Disjoint Set Union (Union–Find).
4+
*
5+
* @details
6+
* This implementation uses the Union–Find (Disjoint Set Union) data structure
7+
* with path compression and union by rank for near-constant-time cycle detection.
8+
* For each edge (u, v):
9+
* - If u and v belong to the same set, a cycle exists.
10+
* - Otherwise, merge their sets.
11+
*
12+
* @complexity
13+
* - Time: O(E α(V)), where α(V) is inverse Ackermann function (~constant)
14+
* - Space: O(V)
15+
*
16+
* @example
17+
* Input graph:
18+
* 0--1
19+
* | /
20+
* 2
21+
* has a cycle.
22+
*
23+
*/
24+
25+
#include <iostream>
26+
#include <vector>
27+
#include <unordered_set>
28+
29+
class DisjointSet {
30+
private:
31+
std::vector<int> parent, rank;
32+
33+
public:
34+
explicit DisjointSet(int n) : parent(n), rank(n, 0) {
35+
for (int i = 0; i < n; ++i)
36+
parent[i] = i;
37+
}
38+
39+
int find(int x) {
40+
if (parent[x] != x)
41+
parent[x] = find(parent[x]);
42+
return parent[x];
43+
}
44+
45+
bool unite(int x, int y) {
46+
int rx = find(x), ry = find(y);
47+
if (rx == ry)
48+
return false; // cycle detected
49+
50+
if (rank[rx] < rank[ry])
51+
parent[rx] = ry;
52+
else if (rank[rx] > rank[ry])
53+
parent[ry] = rx;
54+
else {
55+
parent[ry] = rx;
56+
rank[rx]++;
57+
}
58+
return true;
59+
}
60+
};
61+
62+
/**
63+
* @brief Detect if an undirected graph contains a cycle.
64+
* Avoids counting duplicated edges (v,u) and (u,v) twice.
65+
*/
66+
bool hasCycle(int V, const std::vector<std::pair<int, int>>& edges) {
67+
DisjointSet dsu(V);
68+
std::unordered_set<std::string> seen;
69+
70+
for (const auto& edge : edges) {
71+
int u = edge.first;
72+
int v = edge.second;
73+
74+
if (u == v)
75+
return true; // self-loop is a cycle
76+
77+
// Skip duplicate reversed edges
78+
std::string key1 = std::to_string(u) + "-" + std::to_string(v);
79+
std::string key2 = std::to_string(v) + "-" + std::to_string(u);
80+
if (seen.count(key1) || seen.count(key2))
81+
continue;
82+
seen.insert(key1);
83+
84+
if (!dsu.unite(u, v))
85+
return true; // cycle found
86+
}
87+
return false;
88+
}
89+
90+
int main() {
91+
// Test 1: cyclic triangle
92+
{
93+
int V = 3;
94+
std::vector<std::pair<int, int>> edges = {{0, 1}, {1, 2}, {0, 2}};
95+
std::cout << "Test 1 (triangle): " << (hasCycle(V, edges) ? "Cycle\n" : "No cycle\n");
96+
}
97+
98+
// Test 2: simple chain
99+
{
100+
int V = 4;
101+
std::vector<std::pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 3}};
102+
std::cout << "Test 2 (chain): " << (hasCycle(V, edges) ? "Cycle\n" : "No cycle\n");
103+
}
104+
105+
// Test 3: disconnected with one cycle
106+
{
107+
int V = 6;
108+
std::vector<std::pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}, {4, 5}};
109+
std::cout << "Test 3 (disconnected): " << (hasCycle(V, edges) ? "Cycle\n" : "No cycle\n");
110+
}
111+
112+
// Test 4: self-loop
113+
{
114+
int V = 3;
115+
std::vector<std::pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 2}};
116+
std::cout << "Test 4 (self-loop): " << (hasCycle(V, edges) ? "Cycle\n" : "No cycle\n");
117+
}
118+
119+
return 0;
120+
}

0 commit comments

Comments
 (0)