From c5526907210421454f5c642bed2afccd28cb20c0 Mon Sep 17 00:00:00 2001 From: gowtham1412-p Date: Tue, 25 Nov 2025 04:10:17 +0000 Subject: [PATCH] Master This PR implements a DFS-based topological sorting algorithm for Directed Acyclic Graphs (DAGs) as requested in issue #6938. --- .../graphs/TopologicalSortDFS.java | 224 ++++++++++++++++++ .../graphs/TopologicalSortDFSTest.java | 189 +++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFS.java create mode 100644 src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFS.java b/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFS.java new file mode 100644 index 000000000000..3a6b2c325da5 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFS.java @@ -0,0 +1,224 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.*; + +/** + * Topological Sorting using Depth-First Search (DFS) + * + * Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering of vertices + * such that for every directed edge u -> v, vertex u comes before v in the ordering. + * + * Applications: + * - Task scheduling with dependencies + * - Build systems and compilation order + * - Course prerequisite ordering + * - Package dependency resolution + * + * Time Complexity: O(V + E) where V is vertices and E is edges + * Space Complexity: O(V) for the stack and visited array + * + * @author Your Name + */ +public class TopologicalSortDFS { + + private int vertices; + private List> adjList; + + /** + * Constructor to initialize the graph + * + * @param vertices Number of vertices in the graph + */ + public TopologicalSortDFS(int vertices) { + this.vertices = vertices; + adjList = new ArrayList<>(vertices); + + for (int i = 0; i < vertices; i++) { + adjList.add(new ArrayList<>()); + } + } + + /** + * Add a directed edge from source to destination + * + * @param src Source vertex + * @param dest Destination vertex + */ + public void addEdge(int src, int dest) { + if (src >= 0 && src < vertices && dest >= 0 && dest < vertices) { + adjList.get(src).add(dest); + } else { + throw new IllegalArgumentException("Invalid vertex index"); + } + } + + /** + * Recursive DFS helper function for topological sorting + * + * @param vertex Current vertex being visited + * @param visited Array to track visited vertices + * @param stack Stack to store the topological order + */ + private void topologicalSortUtil(int vertex, boolean[] visited, Stack stack) { + // Mark current vertex as visited + visited[vertex] = true; + + // Recursively visit all adjacent vertices + for (Integer neighbor : adjList.get(vertex)) { + if (!visited[neighbor]) { + topologicalSortUtil(neighbor, visited, stack); + } + } + + // Push current vertex to stack after visiting all neighbors + // This ensures vertices are in reverse topological order in the stack + stack.push(vertex); + } + + /** + * Perform topological sort on the graph + * + * @return List of vertices in topological order, or null if cycle detected + */ + public List topologicalSort() { + // Check if graph has cycles + if (hasCycle()) { + return null; // Topological sort not possible for cyclic graphs + } + + Stack stack = new Stack<>(); + boolean[] visited = new boolean[vertices]; + + // Call the recursive helper function for all unvisited vertices + for (int i = 0; i < vertices; i++) { + if (!visited[i]) { + topologicalSortUtil(i, visited, stack); + } + } + + // Pop all vertices from stack to get topological order + List result = new ArrayList<>(); + while (!stack.isEmpty()) { + result.add(stack.pop()); + } + + return result; + } + + /** + * Check if the graph contains a cycle using DFS + * + * @return true if cycle exists, false otherwise + */ + public boolean hasCycle() { + boolean[] visited = new boolean[vertices]; + boolean[] recStack = new boolean[vertices]; + + // Check for cycle starting from each vertex + for (int i = 0; i < vertices; i++) { + if (hasCycleUtil(i, visited, recStack)) { + return true; + } + } + + return false; + } + + /** + * Helper function to detect cycle using DFS + * + * @param vertex Current vertex + * @param visited Array to track visited vertices + * @param recStack Recursion stack to track vertices in current path + * @return true if cycle detected, false otherwise + */ + private boolean hasCycleUtil(int vertex, boolean[] visited, boolean[] recStack) { + // If vertex is in recursion stack, cycle detected + if (recStack[vertex]) { + return true; + } + + // If already visited and not in recursion stack, no cycle from this vertex + if (visited[vertex]) { + return false; + } + + // Mark vertex as visited and add to recursion stack + visited[vertex] = true; + recStack[vertex] = true; + + // Check all neighbors + for (Integer neighbor : adjList.get(vertex)) { + if (hasCycleUtil(neighbor, visited, recStack)) { + return true; + } + } + + // Remove vertex from recursion stack before backtracking + recStack[vertex] = false; + + return false; + } + + /** + * Get the adjacency list representation of the graph + * + * @return Adjacency list + */ + public List> getAdjList() { + return adjList; + } + + /** + * Main method with example usage + */ + public static void main(String[] args) { + // Example 1: Valid DAG + System.out.println("Example 1: Course Prerequisites"); + TopologicalSortDFS graph1 = new TopologicalSortDFS(6); + + // Course dependencies: must take course X before course Y + graph1.addEdge(5, 2); // Course 5 before Course 2 + graph1.addEdge(5, 0); // Course 5 before Course 0 + graph1.addEdge(4, 0); // Course 4 before Course 0 + graph1.addEdge(4, 1); // Course 4 before Course 1 + graph1.addEdge(2, 3); // Course 2 before Course 3 + graph1.addEdge(3, 1); // Course 3 before Course 1 + + List result1 = graph1.topologicalSort(); + if (result1 != null) { + System.out.println("Topological Order: " + result1); + } else { + System.out.println("Cycle detected! Topological sort not possible."); + } + + // Example 2: Cyclic Graph + System.out.println("\nExample 2: Cyclic Graph"); + TopologicalSortDFS graph2 = new TopologicalSortDFS(3); + graph2.addEdge(0, 1); + graph2.addEdge(1, 2); + graph2.addEdge(2, 0); // Creates a cycle + + List result2 = graph2.topologicalSort(); + if (result2 != null) { + System.out.println("Topological Order: " + result2); + } else { + System.out.println("Cycle detected! Topological sort not possible."); + } + + // Example 3: Build System Dependencies + System.out.println("\nExample 3: Build System"); + TopologicalSortDFS graph3 = new TopologicalSortDFS(4); + graph3.addEdge(0, 1); // Module 0 must be built before Module 1 + graph3.addEdge(0, 2); // Module 0 must be built before Module 2 + graph3.addEdge(1, 3); // Module 1 must be built before Module 3 + graph3.addEdge(2, 3); // Module 2 must be built before Module 3 + + List result3 = graph3.topologicalSort(); + if (result3 != null) { + System.out.println("Build Order: " + result3); + } else { + System.out.println("Cycle detected! Build order not possible."); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java b/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java new file mode 100644 index 000000000000..76ef1e4f188c --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java @@ -0,0 +1,189 @@ +package com.thealgorithms.datastructures.graphs; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +/** + * Test cases for TopologicalSortDFS + * + * @author Your Name + */ +class TopologicalSortDFSTest { + + @Test + void testSimpleDAG() { + // Create a simple DAG: 0 -> 1 -> 2 + TopologicalSortDFS graph = new TopologicalSortDFS(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should not be null for valid DAG"); + assertEquals(3, result.size(), "Result should contain all vertices"); + + // Verify order: 0 should come before 1, and 1 before 2 + assertTrue(result.indexOf(0) < result.indexOf(1), "Vertex 0 should come before vertex 1"); + assertTrue(result.indexOf(1) < result.indexOf(2), "Vertex 1 should come before vertex 2"); + } + + @Test + void testComplexDAG() { + // Create a more complex DAG + TopologicalSortDFS graph = new TopologicalSortDFS(6); + graph.addEdge(5, 2); + graph.addEdge(5, 0); + graph.addEdge(4, 0); + graph.addEdge(4, 1); + graph.addEdge(2, 3); + graph.addEdge(3, 1); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should not be null for valid DAG"); + assertEquals(6, result.size(), "Result should contain all vertices"); + + // Verify dependencies are respected + assertTrue(result.indexOf(5) < result.indexOf(2), "5 should come before 2"); + assertTrue(result.indexOf(5) < result.indexOf(0), "5 should come before 0"); + assertTrue(result.indexOf(4) < result.indexOf(0), "4 should come before 0"); + assertTrue(result.indexOf(4) < result.indexOf(1), "4 should come before 1"); + assertTrue(result.indexOf(2) < result.indexOf(3), "2 should come before 3"); + assertTrue(result.indexOf(3) < result.indexOf(1), "3 should come before 1"); + } + + @Test + void testCyclicGraph() { + // Create a cyclic graph: 0 -> 1 -> 2 -> 0 + TopologicalSortDFS graph = new TopologicalSortDFS(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 0); // Creates cycle + + List result = graph.topologicalSort(); + + assertNull(result, "Topological sort should return null for cyclic graph"); + assertTrue(graph.hasCycle(), "Graph should be detected as cyclic"); + } + + @Test + void testSingleVertex() { + // Graph with single vertex + TopologicalSortDFS graph = new TopologicalSortDFS(1); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should work for single vertex"); + assertEquals(1, result.size(), "Result should contain the single vertex"); + assertEquals(0, result.get(0), "Single vertex should be 0"); + } + + @Test + void testDisconnectedGraph() { + // Graph with disconnected components: 0 -> 1 and 2 -> 3 + TopologicalSortDFS graph = new TopologicalSortDFS(4); + graph.addEdge(0, 1); + graph.addEdge(2, 3); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should work for disconnected graph"); + assertEquals(4, result.size(), "Result should contain all vertices"); + + // Verify ordering within components + assertTrue(result.indexOf(0) < result.indexOf(1), "0 should come before 1"); + assertTrue(result.indexOf(2) < result.indexOf(3), "2 should come before 3"); + } + + @Test + void testEmptyGraph() { + // Graph with no edges + TopologicalSortDFS graph = new TopologicalSortDFS(3); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should work for graph with no edges"); + assertEquals(3, result.size(), "Result should contain all vertices"); + } + + @Test + void testSelfLoop() { + // Graph with self-loop: 0 -> 0 + TopologicalSortDFS graph = new TopologicalSortDFS(2); + graph.addEdge(0, 0); // Self-loop + + List result = graph.topologicalSort(); + + assertNull(result, "Topological sort should return null for graph with self-loop"); + assertTrue(graph.hasCycle(), "Self-loop should be detected as cycle"); + } + + @Test + void testLinearChain() { + // Linear chain: 0 -> 1 -> 2 -> 3 -> 4 + TopologicalSortDFS graph = new TopologicalSortDFS(5); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should work for linear chain"); + assertEquals(Arrays.asList(0, 1, 2, 3, 4), result, "Linear chain should maintain order"); + } + + @Test + void testMultiplePaths() { + // Graph with multiple paths: 0 -> 1, 0 -> 2, 1 -> 3, 2 -> 3 + TopologicalSortDFS graph = new TopologicalSortDFS(4); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + + List result = graph.topologicalSort(); + + assertNotNull(result, "Topological sort should work for graph with multiple paths"); + assertEquals(4, result.size(), "Result should contain all vertices"); + + // Verify all dependencies + assertTrue(result.indexOf(0) < result.indexOf(1), "0 should come before 1"); + assertTrue(result.indexOf(0) < result.indexOf(2), "0 should come before 2"); + assertTrue(result.indexOf(1) < result.indexOf(3), "1 should come before 3"); + assertTrue(result.indexOf(2) < result.indexOf(3), "2 should come before 3"); + } + + @Test + void testInvalidVertexIndex() { + TopologicalSortDFS graph = new TopologicalSortDFS(3); + + // Test invalid source vertex + assertThrows(IllegalArgumentException.class, () -> { + graph.addEdge(-1, 1); + }, "Should throw exception for negative vertex index"); + + // Test invalid destination vertex + assertThrows(IllegalArgumentException.class, () -> { + graph.addEdge(0, 5); + }, "Should throw exception for vertex index out of bounds"); + } + + @Test + void testComplexCycle() { + // More complex cycle: 0 -> 1 -> 2 -> 3 -> 1 + TopologicalSortDFS graph = new TopologicalSortDFS(4); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 1); // Creates cycle back to 1 + + List result = graph.topologicalSort(); + + assertNull(result, "Topological sort should return null for complex cycle"); + assertTrue(graph.hasCycle(), "Complex cycle should be detected"); + } +} \ No newline at end of file