Skip to content

Commit 72788cc

Browse files
committed
feat: Topological Sort using DFS added
1 parent 9e771a5 commit 72788cc

2 files changed

Lines changed: 221 additions & 0 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.HashMap;
6+
import java.util.HashSet;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Set;
10+
11+
/**
12+
* Implementation of <b>Topological Sort</b> using <b>Depth-First Search
13+
* (DFS)</b>.
14+
*
15+
* <p>
16+
* This algorithm returns a valid topological ordering of a directed acyclic
17+
* graph (DAG).
18+
* If a cycle is detected, meaning the graph cannot be topologically sorted,
19+
* it returns an empty array.
20+
*
21+
* <p>
22+
* <b>Use Case:</b> Determining the order of course completion based on
23+
* prerequisite dependencies
24+
* (commonly known as the “Course Schedule II” problem on LeetCode).
25+
* Problem link: <a href=
26+
* "https://leetcode.com/problems/course-schedule-ii/description/">LeetCode —
27+
* Course Schedule II</a>
28+
*
29+
* <p>
30+
* <b>Algorithm Overview:</b>
31+
* <ul>
32+
* <li>Each course (node) is visited using DFS.</li>
33+
* <li>During traversal, nodes currently in the recursion stack are tracked to
34+
* detect cycles.</li>
35+
* <li>When a node finishes processing, it is added to the output list.</li>
36+
* <li>The output list is then reversed to form a valid topological order.</li>
37+
* </ul>
38+
*
39+
* <p>
40+
* <b>Time Complexity:</b> O(V + E) — where V is the number of courses
41+
* (vertices),
42+
* and E is the number of prerequisite relations (edges).
43+
* <br>
44+
* <b>Space Complexity:</b> O(V + E) — for adjacency list, recursion stack, and
45+
* auxiliary sets.
46+
*
47+
* <p>
48+
* <b>Example:</b>
49+
*
50+
* <pre>
51+
* int numCourses = 4;
52+
* int[][] prerequisites = { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } };
53+
* TopologicalSortDFS topo = new TopologicalSortDFS();
54+
* int[] order = topo.findOrder(numCourses, prerequisites);
55+
* // Possible output: [0, 2, 1, 3]
56+
* </pre>
57+
*
58+
* @author Muhammad Junaid
59+
*/
60+
public class TopologicalSortDFS {
61+
62+
/**
63+
* Finds a valid topological order of courses given prerequisite constraints.
64+
*
65+
* @param numCourses the total number of courses labeled from 0 to numCourses
66+
* - 1
67+
* @param prerequisites an array of prerequisite pairs where each pair [a, b]
68+
* indicates that course {@code a} depends on course
69+
* {@code b}
70+
* @return an integer array representing one possible order to complete all
71+
* courses;
72+
* returns an empty array if it is impossible (i.e., a cycle exists)
73+
*/
74+
public int[] findOrder(int numCourses, int[][] prerequisites) {
75+
Map<Integer, List<Integer>> prereq = new HashMap<>();
76+
for (int i = 0; i < numCourses; i++) {
77+
prereq.put(i, new ArrayList<>());
78+
}
79+
for (int[] pair : prerequisites) {
80+
int crs = pair[0];
81+
int pre = pair[1];
82+
prereq.get(crs).add(pre);
83+
}
84+
85+
List<Integer> output = new ArrayList<>();
86+
Set<Integer> visited = new HashSet<>();
87+
Set<Integer> cycle = new HashSet<>();
88+
89+
for (int c = 0; c < numCourses; c++) {
90+
if (!dfs(c, prereq, visited, cycle, output)) {
91+
return new int[0]; // Cycle detected — impossible order
92+
}
93+
}
94+
95+
// Reverse post-order result to obtain topological order
96+
Collections.reverse(output);
97+
return output.stream().mapToInt(Integer::intValue).toArray();
98+
}
99+
100+
/**
101+
* Performs a depth-first search to visit all prerequisites of a course.
102+
*
103+
* @param crs the current course being visited
104+
* @param prereq adjacency list mapping courses to their prerequisites
105+
* @param visited set of courses that have been completely processed
106+
* @param cycle set of courses currently in the recursion stack (used for
107+
* cycle detection)
108+
* @param output list that accumulates the topological order in reverse
109+
* @return {@code true} if the current course and its prerequisites can be
110+
* processed without cycles;
111+
* {@code false} if a cycle is detected
112+
*/
113+
private boolean dfs(int crs, Map<Integer, List<Integer>> prereq,
114+
Set<Integer> visited, Set<Integer> cycle,
115+
List<Integer> output) {
116+
117+
if (cycle.contains(crs)) {
118+
return false; // Cycle detected
119+
}
120+
if (visited.contains(crs)) {
121+
return true; // Already processed
122+
}
123+
124+
cycle.add(crs);
125+
for (int pre : prereq.get(crs)) {
126+
if (!dfs(pre, prereq, visited, cycle, output)) {
127+
return false;
128+
}
129+
}
130+
131+
cycle.remove(crs);
132+
visited.add(crs);
133+
output.add(crs);
134+
return true;
135+
}
136+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class TopologicalSortDFSTest {
9+
private TopologicalSortDFS topologicalSortDFS;
10+
11+
@BeforeEach
12+
public void setUp() {
13+
topologicalSortDFS = new TopologicalSortDFS();
14+
}
15+
16+
@Test
17+
public void testSimpleCase() {
18+
// Example: Two courses where 1 depends on 0
19+
int numCourses = 2;
20+
int[][] prerequisites = { { 1, 0 } };
21+
int[] expected = { 0, 1 };
22+
23+
int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites);
24+
25+
assertArrayEquals(expected, result, "Expected order is [0, 1].");
26+
}
27+
28+
@Test
29+
public void testMultipleDependencies() {
30+
// Example: 4 courses with dependencies
31+
// 1 -> 0, 2 -> 0, 3 -> 1, 3 -> 2
32+
int numCourses = 4;
33+
int[][] prerequisites = { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } };
34+
int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites);
35+
36+
// Valid answers could be [0,1,2,3] or [0,2,1,3]
37+
int[] expected = { 0, 2, 1, 3 };
38+
assertArrayEquals(expected, result, "Valid topological order expected, e.g., [0,1,2,3] or [0,2,1,3].");
39+
}
40+
41+
@Test
42+
public void testNoDependencies() {
43+
// Example: 3 courses with no dependencies
44+
int numCourses = 3;
45+
int[][] prerequisites = {};
46+
int[] expected = { 0, 1, 2 };
47+
48+
int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites);
49+
50+
assertArrayEquals(expected, result, "Any order is valid when there are no dependencies.");
51+
}
52+
53+
@Test
54+
public void testCycleGraph() {
55+
// Example: A cycle exists (0 -> 1 -> 0)
56+
int numCourses = 2;
57+
int[][] prerequisites = { { 0, 1 }, { 1, 0 } };
58+
int[] expected = {};
59+
60+
int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites);
61+
62+
assertArrayEquals(expected, result, "Cycle detected, no valid course order.");
63+
}
64+
65+
@Test
66+
public void testComplexGraph() {
67+
// Complex example: 6 courses
68+
// Dependencies: 5->2, 5->0, 4->0, 4->1, 2->3, 3->1
69+
int numCourses = 6;
70+
int[][] prerequisites = {
71+
{ 2, 5 },
72+
{ 0, 5 },
73+
{ 0, 4 },
74+
{ 1, 4 },
75+
{ 3, 2 },
76+
{ 1, 3 }
77+
};
78+
79+
int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites);
80+
81+
// Valid order: [5, 4, 2, 3, 1, 0]
82+
int[] expected = { 5, 4, 2, 3, 1, 0 };
83+
assertArrayEquals(expected, result, "Valid topological order expected such as [5, 4, 2, 3, 1, 0].");
84+
}
85+
}

0 commit comments

Comments
 (0)