diff --git a/DIRECTORY.md b/DIRECTORY.md
index b311b10fa177..c6b20517eb34 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -348,9 +348,11 @@
- 📄 [Point](src/main/java/com/thealgorithms/geometry/Point.java)
- 📁 **graph**
- 📄 [ConstrainedShortestPath](src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java)
+ - 📄 [DisjointSet](src/main/java/com/thealgorithms/graph/DisjointSet.java)
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
- 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
+ - 📄 [TopologicalSort](src/main/java/com/thealgorithms/graph/TopologicalSort.java)
- 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java)
- 📁 **greedyalgorithms**
- 📄 [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
diff --git a/src/main/java/com/thealgorithms/graph/DisjointSet.java b/src/main/java/com/thealgorithms/graph/DisjointSet.java
new file mode 100644
index 000000000000..d1ad400007ef
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/DisjointSet.java
@@ -0,0 +1,185 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the Disjoint Set (Union-Find) data structure with path
+ * compression
+ * and union by rank optimizations.
+ *
+ *
+ * A disjoint-set data structure maintains a collection of disjoint dynamic
+ * sets.
+ * Each set is represented by a "representative" element. The data structure
+ * supports
+ * two main operations:
+ *
+ *
+ * - Find: Determine which set an element belongs to by finding the
+ * representative
+ * - Union: Join two sets into a single set
+ *
+ *
+ *
+ * Time Complexity:
+ *
+ *
+ * - Find: O(α(n)) amortized
+ * - Union: O(α(n)) amortized
+ *
+ *
+ * where α(n) is the inverse Ackermann function, which grows extremely slowly.
+ * For all practical values of n, α(n) ≤ 4.
+ *
+ *
+ *
+ * Space Complexity: O(n) where n is the number of elements
+ *
+ *
+ *
+ * Applications:
+ *
+ *
+ * - Kruskal's algorithm for minimum spanning trees
+ * - Finding connected components in graphs
+ * - Online dynamic connectivity
+ * - Least common ancestor in trees
+ *
+ */
+public class DisjointSet {
+ private final int[] parent;
+ private final int[] rank;
+ private final int size;
+ private int numSets; // Tracks number of disjoint sets
+
+ /**
+ * Initializes a disjoint set data structure with elements from 0 to size-1.
+ *
+ * @param size number of elements
+ * @throws IllegalArgumentException if size is negative
+ */
+ public DisjointSet(int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Size must be non-negative");
+ }
+ this.size = size;
+ this.numSets = size;
+ parent = new int[size];
+ rank = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ parent[i] = i;
+ rank[i] = 0;
+ }
+ }
+
+ /**
+ * Finds the representative of the set containing element x.
+ * Uses path compression to optimize future queries.
+ *
+ * @param x element to find representative for
+ * @return representative of x's set
+ * @throws IllegalArgumentException if x is out of bounds
+ */
+ public int find(int x) {
+ if (x < 0 || x >= size) {
+ throw new IllegalArgumentException("Element out of bounds: " + x);
+ }
+
+ // Path compression: Make all nodes on path point directly to root
+ if (parent[x] != x) {
+ parent[x] = find(parent[x]);
+ }
+ return parent[x];
+ }
+
+ /**
+ * Merges the sets containing elements x and y.
+ * Uses union by rank to keep the tree shallow.
+ *
+ * @param x first element
+ * @param y second element
+ * @return true if x and y were in different sets, false if they were already in
+ * same set
+ * @throws IllegalArgumentException if either element is out of bounds
+ */
+ public boolean union(int x, int y) {
+ int rootX = find(x);
+ int rootY = find(y);
+
+ if (rootX == rootY) {
+ return false; // Already in same set
+ }
+
+ // Union by rank: Attach smaller rank tree under root of higher rank tree
+ if (rank[rootX] < rank[rootY]) {
+ parent[rootX] = rootY;
+ } else if (rank[rootX] > rank[rootY]) {
+ parent[rootY] = rootX;
+ } else {
+ parent[rootY] = rootX;
+ rank[rootX]++;
+ }
+
+ numSets--;
+ return true;
+ }
+
+ /**
+ * Checks if two elements are in the same set.
+ *
+ * @param x first element
+ * @param y second element
+ * @return true if x and y are in the same set
+ * @throws IllegalArgumentException if either element is out of bounds
+ */
+ public boolean connected(int x, int y) {
+ return find(x) == find(y);
+ }
+
+ /**
+ * Returns the current number of disjoint sets.
+ *
+ * @return number of disjoint sets
+ */
+ public int getNumSets() {
+ return numSets;
+ }
+
+ /**
+ * Returns the size of the set containing element x.
+ *
+ * @param x element to find set size for
+ * @return size of x's set
+ * @throws IllegalArgumentException if x is out of bounds
+ */
+ public int getSetSize(int x) {
+ int root = find(x);
+ int count = 0;
+ for (int i = 0; i < size; i++) {
+ if (find(i) == root) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns all elements in the same set as x.
+ *
+ * @param x element to find set members for
+ * @return list of all elements in x's set
+ * @throws IllegalArgumentException if x is out of bounds
+ */
+ public List getSetMembers(int x) {
+ int root = find(x);
+ List members = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ if (find(i) == root) {
+ members.add(i);
+ }
+ }
+ return members;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/graph/TopologicalSort.java b/src/main/java/com/thealgorithms/graph/TopologicalSort.java
new file mode 100644
index 000000000000..b6e84c67f051
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/TopologicalSort.java
@@ -0,0 +1,127 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Implementation of Kahn's algorithm for topological sorting of a directed
+ * acyclic graph (DAG).
+ *
+ *
+ * The algorithm finds a linear ordering of vertices such that for every
+ * directed edge u -> v,
+ * vertex u comes before v in the ordering. A topological ordering is possible
+ * if and only if the
+ * graph has no directed cycles (i.e., it is a DAG).
+ *
+ *
+ *
+ * Time Complexity: O(V + E) where V is the number of vertices and E is the
+ * number of edges
+ *
+ *
+ * Space Complexity: O(V) for the queue and in-degree array
+ *
+ *
+ *
+ * Applications:
+ *
+ *
+ * - Task scheduling with dependencies
+ * - Build system dependency resolution
+ * - Course prerequisite planning
+ * - Package dependency management
+ *
+ */
+public final class TopologicalSort {
+
+ private TopologicalSort() {
+ }
+
+ /**
+ * Performs topological sorting using Kahn's algorithm.
+ *
+ * @param numVertices number of vertices in the graph (0 to numVertices-1)
+ * @param edges list of directed edges, where each edge is represented as
+ * int[]{from, to}
+ * @return an array containing the topologically sorted order, or null if a
+ * cycle exists
+ * @throws IllegalArgumentException if edges is null or contains invalid
+ * vertices
+ */
+ public static int[] sort(int numVertices, Iterable edges) {
+ if (edges == null) {
+ throw new IllegalArgumentException("Edge list must not be null");
+ }
+
+ // Create adjacency list representation
+ List> graph = new ArrayList<>(numVertices);
+ for (int i = 0; i < numVertices; i++) {
+ graph.add(new ArrayList<>());
+ }
+
+ // Calculate in-degree for each vertex
+ int[] inDegree = new int[numVertices];
+ for (int[] edge : edges) {
+ if (edge[0] < 0 || edge[0] >= numVertices || edge[1] < 0 || edge[1] >= numVertices) {
+ throw new IllegalArgumentException("Invalid vertex in edge: " + Arrays.toString(edge));
+ }
+ graph.get(edge[0]).add(edge[1]);
+ inDegree[edge[1]]++;
+ }
+
+ // Initialize queue with vertices having no incoming edges
+ Queue queue = new LinkedList<>();
+ for (int i = 0; i < numVertices; i++) {
+ if (inDegree[i] == 0) {
+ queue.offer(i);
+ }
+ }
+
+ int[] result = new int[numVertices];
+ int index = 0;
+
+ // Process vertices in topological order
+ while (!queue.isEmpty()) {
+ int vertex = queue.poll();
+ result[index++] = vertex;
+
+ // Reduce in-degree of neighbors and add to queue if in-degree becomes 0
+ for (int neighbor : graph.get(vertex)) {
+ inDegree[neighbor]--;
+ if (inDegree[neighbor] == 0) {
+ queue.offer(neighbor);
+ }
+ }
+ }
+
+ // Check if topological sort is possible (no cycles)
+ return index == numVertices ? result : null;
+ }
+
+ /**
+ * Alternative version that returns the result as a List and throws an exception
+ * if a cycle is detected.
+ *
+ * @param numVertices number of vertices in the graph (0 to numVertices-1)
+ * @param edges list of directed edges, where each edge is represented as
+ * int[]{from, to}
+ * @return a list containing the vertices in topologically sorted order
+ * @throws IllegalArgumentException if the graph contains a cycle or if input is
+ * invalid
+ */
+ public static List sortAndDetectCycle(int numVertices, List edges) {
+ int[] result = sort(numVertices, edges);
+ if (result == null) {
+ throw new IllegalArgumentException("Graph contains a cycle");
+ }
+ List sorted = new ArrayList<>(numVertices);
+ for (int vertex : result) {
+ sorted.add(vertex);
+ }
+ return sorted;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/DisjointSetTest.java b/src/test/java/com/thealgorithms/graph/DisjointSetTest.java
new file mode 100644
index 000000000000..58dc09fc23b5
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/DisjointSetTest.java
@@ -0,0 +1,124 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class DisjointSetTest {
+
+ @Test
+ @DisplayName("Initially all elements are in separate sets")
+ void initialState() {
+ DisjointSet ds = new DisjointSet(5);
+ assertEquals(5, ds.getNumSets());
+ for (int i = 0; i < 5; i++) {
+ assertEquals(i, ds.find(i));
+ assertEquals(1, ds.getSetSize(i));
+ }
+ }
+
+ @Test
+ @DisplayName("Union of elements creates correct sets")
+ void basicUnion() {
+ DisjointSet ds = new DisjointSet(4);
+ assertTrue(ds.union(0, 1)); // Should succeed
+ assertTrue(ds.union(2, 3)); // Should succeed
+ assertFalse(ds.union(0, 1)); // Should fail (already united)
+ assertEquals(2, ds.getNumSets());
+ assertTrue(ds.connected(0, 1));
+ assertTrue(ds.connected(2, 3));
+ assertFalse(ds.connected(0, 2));
+ }
+
+ @Test
+ @DisplayName("Path compression optimizes tree structure")
+ void pathCompression() {
+ DisjointSet ds = new DisjointSet(4);
+ ds.union(0, 1);
+ ds.union(1, 2);
+ ds.union(2, 3);
+ // After these unions, we should have one set
+ assertEquals(1, ds.getNumSets());
+ // After path compression, all elements should point to the same root
+ int root = ds.find(0);
+ for (int i = 1; i < 4; i++) {
+ assertEquals(root, ds.find(i));
+ }
+ }
+
+ @Test
+ @DisplayName("Set size tracking works correctly")
+ void setSizeTracking() {
+ DisjointSet ds = new DisjointSet(6);
+ ds.union(0, 1); // Set of size 2
+ ds.union(2, 3); // Another set of size 2
+ ds.union(0, 2); // Merge into set of size 4
+ assertEquals(4, ds.getSetSize(0));
+ assertEquals(4, ds.getSetSize(1));
+ assertEquals(4, ds.getSetSize(2));
+ assertEquals(4, ds.getSetSize(3));
+ assertEquals(1, ds.getSetSize(4));
+ assertEquals(1, ds.getSetSize(5));
+ }
+
+ @Test
+ @DisplayName("Get set members returns correct lists")
+ void getSetMembers() {
+ DisjointSet ds = new DisjointSet(4);
+ ds.union(0, 1);
+ ds.union(2, 3);
+
+ List set1 = ds.getSetMembers(0);
+ List set2 = ds.getSetMembers(2);
+
+ assertEquals(2, set1.size());
+ assertTrue(set1.contains(0));
+ assertTrue(set1.contains(1));
+
+ assertEquals(2, set2.size());
+ assertTrue(set2.contains(2));
+ assertTrue(set2.contains(3));
+ }
+
+ @Test
+ @DisplayName("Out of bounds access throws exception")
+ void outOfBounds() {
+ DisjointSet ds = new DisjointSet(3);
+ assertThrows(IllegalArgumentException.class, () -> ds.find(-1));
+ assertThrows(IllegalArgumentException.class, () -> ds.find(3));
+ assertThrows(IllegalArgumentException.class, () -> ds.union(-1, 0));
+ assertThrows(IllegalArgumentException.class, () -> ds.union(0, 3));
+ }
+
+ @Test
+ @DisplayName("Negative size throws exception")
+ void negativeSize() {
+ assertThrows(IllegalArgumentException.class, () -> new DisjointSet(-1));
+ }
+
+ @Test
+ @DisplayName("Connected components example")
+ void connectedComponents() {
+ DisjointSet ds = new DisjointSet(6);
+ // Create two connected components: 0-1-2 and 3-4-5
+ ds.union(0, 1);
+ ds.union(1, 2);
+ ds.union(3, 4);
+ ds.union(4, 5);
+
+ assertEquals(2, ds.getNumSets());
+
+ // Check connectivity within components
+ assertTrue(ds.connected(0, 2));
+ assertTrue(ds.connected(3, 5));
+
+ // Check separation between components
+ assertFalse(ds.connected(0, 3));
+ assertFalse(ds.connected(2, 4));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/TopologicalSortTest.java b/src/test/java/com/thealgorithms/graph/TopologicalSortTest.java
new file mode 100644
index 000000000000..7eed902bdbec
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/TopologicalSortTest.java
@@ -0,0 +1,92 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class TopologicalSortTest {
+
+ @Test
+ @DisplayName("Simple DAG returns correct ordering")
+ void simpleDAG() {
+ List edges = new ArrayList<>();
+ edges.add(new int[] { 0, 1 });
+ edges.add(new int[] { 0, 2 });
+ edges.add(new int[] { 1, 3 });
+ edges.add(new int[] { 2, 3 });
+ int[] result = TopologicalSort.sort(4, edges);
+ assertArrayEquals(new int[] { 0, 1, 2, 3 }, result);
+ }
+
+ @Test
+ @DisplayName("Empty graph returns valid ordering")
+ void emptyGraph() {
+ List edges = new ArrayList<>();
+ int[] result = TopologicalSort.sort(3, edges);
+ assertEquals(3, result.length);
+ // Any permutation is valid for empty graph
+ assertTrue(Arrays.stream(result).allMatch(v -> v >= 0 && v < 3));
+ }
+
+ @Test
+ @DisplayName("Graph with cycle returns null")
+ void graphWithCycle() {
+ List edges = new ArrayList<>();
+ edges.add(new int[] { 0, 1 });
+ edges.add(new int[] { 1, 2 });
+ edges.add(new int[] { 2, 0 });
+ assertNull(TopologicalSort.sort(3, edges));
+ }
+
+ @Test
+ @DisplayName("Course prerequisites example")
+ void coursePrerequisites() {
+ // Example: Course prerequisites where edge [a,b] means course a must be taken
+ // before b
+ List edges = new ArrayList<>();
+ edges.add(new int[] { 1, 0 }); // Calculus I -> Calculus II
+ edges.add(new int[] { 2, 0 }); // Linear Algebra -> Calculus II
+ edges.add(new int[] { 1, 3 }); // Calculus I -> Differential Equations
+ edges.add(new int[] { 2, 3 }); // Linear Algebra -> Differential Equations
+ List result = TopologicalSort.sortAndDetectCycle(4, edges);
+ // Either [1,2,0,3] or [2,1,0,3] is valid
+ assertEquals(4, result.size());
+ // Verify prerequisites are satisfied
+ assertTrue(result.indexOf(1) < result.indexOf(0)); // Calc I before Calc II
+ assertTrue(result.indexOf(2) < result.indexOf(0)); // Linear Algebra before Calc II
+ assertTrue(result.indexOf(1) < result.indexOf(3)); // Calc I before Diff Eq
+ assertTrue(result.indexOf(2) < result.indexOf(3)); // Linear Algebra before Diff Eq
+ }
+
+ @Test
+ @DisplayName("Invalid vertex throws exception")
+ void invalidVertex() {
+ List edges = new ArrayList<>();
+ edges.add(new int[] { 0, 5 }); // Vertex 5 is invalid for a graph with 3 vertices
+ assertThrows(IllegalArgumentException.class, () -> TopologicalSort.sort(3, edges));
+ }
+
+ @Test
+ @DisplayName("Null edge list throws exception")
+ void nullEdgeList() {
+ assertThrows(IllegalArgumentException.class, () -> TopologicalSort.sort(3, null));
+ }
+
+ @Test
+ @DisplayName("sortAndDetectCycle throws exception for cyclic graph")
+ void detectCycleThrowsException() {
+ List edges = new ArrayList<>();
+ edges.add(new int[] { 0, 1 });
+ edges.add(new int[] { 1, 2 });
+ edges.add(new int[] { 2, 0 });
+ assertThrows(IllegalArgumentException.class, () -> TopologicalSort.sortAndDetectCycle(3, edges));
+ }
+}