Skip to content

Commit 6ecbd41

Browse files
committed
feat(graph): Add TopologicalSort and DisjointSet implementations
1 parent b1aa896 commit 6ecbd41

File tree

5 files changed

+521
-0
lines changed

5 files changed

+521
-0
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,9 +348,11 @@
348348
- 📄 [Point](src/main/java/com/thealgorithms/geometry/Point.java)
349349
- 📁 **graph**
350350
- 📄 [ConstrainedShortestPath](src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java)
351+
- 📄 [DisjointSet](src/main/java/com/thealgorithms/graph/DisjointSet.java)
351352
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
352353
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
353354
- 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
355+
- 📄 [TopologicalSort](src/main/java/com/thealgorithms/graph/TopologicalSort.java)
354356
- 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java)
355357
- 📁 **greedyalgorithms**
356358
- 📄 [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.*;
4+
5+
/**
6+
* Implementation of the Disjoint Set (Union-Find) data structure with path
7+
* compression
8+
* and union by rank optimizations.
9+
*
10+
* <p>
11+
* A disjoint-set data structure maintains a collection of disjoint dynamic
12+
* sets.
13+
* Each set is represented by a "representative" element. The data structure
14+
* supports
15+
* two main operations:
16+
* </p>
17+
* <ul>
18+
* <li>Find: Determine which set an element belongs to by finding the
19+
* representative</li>
20+
* <li>Union: Join two sets into a single set</li>
21+
* </ul>
22+
*
23+
* <p>
24+
* Time Complexity:
25+
* </p>
26+
* <ul>
27+
* <li>Find: O(α(n)) amortized</li>
28+
* <li>Union: O(α(n)) amortized</li>
29+
* </ul>
30+
* <p>
31+
* where α(n) is the inverse Ackermann function, which grows extremely slowly.
32+
* For all practical values of n, α(n) ≤ 4.
33+
* </p>
34+
*
35+
* <p>
36+
* Space Complexity: O(n) where n is the number of elements
37+
* </p>
38+
*
39+
* <p>
40+
* Applications:
41+
* </p>
42+
* <ul>
43+
* <li>Kruskal's algorithm for minimum spanning trees</li>
44+
* <li>Finding connected components in graphs</li>
45+
* <li>Online dynamic connectivity</li>
46+
* <li>Least common ancestor in trees</li>
47+
* </ul>
48+
*/
49+
public class DisjointSet {
50+
private final int[] parent;
51+
private final int[] rank;
52+
private final int size;
53+
private int numSets; // Tracks number of disjoint sets
54+
55+
/**
56+
* Initializes a disjoint set data structure with elements from 0 to size-1.
57+
*
58+
* @param size number of elements
59+
* @throws IllegalArgumentException if size is negative
60+
*/
61+
public DisjointSet(int size) {
62+
if (size < 0) {
63+
throw new IllegalArgumentException("Size must be non-negative");
64+
}
65+
this.size = size;
66+
this.numSets = size;
67+
parent = new int[size];
68+
rank = new int[size];
69+
70+
// Initialize each element as its own set
71+
for (int i = 0; i < size; i++) {
72+
parent[i] = i;
73+
rank[i] = 0;
74+
}
75+
}
76+
77+
/**
78+
* Finds the representative of the set containing element x.
79+
* Uses path compression to optimize future queries.
80+
*
81+
* @param x element to find representative for
82+
* @return representative of x's set
83+
* @throws IllegalArgumentException if x is out of bounds
84+
*/
85+
public int find(int x) {
86+
if (x < 0 || x >= size) {
87+
throw new IllegalArgumentException("Element out of bounds: " + x);
88+
}
89+
90+
// Path compression: Make all nodes on path point directly to root
91+
if (parent[x] != x) {
92+
parent[x] = find(parent[x]);
93+
}
94+
return parent[x];
95+
}
96+
97+
/**
98+
* Merges the sets containing elements x and y.
99+
* Uses union by rank to keep the tree shallow.
100+
*
101+
* @param x first element
102+
* @param y second element
103+
* @return true if x and y were in different sets, false if they were already in
104+
* same set
105+
* @throws IllegalArgumentException if either element is out of bounds
106+
*/
107+
public boolean union(int x, int y) {
108+
int rootX = find(x);
109+
int rootY = find(y);
110+
111+
if (rootX == rootY) {
112+
return false; // Already in same set
113+
}
114+
115+
// Union by rank: Attach smaller rank tree under root of higher rank tree
116+
if (rank[rootX] < rank[rootY]) {
117+
parent[rootX] = rootY;
118+
} else if (rank[rootX] > rank[rootY]) {
119+
parent[rootY] = rootX;
120+
} else {
121+
parent[rootY] = rootX;
122+
rank[rootX]++;
123+
}
124+
125+
numSets--;
126+
return true;
127+
}
128+
129+
/**
130+
* Checks if two elements are in the same set.
131+
*
132+
* @param x first element
133+
* @param y second element
134+
* @return true if x and y are in the same set
135+
* @throws IllegalArgumentException if either element is out of bounds
136+
*/
137+
public boolean connected(int x, int y) {
138+
return find(x) == find(y);
139+
}
140+
141+
/**
142+
* Returns the current number of disjoint sets.
143+
*
144+
* @return number of disjoint sets
145+
*/
146+
public int getNumSets() {
147+
return numSets;
148+
}
149+
150+
/**
151+
* Returns the size of the set containing element x.
152+
*
153+
* @param x element to find set size for
154+
* @return size of x's set
155+
* @throws IllegalArgumentException if x is out of bounds
156+
*/
157+
public int getSetSize(int x) {
158+
int root = find(x);
159+
int count = 0;
160+
for (int i = 0; i < size; i++) {
161+
if (find(i) == root) {
162+
count++;
163+
}
164+
}
165+
return count;
166+
}
167+
168+
/**
169+
* Returns all elements in the same set as x.
170+
*
171+
* @param x element to find set members for
172+
* @return list of all elements in x's set
173+
* @throws IllegalArgumentException if x is out of bounds
174+
*/
175+
public List<Integer> getSetMembers(int x) {
176+
int root = find(x);
177+
List<Integer> members = new ArrayList<>();
178+
for (int i = 0; i < size; i++) {
179+
if (find(i) == root) {
180+
members.add(i);
181+
}
182+
}
183+
return members;
184+
}
185+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.*;
4+
5+
/**
6+
* Implementation of Kahn's algorithm for topological sorting of a directed
7+
* acyclic graph (DAG).
8+
*
9+
* <p>
10+
* The algorithm finds a linear ordering of vertices such that for every
11+
* directed edge u -> v,
12+
* vertex u comes before v in the ordering. A topological ordering is possible
13+
* if and only if the
14+
* graph has no directed cycles (i.e., it is a DAG).
15+
* </p>
16+
*
17+
* <p>
18+
* Time Complexity: O(V + E) where V is the number of vertices and E is the
19+
* number of edges
20+
* </p>
21+
* <p>
22+
* Space Complexity: O(V) for the queue and in-degree array
23+
* </p>
24+
*
25+
* <p>
26+
* Applications:
27+
* </p>
28+
* <ul>
29+
* <li>Task scheduling with dependencies</li>
30+
* <li>Build system dependency resolution</li>
31+
* <li>Course prerequisite planning</li>
32+
* <li>Package dependency management</li>
33+
* </ul>
34+
*/
35+
public final class TopologicalSort {
36+
37+
private TopologicalSort() {
38+
}
39+
40+
/**
41+
* Performs topological sorting using Kahn's algorithm.
42+
*
43+
* @param numVertices number of vertices in the graph (0 to numVertices-1)
44+
* @param edges list of directed edges, where each edge is represented as
45+
* int[]{from, to}
46+
* @return an array containing the topologically sorted order, or null if a
47+
* cycle exists
48+
* @throws IllegalArgumentException if edges is null or contains invalid
49+
* vertices
50+
*/
51+
public static int[] sort(int numVertices, List<int[]> edges) {
52+
if (edges == null) {
53+
throw new IllegalArgumentException("Edge list must not be null");
54+
}
55+
56+
// Create adjacency list representation
57+
List<List<Integer>> graph = new ArrayList<>(numVertices);
58+
for (int i = 0; i < numVertices; i++) {
59+
graph.add(new ArrayList<>());
60+
}
61+
62+
// Calculate in-degree for each vertex
63+
int[] inDegree = new int[numVertices];
64+
for (int[] edge : edges) {
65+
if (edge[0] < 0 || edge[0] >= numVertices || edge[1] < 0 || edge[1] >= numVertices) {
66+
throw new IllegalArgumentException("Invalid vertex in edge: " + Arrays.toString(edge));
67+
}
68+
graph.get(edge[0]).add(edge[1]);
69+
inDegree[edge[1]]++;
70+
}
71+
72+
// Initialize queue with vertices having no incoming edges
73+
Queue<Integer> queue = new LinkedList<>();
74+
for (int i = 0; i < numVertices; i++) {
75+
if (inDegree[i] == 0) {
76+
queue.offer(i);
77+
}
78+
}
79+
80+
int[] result = new int[numVertices];
81+
int index = 0;
82+
83+
// Process vertices in topological order
84+
while (!queue.isEmpty()) {
85+
int vertex = queue.poll();
86+
result[index++] = vertex;
87+
88+
// Reduce in-degree of neighbors and add to queue if in-degree becomes 0
89+
for (int neighbor : graph.get(vertex)) {
90+
inDegree[neighbor]--;
91+
if (inDegree[neighbor] == 0) {
92+
queue.offer(neighbor);
93+
}
94+
}
95+
}
96+
97+
// Check if topological sort is possible (no cycles)
98+
return index == numVertices ? result : null;
99+
}
100+
101+
/**
102+
* Alternative version that returns the result as a List and throws an exception
103+
* if a cycle is detected.
104+
*
105+
* @param numVertices number of vertices in the graph (0 to numVertices-1)
106+
* @param edges list of directed edges, where each edge is represented as
107+
* int[]{from, to}
108+
* @return a list containing the vertices in topologically sorted order
109+
* @throws IllegalArgumentException if the graph contains a cycle or if input is
110+
* invalid
111+
*/
112+
public static List<Integer> sortAndDetectCycle(int numVertices, List<int[]> edges) {
113+
int[] result = sort(numVertices, edges);
114+
if (result == null) {
115+
throw new IllegalArgumentException("Graph contains a cycle");
116+
}
117+
List<Integer> sorted = new ArrayList<>(numVertices);
118+
for (int vertex : result) {
119+
sorted.add(vertex);
120+
}
121+
return sorted;
122+
}
123+
}

0 commit comments

Comments
 (0)