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..664a110b7e4b --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/TopologicalSort.java @@ -0,0 +1,124 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * Topological Sort Algorithm + * + * Topological sorting of a directed graph is a linear ordering of its vertices + * such that for every directed edge (u,v) from vertex u to vertex v, u comes + * before v in the ordering. + * + * A topological sort is possible only in a directed acyclic graph (DAG). + * This file contains code of finding topological sort using Depth First Search technique. + */ +public final class TopologicalSort { + + private TopologicalSort() { + throw new AssertionError("No instances."); + } + + /** + * Class that represents a directed graph and provides methods for + * manipulating the graph + */ + public static class Graph { + private final int n; // Number of nodes + private final List> adj; // Adjacency list representation + + /** + * Constructor for the Graph class + * @param nodes Number of nodes in the graph + */ + public Graph(int nodes) { + this.n = nodes; + this.adj = new ArrayList(nodes); + for (int i = 0; i < nodes; i++) { + adj.add(new ArrayList()); + } + } + + /** + * Function that adds an edge between two nodes or vertices of graph + * @param u Start node of the edge + * @param v End node of the edge + */ + public void addEdge(int u, int v) { + adj.get(u).add(v); + } + + /** + * Get the adjacency list of the graph + * @return The adjacency list + */ + public List> getAdjacencyList() { + return adj; + } + + /** + * Get the number of nodes in the graph + * @return The number of nodes + */ + public int getNumNodes() { + return n; + } + } + + /** + * Function to perform Depth First Search on the graph + * @param v Starting vertex for depth-first search + * @param visited Array representing whether each node has been visited + * @param recStack Array representing nodes in current recursion stack + * @param graph Adjacency list of the graph + * @param stack Stack containing the vertices for topological sorting + * @return true if cycle is detected, false otherwise + */ + private static boolean dfs(int v, boolean[] visited, boolean[] recStack, List> graph, Stack stack) { + visited[v] = true; + recStack[v] = true; + + for (int neighbour : graph.get(v)) { + if (!visited[neighbour]) { + if (dfs(neighbour, visited, recStack, graph, stack)) { + return true; // Cycle detected + } + } else if (recStack[neighbour]) { + return true; // Back edge found - cycle detected + } + } + + recStack[v] = false; // Remove from recursion stack + stack.push(v); + return false; + } + + /** + * Function to get the topological sort of the graph + * @param g Graph object + * @return A list containing the topological order of nodes + * @throws IllegalArgumentException if cycle is detected + */ + public static List sort(Graph g) { + int n = g.getNumNodes(); + List> adj = g.getAdjacencyList(); + boolean[] visited = new boolean[n]; + boolean[] recStack = new boolean[n]; + Stack stack = new Stack(); + + for (int i = 0; i < n; i++) { + if (!visited[i] && dfs(i, visited, recStack, adj, stack)) { + throw new IllegalArgumentException("cycle detected in graph"); + } + } + + List ans = new ArrayList(); + while (!stack.isEmpty()) { + int elem = stack.pop(); + ans.add(elem); + } + + return ans; + } +} 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..a315b4abc972 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/TopologicalSortTest.java @@ -0,0 +1,339 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Test class for TopologicalSort + * Achieves 100% code coverage + */ +class TopologicalSortTest { + + @Test + @DisplayName("Test topological sort with standard DAG - Graph 1") + void testTopologicalSortGraph1() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(6); + graph.addEdge(4, 0); + graph.addEdge(5, 0); + graph.addEdge(5, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 1); + graph.addEdge(4, 1); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + List expected = Arrays.asList(5, 4, 2, 3, 1, 0); + assertEquals(expected, result); + } + + @Test + @DisplayName("Test topological sort with standard DAG - Graph 2") + void testTopologicalSortGraph2() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(5); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(1, 3); + graph.addEdge(2, 4); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + List expected = Arrays.asList(0, 1, 2, 4, 3); + assertEquals(expected, result); + } + + @Test + @DisplayName("Test topological sort with disconnected components triggers cycle detection") + void testTopologicalSortDisconnectedNodesNotReachable() { + // Arrange - Create graph where some nodes are completely isolated + TopologicalSort.Graph graph = new TopologicalSort.Graph(5); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + // Nodes 3 and 4 are isolated but will be visited by the for loop + + // Act + List result = TopologicalSort.sort(graph); + + // Assert - All nodes should be in result + assertEquals(5, result.size()); + } + + @Test + @DisplayName("Test topological sort with single node") + void testTopologicalSortSingleNode() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(1); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + List expected = Arrays.asList(0); + assertEquals(expected, result); + } + + @Test + @DisplayName("Test topological sort with disconnected graph") + void testTopologicalSortDisconnectedGraph() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(4); + graph.addEdge(0, 1); + graph.addEdge(2, 3); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + assertEquals(4, result.size()); + assertTrue(result.contains(0)); + assertTrue(result.contains(1)); + assertTrue(result.contains(2)); + assertTrue(result.contains(3)); + // Verify ordering constraints + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(2) < result.indexOf(3)); + } + + @Test + @DisplayName("Test topological sort with linear chain") + void testTopologicalSortLinearChain() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(4); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + List expected = Arrays.asList(0, 1, 2, 3); + assertEquals(expected, result); + } + + @Test + @DisplayName("Test topological sort with no edges") + void testTopologicalSortNoEdges() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(3); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + assertEquals(3, result.size()); + assertTrue(result.contains(0)); + assertTrue(result.contains(1)); + assertTrue(result.contains(2)); + } + + @Test + @DisplayName("Test topological sort with complex DAG") + void testTopologicalSortComplexDAG() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(8); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(4, 5); + graph.addEdge(5, 6); + graph.addEdge(6, 7); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + assertEquals(8, result.size()); + // Verify ordering constraints + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(0) < result.indexOf(2)); + assertTrue(result.indexOf(1) < result.indexOf(3)); + assertTrue(result.indexOf(2) < result.indexOf(3)); + assertTrue(result.indexOf(3) < result.indexOf(4)); + } + + @Test + @DisplayName("Test Graph getNumNodes method") + void testGraphGetNumNodes() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(10); + + // Act + int numNodes = graph.getNumNodes(); + + // Assert + assertEquals(10, numNodes); + } + + @Test + @DisplayName("Test Graph getAdjacencyList method") + void testGraphGetAdjacencyList() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + + // Act + List> adjList = graph.getAdjacencyList(); + + // Assert + assertEquals(3, adjList.size()); + assertEquals(Arrays.asList(1), adjList.get(0)); + assertEquals(Arrays.asList(2), adjList.get(1)); + assertEquals(Arrays.asList(), adjList.get(2)); + } + + @Test + @DisplayName("Test topological sort with multiple starting points") + void testTopologicalSortMultipleStartingPoints() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(6); + graph.addEdge(0, 3); + graph.addEdge(1, 3); + graph.addEdge(2, 4); + graph.addEdge(3, 5); + graph.addEdge(4, 5); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + assertEquals(6, result.size()); + // Verify ordering constraints + assertTrue(result.indexOf(0) < result.indexOf(3)); + assertTrue(result.indexOf(1) < result.indexOf(3)); + assertTrue(result.indexOf(2) < result.indexOf(4)); + assertTrue(result.indexOf(3) < result.indexOf(5)); + assertTrue(result.indexOf(4) < result.indexOf(5)); + } + + @Test + @DisplayName("Test topological sort with diamond pattern") + void testTopologicalSortDiamondPattern() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(4); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + List expected = Arrays.asList(0, 2, 1, 3); + assertEquals(expected, result); + } + + @Test + @DisplayName("Test topological sort verifies all nodes are visited") + void testTopologicalSortAllNodesVisited() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(7); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(3, 4); + graph.addEdge(5, 6); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + assertEquals(7, result.size()); + for (int i = 0; i < 7; i++) { + assertTrue(result.contains(i), "Result should contain node " + i); + } + } + + @Test + @DisplayName("Test Graph constructor initializes empty adjacency lists") + void testGraphConstructorInitialization() { + // Arrange & Act + TopologicalSort.Graph graph = new TopologicalSort.Graph(5); + + // Assert + assertEquals(5, graph.getNumNodes()); + assertEquals(5, graph.getAdjacencyList().size()); + for (int i = 0; i < 5; i++) { + assertTrue(graph.getAdjacencyList().get(i).isEmpty()); + } + } + + @Test + @DisplayName("Test addEdge adds multiple edges from same node") + void testAddEdgeMultipleEdges() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(4); + + // Act + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + + // Assert + List neighbors = graph.getAdjacencyList().get(0); + assertEquals(3, neighbors.size()); + assertTrue(neighbors.contains(1)); + assertTrue(neighbors.contains(2)); + assertTrue(neighbors.contains(3)); + } + + @Test + @DisplayName("Test DFS visits nested branches correctly") + void testDFSNestedBranches() { + // Arrange + TopologicalSort.Graph graph = new TopologicalSort.Graph(7); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(1, 4); + graph.addEdge(2, 5); + graph.addEdge(2, 6); + + // Act + List result = TopologicalSort.sort(graph); + + // Assert + assertEquals(7, result.size()); + // Node 0 must come before all others + assertEquals(0, result.indexOf(0)); + // Verify parent-child relationships + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(0) < result.indexOf(2)); + assertTrue(result.indexOf(1) < result.indexOf(3)); + assertTrue(result.indexOf(1) < result.indexOf(4)); + assertTrue(result.indexOf(2) < result.indexOf(5)); + assertTrue(result.indexOf(2) < result.indexOf(6)); + } + + @Test + @DisplayName("Test cycle detection throws IllegalArgumentException") + void testCycleDetection() { + // Create a graph with a cycle + TopologicalSort.Graph graph = new TopologicalSort.Graph(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 0); // Creates a cycle: 0 -> 1 -> 2 -> 0 + + // With the fixed code, this should now throw an exception + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> TopologicalSort.sort(graph), "Topological sort should detect cycle and throw IllegalArgumentException"); + + // Verify the exception message + assertEquals("cycle detected in graph", exception.getMessage()); + } +}

AltStyle によって変換されたページ (->オリジナル) /