Skip to content

Commit f2a87e2

Browse files
committed
feat: Add Topological Sorting using DFS
- Implement DFS-based topological sort for DAGs - Add cycle detection for DAG validation - Include comprehensive test cases - Add documentation and examples Fixes #6938
1 parent 0977ccf commit f2a87e2

File tree

4 files changed

+607
-0
lines changed

4 files changed

+607
-0
lines changed

Kruskal.java

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package com.thealgorithms.graphs;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
7+
/**
8+
* Kruskal's Algorithm for finding Minimum Spanning Tree (MST)
9+
*
10+
* Kruskal's algorithm is a greedy algorithm that finds a minimum spanning tree
11+
* for a connected weighted undirected graph. The algorithm works by sorting all
12+
* edges by weight and adding them one by one to the MST, provided they don't
13+
* create a cycle.
14+
*
15+
* Time Complexity: O(E log E) where E is the number of edges
16+
* Space Complexity: O(V + E) where V is the number of vertices
17+
*
18+
* @author Raghu0703
19+
* @see <a href="https://en.wikipedia.org/wiki/Kruskal%27s_algorithm">Kruskal's Algorithm</a>
20+
*/
21+
public final class Kruskal {
22+
23+
private Kruskal() {
24+
// Utility class, no instantiation
25+
}
26+
27+
/**
28+
* Edge class representing an edge in the graph
29+
*/
30+
static class Edge implements Comparable<Edge> {
31+
int src;
32+
int dest;
33+
int weight;
34+
35+
Edge(int src, int dest, int weight) {
36+
this.src = src;
37+
this.dest = dest;
38+
this.weight = weight;
39+
}
40+
41+
@Override
42+
public int compareTo(Edge other) {
43+
return Integer.compare(this.weight, other.weight);
44+
}
45+
46+
@Override
47+
public String toString() {
48+
return src + " -- " + dest + " (weight: " + weight + ")";
49+
}
50+
}
51+
52+
/**
53+
* Disjoint Set (Union-Find) data structure for cycle detection
54+
*/
55+
static class DisjointSet {
56+
private final int[] parent;
57+
private final int[] rank;
58+
59+
DisjointSet(int n) {
60+
parent = new int[n];
61+
rank = new int[n];
62+
for (int i = 0; i < n; i++) {
63+
parent[i] = i;
64+
rank[i] = 0;
65+
}
66+
}
67+
68+
/**
69+
* Find operation with path compression
70+
*/
71+
int find(int x) {
72+
if (parent[x] != x) {
73+
parent[x] = find(parent[x]); // Path compression
74+
}
75+
return parent[x];
76+
}
77+
78+
/**
79+
* Union operation by rank
80+
*/
81+
void union(int x, int y) {
82+
int rootX = find(x);
83+
int rootY = find(y);
84+
85+
if (rootX != rootY) {
86+
// Union by rank
87+
if (rank[rootX] < rank[rootY]) {
88+
parent[rootX] = rootY;
89+
} else if (rank[rootX] > rank[rootY]) {
90+
parent[rootY] = rootX;
91+
} else {
92+
parent[rootY] = rootX;
93+
rank[rootX]++;
94+
}
95+
}
96+
}
97+
}
98+
99+
/**
100+
* Finds the Minimum Spanning Tree using Kruskal's Algorithm
101+
*
102+
* @param vertices Number of vertices in the graph
103+
* @param edges List of all edges in the graph
104+
* @return List of edges that form the MST
105+
*/
106+
public static List<Edge> kruskalMST(int vertices, List<Edge> edges) {
107+
if (vertices <= 0) {
108+
throw new IllegalArgumentException("Number of vertices must be positive");
109+
}
110+
if (edges == null) {
111+
throw new IllegalArgumentException("Edges list cannot be null");
112+
}
113+
114+
List<Edge> mst = new ArrayList<>();
115+
116+
// Sort edges by weight
117+
Collections.sort(edges);
118+
119+
// Initialize disjoint set
120+
DisjointSet ds = new DisjointSet(vertices);
121+
122+
// Process edges in sorted order
123+
for (Edge edge : edges) {
124+
int rootSrc = ds.find(edge.src);
125+
int rootDest = ds.find(edge.dest);
126+
127+
// If including this edge doesn't create a cycle
128+
if (rootSrc != rootDest) {
129+
mst.add(edge);
130+
ds.union(rootSrc, rootDest);
131+
132+
// MST is complete when we have (V-1) edges
133+
if (mst.size() == vertices - 1) {
134+
break;
135+
}
136+
}
137+
}
138+
139+
return mst;
140+
}
141+
142+
/**
143+
* Calculates the total weight of the MST
144+
*
145+
* @param mst List of edges in the MST
146+
* @return Total weight of the MST
147+
*/
148+
public static int getMSTWeight(List<Edge> mst) {
149+
int totalWeight = 0;
150+
for (Edge edge : mst) {
151+
totalWeight += edge.weight;
152+
}
153+
return totalWeight;
154+
}
155+
156+
/**
157+
* Example usage and demonstration
158+
*/
159+
public static void main(String[] args) {
160+
// Example graph with 5 vertices (0-4)
161+
int vertices = 5;
162+
List<Edge> edges = new ArrayList<>();
163+
164+
// Add edges: (src, dest, weight)
165+
edges.add(new Edge(0, 1, 10));
166+
edges.add(new Edge(0, 2, 6));
167+
edges.add(new Edge(0, 3, 5));
168+
edges.add(new Edge(1, 3, 15));
169+
edges.add(new Edge(2, 3, 4));
170+
edges.add(new Edge(2, 4, 8));
171+
edges.add(new Edge(3, 4, 12));
172+
173+
// Find MST
174+
List<Edge> mst = kruskalMST(vertices, edges);
175+
176+
// Print results
177+
System.out.println("Edges in the Minimum Spanning Tree:");
178+
for (Edge edge : mst) {
179+
System.out.println(edge);
180+
}
181+
182+
System.out.println("\nTotal weight of MST: " + getMSTWeight(mst));
183+
}
184+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package com.thealgorithms.datastructures.graphs;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Stack;
6+
7+
/**
8+
* Topological Sorting using Depth-First Search (DFS)
9+
*
10+
* <p>Topological sorting for a Directed Acyclic Graph (DAG) is a linear ordering of vertices
11+
* such that for every directed edge u → v, vertex u comes before v in the ordering.
12+
*
13+
* <p>This algorithm uses DFS traversal to compute the topological order. It maintains a visited
14+
* set to track processed nodes and a recursion stack to detect cycles.
15+
*
16+
* <p>Time Complexity: O(V + E) where V is the number of vertices and E is the number of edges
17+
* <p>Space Complexity: O(V) for the recursion stack and data structures
18+
*
19+
* <p>Applications:
20+
* - Task scheduling with dependencies
21+
* - Build systems (e.g., makefiles)
22+
* - Course prerequisite resolution
23+
* - Compilation order in software projects
24+
*
25+
* @author Raghu0703
26+
* @see <a href="https://en.wikipedia.org/wiki/Topological_sorting">Topological Sorting</a>
27+
*/
28+
public final class TopologicalSortDFS {
29+
30+
private TopologicalSortDFS() {
31+
}
32+
33+
/**
34+
* Performs topological sorting on a directed graph using DFS
35+
*
36+
* @param graph the adjacency list representation of the directed graph
37+
* @return a list containing vertices in topological order
38+
* @throws IllegalArgumentException if the graph contains a cycle
39+
*/
40+
public static List<Integer> topologicalSort(List<List<Integer>> graph) {
41+
if (graph == null || graph.isEmpty()) {
42+
return new ArrayList<>();
43+
}
44+
45+
int vertices = graph.size();
46+
boolean[] visited = new boolean[vertices];
47+
boolean[] recursionStack = new boolean[vertices];
48+
Stack<Integer> stack = new Stack<>();
49+
50+
// Perform DFS from all unvisited vertices
51+
for (int i = 0; i < vertices; i++) {
52+
if (!visited[i]) {
53+
if (hasCycleDFS(graph, i, visited, recursionStack, stack)) {
54+
throw new IllegalArgumentException("Graph contains a cycle. Topological sort is not possible for cyclic graphs.");
55+
}
56+
}
57+
}
58+
59+
// Pop all vertices from stack to get topological order
60+
List<Integer> result = new ArrayList<>();
61+
while (!stack.isEmpty()) {
62+
result.add(stack.pop());
63+
}
64+
65+
return result;
66+
}
67+
68+
/**
69+
* Helper method to perform DFS and detect cycles
70+
*
71+
* @param graph the adjacency list representation of the graph
72+
* @param vertex current vertex being processed
73+
* @param visited array to track visited vertices
74+
* @param recursionStack array to track vertices in current recursion path
75+
* @param stack stack to store vertices in reverse topological order
76+
* @return true if a cycle is detected, false otherwise
77+
*/
78+
private static boolean hasCycleDFS(List<List<Integer>> graph, int vertex, boolean[] visited, boolean[] recursionStack, Stack<Integer> stack) {
79+
// Mark current node as visited and part of recursion stack
80+
visited[vertex] = true;
81+
recursionStack[vertex] = true;
82+
83+
// Recur for all adjacent vertices
84+
List<Integer> neighbors = graph.get(vertex);
85+
if (neighbors != null) {
86+
for (Integer neighbor : neighbors) {
87+
// If neighbor is not visited, recursively check for cycles
88+
if (!visited[neighbor]) {
89+
if (hasCycleDFS(graph, neighbor, visited, recursionStack, stack)) {
90+
return true;
91+
}
92+
} else if (recursionStack[neighbor]) {
93+
// If neighbor is in recursion stack, cycle detected
94+
return true;
95+
}
96+
}
97+
}
98+
99+
// Remove vertex from recursion stack and push to result stack
100+
recursionStack[vertex] = false;
101+
stack.push(vertex);
102+
return false;
103+
}
104+
105+
/**
106+
* Checks if the given directed graph is a Directed Acyclic Graph (DAG)
107+
*
108+
* @param graph the adjacency list representation of the directed graph
109+
* @return true if graph is a DAG, false if it contains a cycle
110+
*/
111+
public static boolean isDAG(List<List<Integer>> graph) {
112+
if (graph == null || graph.isEmpty()) {
113+
return true;
114+
}
115+
116+
int vertices = graph.size();
117+
boolean[] visited = new boolean[vertices];
118+
boolean[] recursionStack = new boolean[vertices];
119+
120+
for (int i = 0; i < vertices; i++) {
121+
if (!visited[i]) {
122+
if (detectCycle(graph, i, visited, recursionStack)) {
123+
return false;
124+
}
125+
}
126+
}
127+
return true;
128+
}
129+
130+
/**
131+
* Helper method to detect cycle in a directed graph
132+
*
133+
* @param graph the adjacency list representation of the graph
134+
* @param vertex current vertex being processed
135+
* @param visited array to track visited vertices
136+
* @param recursionStack array to track vertices in current recursion path
137+
* @return true if a cycle is detected, false otherwise
138+
*/
139+
private static boolean detectCycle(List<List<Integer>> graph, int vertex, boolean[] visited, boolean[] recursionStack) {
140+
visited[vertex] = true;
141+
recursionStack[vertex] = true;
142+
143+
List<Integer> neighbors = graph.get(vertex);
144+
if (neighbors != null) {
145+
for (Integer neighbor : neighbors) {
146+
if (!visited[neighbor]) {
147+
if (detectCycle(graph, neighbor, visited, recursionStack)) {
148+
return true;
149+
}
150+
} else if (recursionStack[neighbor]) {
151+
return true;
152+
}
153+
}
154+
}
155+
156+
recursionStack[vertex] = false;
157+
return false;
158+
}
159+
}

0 commit comments

Comments
 (0)