Skip to content

Commit 45e77b8

Browse files
committed
feat: Add Topological Sorting using DFS
1 parent 1c6026e commit 45e77b8

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.thealgorithms.graphs;
2+
3+
import java.util.*;
4+
5+
/**
6+
* Topological Sorting using Depth-First Search (DFS)
7+
*
8+
* Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering
9+
* of vertices such that for every directed edge u -> v, vertex u comes before v.
10+
*
11+
* Time Complexity: O(V + E)
12+
* Space Complexity: O(V)
13+
*
14+
* @author gowtham1412-p
15+
*/
16+
public class TopologicalSortDFS {
17+
18+
private int vertices;
19+
private List<List<Integer>> adjList;
20+
21+
/**
22+
* Constructor to initialize the graph
23+
*
24+
* @param vertices Number of vertices in the graph
25+
*/
26+
public TopologicalSortDFS(int vertices) {
27+
this.vertices = vertices;
28+
this.adjList = new ArrayList<>(vertices);
29+
for (int i = 0; i < vertices; i++) {
30+
adjList.add(new ArrayList<>());
31+
}
32+
}
33+
34+
/**
35+
* Add a directed edge to the graph
36+
*
37+
* @param source Source vertex
38+
* @param destination Destination vertex
39+
*/
40+
public void addEdge(int source, int destination) {
41+
if (source < 0 || source >= vertices || destination < 0 || destination >= vertices) {
42+
throw new IllegalArgumentException("Invalid vertex");
43+
}
44+
adjList.get(source).add(destination);
45+
}
46+
47+
/**
48+
* Performs topological sort using DFS
49+
*
50+
* @return List of vertices in topological order
51+
* @throws IllegalArgumentException if graph contains a cycle
52+
*/
53+
public List<Integer> topologicalSort() {
54+
boolean[] visited = new boolean[vertices];
55+
boolean[] recursionStack = new boolean[vertices];
56+
Stack<Integer> stack = new Stack<>();
57+
58+
// Check for cycles and perform DFS
59+
for (int i = 0; i < vertices; i++) {
60+
if (!visited[i]) {
61+
if (hasCycleDFS(i, visited, recursionStack)) {
62+
throw new IllegalArgumentException("Graph contains a cycle. Topological sort not possible.");
63+
}
64+
}
65+
}
66+
67+
// Reset visited for actual topological sort
68+
Arrays.fill(visited, false);
69+
70+
// Perform DFS to get topological order
71+
for (int i = 0; i < vertices; i++) {
72+
if (!visited[i]) {
73+
topologicalSortDFS(i, visited, stack);
74+
}
75+
}
76+
77+
// Convert stack to list
78+
List<Integer> result = new ArrayList<>();
79+
while (!stack.isEmpty()) {
80+
result.add(stack.pop());
81+
}
82+
83+
return result;
84+
}
85+
86+
/**
87+
* DFS helper method to detect cycles
88+
*
89+
* @param vertex Current vertex
90+
* @param visited Visited array
91+
* @param recursionStack Recursion stack to detect back edges
92+
* @return true if cycle exists, false otherwise
93+
*/
94+
private boolean hasCycleDFS(int vertex, boolean[] visited, boolean[] recursionStack) {
95+
visited[vertex] = true;
96+
recursionStack[vertex] = true;
97+
98+
for (int neighbor : adjList.get(vertex)) {
99+
if (!visited[neighbor]) {
100+
if (hasCycleDFS(neighbor, visited, recursionStack)) {
101+
return true;
102+
}
103+
} else if (recursionStack[neighbor]) {
104+
return true; // Back edge found - cycle detected
105+
}
106+
}
107+
108+
recursionStack[vertex] = false;
109+
return false;
110+
}
111+
112+
/**
113+
* DFS helper method for topological sort
114+
*
115+
* @param vertex Current vertex
116+
* @param visited Visited array
117+
* @param stack Stack to store topological order
118+
*/
119+
private void topologicalSortDFS(int vertex, boolean[] visited, Stack<Integer> stack) {
120+
visited[vertex] = true;
121+
122+
for (int neighbor : adjList.get(vertex)) {
123+
if (!visited[neighbor]) {
124+
topologicalSortDFS(neighbor, visited, stack);
125+
}
126+
}
127+
128+
stack.push(vertex);
129+
}
130+
131+
/**
132+
* Check if the graph is a DAG (Directed Acyclic Graph)
133+
*
134+
* @return true if graph is DAG, false otherwise
135+
*/
136+
public boolean isDAG() {
137+
boolean[] visited = new boolean[vertices];
138+
boolean[] recursionStack = new boolean[vertices];
139+
140+
for (int i = 0; i < vertices; i++) {
141+
if (!visited[i]) {
142+
if (hasCycleDFS(i, visited, recursionStack)) {
143+
return false;
144+
}
145+
}
146+
}
147+
return true;
148+
}
149+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package com.thealgorithms.graphs;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
import java.util.Arrays;
7+
import java.util.List;
8+
9+
/**
10+
* Test cases for TopologicalSortDFS
11+
*
12+
* @author gowtham1412-p
13+
*/
14+
class TopologicalSortDFSTest {
15+
16+
@Test
17+
void testSimpleDAG() {
18+
TopologicalSortDFS graph = new TopologicalSortDFS(6);
19+
graph.addEdge(5, 2);
20+
graph.addEdge(5, 0);
21+
graph.addEdge(4, 0);
22+
graph.addEdge(4, 1);
23+
graph.addEdge(2, 3);
24+
graph.addEdge(3, 1);
25+
26+
List<Integer> result = graph.topologicalSort();
27+
28+
// Verify the order is valid
29+
assertTrue(isValidTopologicalOrder(result, new int[][]{{5,2}, {5,0}, {4,0}, {4,1}, {2,3}, {3,1}}));
30+
}
31+
32+
@Test
33+
void testLinearGraph() {
34+
TopologicalSortDFS graph = new TopologicalSortDFS(4);
35+
graph.addEdge(0, 1);
36+
graph.addEdge(1, 2);
37+
graph.addEdge(2, 3);
38+
39+
List<Integer> result = graph.topologicalSort();
40+
assertEquals(Arrays.asList(0, 1, 2, 3), result);
41+
}
42+
43+
@Test
44+
void testSingleVertex() {
45+
TopologicalSortDFS graph = new TopologicalSortDFS(1);
46+
List<Integer> result = graph.topologicalSort();
47+
assertEquals(Arrays.asList(0), result);
48+
}
49+
50+
@Test
51+
void testDisconnectedGraph() {
52+
TopologicalSortDFS graph = new TopologicalSortDFS(4);
53+
graph.addEdge(0, 1);
54+
graph.addEdge(2, 3);
55+
56+
List<Integer> result = graph.topologicalSort();
57+
assertEquals(4, result.size());
58+
}
59+
60+
@Test
61+
void testCycleDetection() {
62+
TopologicalSortDFS graph = new TopologicalSortDFS(3);
63+
graph.addEdge(0, 1);
64+
graph.addEdge(1, 2);
65+
graph.addEdge(2, 0); // Creates a cycle
66+
67+
assertThrows(IllegalArgumentException.class, () -> {
68+
graph.topologicalSort();
69+
});
70+
}
71+
72+
@Test
73+
void testSelfLoop() {
74+
TopologicalSortDFS graph = new TopologicalSortDFS(3);
75+
graph.addEdge(0, 1);
76+
graph.addEdge(1, 1); // Self loop
77+
78+
assertThrows(IllegalArgumentException.class, () -> {
79+
graph.topologicalSort();
80+
});
81+
}
82+
83+
@Test
84+
void testIsDAG() {
85+
TopologicalSortDFS graph = new TopologicalSortDFS(4);
86+
graph.addEdge(0, 1);
87+
graph.addEdge(1, 2);
88+
graph.addEdge(2, 3);
89+
90+
assertTrue(graph.isDAG());
91+
}
92+
93+
@Test
94+
void testIsNotDAG() {
95+
TopologicalSortDFS graph = new TopologicalSortDFS(3);
96+
graph.addEdge(0, 1);
97+
graph.addEdge(1, 2);
98+
graph.addEdge(2, 0);
99+
100+
assertFalse(graph.isDAG());
101+
}
102+
103+
@Test
104+
void testInvalidVertex() {
105+
TopologicalSortDFS graph = new TopologicalSortDFS(3);
106+
107+
assertThrows(IllegalArgumentException.class, () -> {
108+
graph.addEdge(-1, 0);
109+
});
110+
111+
assertThrows(IllegalArgumentException.class, () -> {
112+
graph.addEdge(0, 5);
113+
});
114+
}
115+
116+
@Test
117+
void testComplexDAG() {
118+
TopologicalSortDFS graph = new TopologicalSortDFS(8);
119+
graph.addEdge(0, 3);
120+
graph.addEdge(0, 4);
121+
graph.addEdge(1, 3);
122+
graph.addEdge(2, 4);
123+
graph.addEdge(2, 7);
124+
graph.addEdge(3, 5);
125+
graph.addEdge(3, 6);
126+
graph.addEdge(3, 7);
127+
graph.addEdge(4, 6);
128+
129+
List<Integer> result = graph.topologicalSort();
130+
131+
// Verify valid ordering
132+
assertTrue(result.indexOf(0) < result.indexOf(3));
133+
assertTrue(result.indexOf(3) < result.indexOf(5));
134+
assertTrue(result.indexOf(4) < result.indexOf(6));
135+
}
136+
137+
/**
138+
* Helper method to verify topological order
139+
*/
140+
private boolean isValidTopologicalOrder(List<Integer> order, int[][] edges) {
141+
for (int[] edge : edges) {
142+
int u = edge[0];
143+
int v = edge[1];
144+
if (order.indexOf(u) > order.indexOf(v)) {
145+
return false;
146+
}
147+
}
148+
return true;
149+
}
150+
}

0 commit comments

Comments
 (0)