diff --git a/Kruskal.java b/Kruskal.java new file mode 100644 index 000000000000..5c3251248e8d --- /dev/null +++ b/Kruskal.java @@ -0,0 +1,184 @@ +package com.thealgorithms.graphs; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Kruskal's Algorithm for finding Minimum Spanning Tree (MST) + * + * Kruskal's algorithm is a greedy algorithm that finds a minimum spanning tree + * for a connected weighted undirected graph. The algorithm works by sorting all + * edges by weight and adding them one by one to the MST, provided they don't + * create a cycle. + * + * Time Complexity: O(E log E) where E is the number of edges + * Space Complexity: O(V + E) where V is the number of vertices + * + * @author Raghu0703 + * @see Kruskal's Algorithm + */ +public final class Kruskal { + + private Kruskal() { + // Utility class, no instantiation + } + + /** + * Edge class representing an edge in the graph + */ + static class Edge implements Comparable { + int src; + int dest; + int weight; + + Edge(int src, int dest, int weight) { + this.src = src; + this.dest = dest; + this.weight = weight; + } + + @Override + public int compareTo(Edge other) { + return Integer.compare(this.weight, other.weight); + } + + @Override + public String toString() { + return src + " -- " + dest + " (weight: " + weight + ")"; + } + } + + /** + * Disjoint Set (Union-Find) data structure for cycle detection + */ + static class DisjointSet { + private final int[] parent; + private final int[] rank; + + DisjointSet(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + rank[i] = 0; + } + } + + /** + * Find operation with path compression + */ + int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); // Path compression + } + return parent[x]; + } + + /** + * Union operation by rank + */ + void union(int x, int y) { + int rootX = find(x); + int rootY = find(y); + + if (rootX != rootY) { + // Union by rank + if (rank[rootX] < rank[rootY]) { + parent[rootX] = rootY; + } else if (rank[rootX] > rank[rootY]) { + parent[rootY] = rootX; + } else { + parent[rootY] = rootX; + rank[rootX]++; + } + } + } + } + + /** + * Finds the Minimum Spanning Tree using Kruskal's Algorithm + * + * @param vertices Number of vertices in the graph + * @param edges List of all edges in the graph + * @return List of edges that form the MST + */ + public static List kruskalMST(int vertices, List edges) { + if (vertices <= 0) { + throw new IllegalArgumentException("Number of vertices must be positive"); + } + if (edges == null) { + throw new IllegalArgumentException("Edges list cannot be null"); + } + + List mst = new ArrayList<>(); + + // Sort edges by weight + Collections.sort(edges); + + // Initialize disjoint set + DisjointSet ds = new DisjointSet(vertices); + + // Process edges in sorted order + for (Edge edge : edges) { + int rootSrc = ds.find(edge.src); + int rootDest = ds.find(edge.dest); + + // If including this edge doesn't create a cycle + if (rootSrc != rootDest) { + mst.add(edge); + ds.union(rootSrc, rootDest); + + // MST is complete when we have (V-1) edges + if (mst.size() == vertices - 1) { + break; + } + } + } + + return mst; + } + + /** + * Calculates the total weight of the MST + * + * @param mst List of edges in the MST + * @return Total weight of the MST + */ + public static int getMSTWeight(List mst) { + int totalWeight = 0; + for (Edge edge : mst) { + totalWeight += edge.weight; + } + return totalWeight; + } + + /** + * Example usage and demonstration + */ + public static void main(String[] args) { + // Example graph with 5 vertices (0-4) + int vertices = 5; + List edges = new ArrayList<>(); + + // Add edges: (src, dest, weight) + edges.add(new Edge(0, 1, 10)); + edges.add(new Edge(0, 2, 6)); + edges.add(new Edge(0, 3, 5)); + edges.add(new Edge(1, 3, 15)); + edges.add(new Edge(2, 3, 4)); + edges.add(new Edge(2, 4, 8)); + edges.add(new Edge(3, 4, 12)); + + // Find MST + List mst = kruskalMST(vertices, edges); + + // Print results + System.out.println("Edges in the Minimum Spanning Tree:"); + for (Edge edge : mst) { + System.out.println(edge); + } + + System.out.println("\nTotal weight of MST: " + getMSTWeight(mst)); + } +} diff --git a/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java index 543fe2d02b50..6fc29c027eed 100644 --- a/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java +++ b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java @@ -2,151 +2,110 @@ /** * Sudoku Solver using Backtracking Algorithm - * Solves a 9x9 Sudoku puzzle by filling empty cells with valid digits (1-9) - * - * @author Navadeep0007 + * + * This class implements a backtracking algorithm to solve a 9x9 Sudoku puzzle. + * The algorithm systematically tries valid numbers in empty cells and backtracks + * when it encounters an invalid state. + * + * Time Complexity: O(9^m) where m is the number of empty cells + * Space Complexity: O(m) for the recursion stack + * + * @author Raghu0703 */ public final class SudokuSolver { - + private static final int GRID_SIZE = 9; - private static final int SUBGRID_SIZE = 3; private static final int EMPTY_CELL = 0; private SudokuSolver() { - // Utility class, prevent instantiation + // Utility class, no instantiation } /** - * Solves the Sudoku puzzle using backtracking - * - * @param board 9x9 Sudoku board with 0 representing empty cells - * @return true if puzzle is solved, false otherwise + * Checks if placing a number at a given position is valid according to Sudoku rules + * + * @param board the Sudoku board + * @param row the row index + * @param col the column index + * @param num the number to place + * @return true if the placement is valid, false otherwise */ - public static boolean solveSudoku(int[][] board) { - if (board == null || board.length != GRID_SIZE) { - return false; + private static boolean isValid(int[][] board, int row, int col, int num) { + // Check if num is already in the row + for (int i = 0; i < GRID_SIZE; i++) { + if (board[row][i] == num) { + return false; + } } - for (int row = 0; row < GRID_SIZE; row++) { - if (board[row].length != GRID_SIZE) { + // Check if num is already in the column + for (int i = 0; i < GRID_SIZE; i++) { + if (board[i][col] == num) { return false; } } - return solve(board); + // Check if num is already in the 3x3 subgrid + int subgridRowStart = row - row % 3; + int subgridColStart = col - col % 3; + for (int i = subgridRowStart; i < subgridRowStart + 3; i++) { + for (int j = subgridColStart; j < subgridColStart + 3; j++) { + if (board[i][j] == num) { + return false; + } + } + } + + return true; } /** - * Recursive helper method to solve the Sudoku puzzle - * - * @param board the Sudoku board - * @return true if solution is found, false otherwise + * Solves the Sudoku puzzle using backtracking + * + * @param board the Sudoku board (0 represents empty cells) + * @return true if the puzzle is solvable, false otherwise */ - private static boolean solve(int[][] board) { + public static boolean solveSudoku(int[][] board) { for (int row = 0; row < GRID_SIZE; row++) { for (int col = 0; col < GRID_SIZE; col++) { + // Find an empty cell if (board[row][col] == EMPTY_CELL) { - for (int number = 1; number <= GRID_SIZE; number++) { - if (isValidPlacement(board, row, col, number)) { - board[row][col] = number; + // Try numbers 1 through 9 + for (int num = 1; num <= GRID_SIZE; num++) { + if (isValid(board, row, col, num)) { + // Place the number + board[row][col] = num; - if (solve(board)) { + // Recursively try to solve the rest + if (solveSudoku(board)) { return true; } - // Backtrack + // Backtrack: undo the placement board[row][col] = EMPTY_CELL; } } + // No valid number found, trigger backtracking return false; } } } + // All cells filled successfully return true; } /** - * Checks if placing a number at given position is valid - * - * @param board the Sudoku board - * @param row row index - * @param col column index - * @param number number to place (1-9) - * @return true if placement is valid, false otherwise + * Prints the Sudoku board in a formatted manner + * + * @param board the Sudoku board to print */ - private static boolean isValidPlacement(int[][] board, int row, int col, int number) { - return !isNumberInRow(board, row, number) && !isNumberInColumn(board, col, number) && !isNumberInSubgrid(board, row, col, number); - } - - /** - * Checks if number exists in the given row - * - * @param board the Sudoku board - * @param row row index - * @param number number to check - * @return true if number exists in row, false otherwise - */ - private static boolean isNumberInRow(int[][] board, int row, int number) { - for (int col = 0; col < GRID_SIZE; col++) { - if (board[row][col] == number) { - return true; - } - } - return false; - } - - /** - * Checks if number exists in the given column - * - * @param board the Sudoku board - * @param col column index - * @param number number to check - * @return true if number exists in column, false otherwise - */ - private static boolean isNumberInColumn(int[][] board, int col, int number) { - for (int row = 0; row < GRID_SIZE; row++) { - if (board[row][col] == number) { - return true; - } - } - return false; - } - - /** - * Checks if number exists in the 3x3 subgrid - * - * @param board the Sudoku board - * @param row row index - * @param col column index - * @param number number to check - * @return true if number exists in subgrid, false otherwise - */ - private static boolean isNumberInSubgrid(int[][] board, int row, int col, int number) { - int subgridRowStart = row - row % SUBGRID_SIZE; - int subgridColStart = col - col % SUBGRID_SIZE; - - for (int i = subgridRowStart; i < subgridRowStart + SUBGRID_SIZE; i++) { - for (int j = subgridColStart; j < subgridColStart + SUBGRID_SIZE; j++) { - if (board[i][j] == number) { - return true; - } - } - } - return false; - } - - /** - * Prints the Sudoku board - * - * @param board the Sudoku board - */ - public static void printBoard(int[][] board) { + private static void printBoard(int[][] board) { for (int row = 0; row < GRID_SIZE; row++) { - if (row % SUBGRID_SIZE == 0 && row != 0) { + if (row % 3 == 0 && row != 0) { System.out.println("-----------"); } for (int col = 0; col < GRID_SIZE; col++) { - if (col % SUBGRID_SIZE == 0 && col != 0) { + if (col % 3 == 0 && col != 0) { System.out.print("|"); } System.out.print(board[row][col]); @@ -154,4 +113,32 @@ public static void printBoard(int[][] board) { System.out.println(); } } + + /** + * Example usage of the Sudoku Solver + */ + public static void main(String[] args) { + // Example Sudoku puzzle (0 represents empty cells) + int[][] board = { + {5, 3, 0, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, 2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 8, 0, 0, 7, 9} + }; + + System.out.println("Original Sudoku Puzzle:"); + printBoard(board); + + if (solveSudoku(board)) { + System.out.println("\nSolved Sudoku:"); + printBoard(board); + } else { + System.out.println("\nNo solution exists for this Sudoku puzzle."); + } + } } 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..76c81022c553 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFS.java @@ -0,0 +1,159 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * Topological Sorting using Depth-First Search (DFS) + * + *

Topological sorting for a 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. + * + *

This algorithm uses DFS traversal to compute the topological order. It maintains a visited + * set to track processed nodes and a recursion stack to detect cycles. + * + *

Time Complexity: O(V + E) where V is the number of vertices and E is the number of edges + *

Space Complexity: O(V) for the recursion stack and data structures + * + *

Applications: + * - Task scheduling with dependencies + * - Build systems (e.g., makefiles) + * - Course prerequisite resolution + * - Compilation order in software projects + * + * @author Raghu0703 + * @see Topological Sorting + */ +public final class TopologicalSortDFS { + + private TopologicalSortDFS() { + } + + /** + * Performs topological sorting on a directed graph using DFS + * + * @param graph the adjacency list representation of the directed graph + * @return a list containing vertices in topological order + * @throws IllegalArgumentException if the graph contains a cycle + */ + public static List topologicalSort(List> graph) { + if (graph == null || graph.isEmpty()) { + return new ArrayList<>(); + } + + int vertices = graph.size(); + boolean[] visited = new boolean[vertices]; + boolean[] recursionStack = new boolean[vertices]; + Stack stack = new Stack<>(); + + // Perform DFS from all unvisited vertices + for (int i = 0; i < vertices; i++) { + if (!visited[i]) { + if (hasCycleDFS(graph, i, visited, recursionStack, stack)) { + throw new IllegalArgumentException("Graph contains a cycle. Topological sort is not possible for cyclic graphs."); + } + } + } + + // Pop all vertices from stack to get topological order + List result = new ArrayList<>(); + while (!stack.isEmpty()) { + result.add(stack.pop()); + } + + return result; + } + + /** + * Helper method to perform DFS and detect cycles + * + * @param graph the adjacency list representation of the graph + * @param vertex current vertex being processed + * @param visited array to track visited vertices + * @param recursionStack array to track vertices in current recursion path + * @param stack stack to store vertices in reverse topological order + * @return true if a cycle is detected, false otherwise + */ + private static boolean hasCycleDFS(List> graph, int vertex, boolean[] visited, boolean[] recursionStack, Stack stack) { + // Mark current node as visited and part of recursion stack + visited[vertex] = true; + recursionStack[vertex] = true; + + // Recur for all adjacent vertices + List neighbors = graph.get(vertex); + if (neighbors != null) { + for (Integer neighbor : neighbors) { + // If neighbor is not visited, recursively check for cycles + if (!visited[neighbor]) { + if (hasCycleDFS(graph, neighbor, visited, recursionStack, stack)) { + return true; + } + } else if (recursionStack[neighbor]) { + // If neighbor is in recursion stack, cycle detected + return true; + } + } + } + + // Remove vertex from recursion stack and push to result stack + recursionStack[vertex] = false; + stack.push(vertex); + return false; + } + + /** + * Checks if the given directed graph is a Directed Acyclic Graph (DAG) + * + * @param graph the adjacency list representation of the directed graph + * @return true if graph is a DAG, false if it contains a cycle + */ + public static boolean isDAG(List> graph) { + if (graph == null || graph.isEmpty()) { + return true; + } + + int vertices = graph.size(); + boolean[] visited = new boolean[vertices]; + boolean[] recursionStack = new boolean[vertices]; + + for (int i = 0; i < vertices; i++) { + if (!visited[i]) { + if (detectCycle(graph, i, visited, recursionStack)) { + return false; + } + } + } + return true; + } + + /** + * Helper method to detect cycle in a directed graph + * + * @param graph the adjacency list representation of the graph + * @param vertex current vertex being processed + * @param visited array to track visited vertices + * @param recursionStack array to track vertices in current recursion path + * @return true if a cycle is detected, false otherwise + */ + private static boolean detectCycle(List> graph, int vertex, boolean[] visited, boolean[] recursionStack) { + visited[vertex] = true; + recursionStack[vertex] = true; + + List neighbors = graph.get(vertex); + if (neighbors != null) { + for (Integer neighbor : neighbors) { + if (!visited[neighbor]) { + if (detectCycle(graph, neighbor, visited, recursionStack)) { + return true; + } + } else if (recursionStack[neighbor]) { + return true; + } + } + } + + recursionStack[vertex] = false; + return false; + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java index 75d3eae08629..171ddbee809c 100644 --- a/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java +++ b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java @@ -1,53 +1,138 @@ package com.thealgorithms.backtracking; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; class SudokuSolverTest { @Test - void testSolveSudokuEasyPuzzle() { - int[][] board = {{5, 3, 0, 0, 7, 0, 0, 0, 0}, {6, 0, 0, 1, 9, 5, 0, 0, 0}, {0, 9, 8, 0, 0, 0, 0, 6, 0}, {8, 0, 0, 0, 6, 0, 0, 0, 3}, {4, 0, 0, 8, 0, 3, 0, 0, 1}, {7, 0, 0, 0, 2, 0, 0, 0, 6}, {0, 6, 0, 0, 0, 0, 2, 8, 0}, {0, 0, 0, 4, 1, 9, 0, 0, 5}, {0, 0, 0, 0, 8, 0, 0, 7, 9}}; + void testSolvableSudoku() { + int[][] board = { + {5, 3, 0, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, 2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 8, 0, 0, 7, 9} + }; + + assertTrue(SudokuSolver.solveSudoku(board), "The Sudoku puzzle should be solvable"); - assertTrue(SudokuSolver.solveSudoku(board)); + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + assertTrue(board[i][j] >= 1 && board[i][j] <= 9, "All cells should contain numbers 1-9"); + } + } - int[][] expected = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; + int[][] expectedSolution = { + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9} + }; - assertArrayEquals(expected, board); + assertArrayEquals(expectedSolution, board, "The solved board should match the expected solution"); } @Test - void testSolveSudokuHardPuzzle() { - int[][] board = {{0, 0, 0, 0, 0, 0, 6, 8, 0}, {0, 0, 0, 0, 7, 3, 0, 0, 9}, {3, 0, 9, 0, 0, 0, 0, 4, 5}, {4, 9, 0, 0, 0, 0, 0, 0, 0}, {8, 0, 3, 0, 5, 0, 9, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 3, 6}, {9, 6, 0, 0, 0, 0, 3, 0, 8}, {7, 0, 0, 6, 8, 0, 0, 0, 0}, {0, 2, 8, 0, 0, 0, 0, 0, 0}}; + void testUnsolvableSudoku() { + int[][] board = { + {5, 5, 0, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, 2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 8, 0, 0, 7, 9} + }; - assertTrue(SudokuSolver.solveSudoku(board)); + assertFalse(SudokuSolver.solveSudoku(board), "Invalid Sudoku puzzle should be unsolvable"); } @Test - void testSolveSudokuAlreadySolved() { - int[][] board = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; + void testAlreadySolvedSudoku() { + int[][] board = { + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9} + }; - assertTrue(SudokuSolver.solveSudoku(board)); + assertTrue(SudokuSolver.solveSudoku(board), "Already solved Sudoku should return true"); + + int[][] expected = { + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9} + }; + + assertArrayEquals(expected, board, "Already solved board should remain unchanged"); } @Test - void testSolveSudokuInvalidSize() { - int[][] board = {{1, 2, 3}, {4, 5, 6}}; - assertFalse(SudokuSolver.solveSudoku(board)); - } + void testEmptySudoku() { + int[][] board = { + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0} + }; - @Test - void testSolveSudokuNullBoard() { - assertFalse(SudokuSolver.solveSudoku(null)); + assertTrue(SudokuSolver.solveSudoku(board), "Empty Sudoku should be solvable"); + + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + assertTrue(board[i][j] >= 1 && board[i][j] <= 9, "All cells should be filled with 1-9"); + } + } } @Test - void testSolveSudokuEmptyBoard() { - int[][] board = {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}}; + void testDifficultSudoku() { + int[][] board = { + {0, 0, 0, 0, 0, 0, 6, 8, 0}, + {0, 0, 0, 0, 7, 3, 0, 0, 9}, + {3, 0, 9, 0, 0, 0, 0, 4, 5}, + {4, 9, 0, 0, 0, 0, 0, 0, 0}, + {8, 0, 3, 0, 5, 0, 9, 0, 2}, + {0, 0, 0, 0, 0, 0, 0, 3, 6}, + {9, 6, 0, 0, 0, 0, 3, 0, 8}, + {7, 0, 0, 6, 8, 0, 0, 0, 0}, + {0, 2, 8, 0, 0, 0, 0, 0, 0} + }; - assertTrue(SudokuSolver.solveSudoku(board)); + assertTrue(SudokuSolver.solveSudoku(board), "Difficult Sudoku puzzle should be solvable"); + + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + assertTrue(board[i][j] >= 1 && board[i][j] <= 9, "All cells should contain numbers 1-9"); + } + } } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java new file mode 100644 index 000000000000..8b4aeb581d85 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/TopologicalSortDFSTest.java @@ -0,0 +1,264 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Test cases for TopologicalSortDFS + * + * @author Raghu0703 + */ +class TopologicalSortDFSTest { + + /** + * Helper method to create a graph with given number of vertices + */ + private List> createGraph(int vertices) { + List> graph = new ArrayList<>(); + for (int i = 0; i < vertices; i++) { + graph.add(new ArrayList<>()); + } + return graph; + } + + /** + * Helper method to add a directed edge to the graph + */ + private void addEdge(List> graph, int from, int to) { + graph.get(from).add(to); + } + + @Test + void testSimpleDAG() { + // Graph: 0 → 1 → 2 + // ↓ + // 3 + List> graph = createGraph(4); + addEdge(graph, 0, 1); + addEdge(graph, 0, 3); + addEdge(graph, 1, 2); + + List result = TopologicalSortDFS.topologicalSort(graph); + + // Valid topological orders: [0, 1, 3, 2] or [0, 3, 1, 2] + assertEquals(4, result.size()); + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(0) < result.indexOf(3)); + assertTrue(result.indexOf(1) < result.indexOf(2)); + } + + @Test + void testComplexDAG() { + // Graph representing course dependencies + // 5 → 2 → 3 → 1 + // ↓ ↑ + // 0 → 4 → + List> graph = createGraph(6); + addEdge(graph, 5, 2); + addEdge(graph, 5, 0); + addEdge(graph, 4, 0); + addEdge(graph, 4, 1); + addEdge(graph, 2, 3); + addEdge(graph, 3, 1); + + List result = TopologicalSortDFS.topologicalSort(graph); + + // Verify dependencies are maintained + assertEquals(6, result.size()); + assertTrue(result.indexOf(5) < result.indexOf(2)); + assertTrue(result.indexOf(5) < result.indexOf(0)); + assertTrue(result.indexOf(4) < result.indexOf(1)); + assertTrue(result.indexOf(2) < result.indexOf(3)); + assertTrue(result.indexOf(3) < result.indexOf(1)); + } + + @Test + void testLinearDAG() { + // Graph: 0 → 1 → 2 → 3 → 4 + List> graph = createGraph(5); + addEdge(graph, 0, 1); + addEdge(graph, 1, 2); + addEdge(graph, 2, 3); + addEdge(graph, 3, 4); + + List result = TopologicalSortDFS.topologicalSort(graph); + + assertEquals(Arrays.asList(0, 1, 2, 3, 4), result); + } + + @Test + void testDisconnectedDAG() { + // Graph with disconnected components: + // 0 → 1 2 → 3 + List> graph = createGraph(4); + addEdge(graph, 0, 1); + addEdge(graph, 2, 3); + + List result = TopologicalSortDFS.topologicalSort(graph); + + assertEquals(4, result.size()); + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(2) < result.indexOf(3)); + } + + @Test + void testSingleVertex() { + // Graph with single vertex and no edges + List> graph = createGraph(1); + + List result = TopologicalSortDFS.topologicalSort(graph); + + assertEquals(Arrays.asList(0), result); + } + + @Test + void testEmptyGraph() { + // Empty graph + List> graph = new ArrayList<>(); + + List result = TopologicalSortDFS.topologicalSort(graph); + + assertTrue(result.isEmpty()); + } + + @Test + void testGraphWithSelfLoop() { + // Graph with self loop: 0 → 0 + List> graph = createGraph(1); + addEdge(graph, 0, 0); + + assertThrows(IllegalArgumentException.class, () -> { + TopologicalSortDFS.topologicalSort(graph); + }); + } + + @Test + void testSimpleCycle() { + // Graph with cycle: 0 → 1 → 2 → 0 + List> graph = createGraph(3); + addEdge(graph, 0, 1); + addEdge(graph, 1, 2); + addEdge(graph, 2, 0); + + assertThrows(IllegalArgumentException.class, () -> { + TopologicalSortDFS.topologicalSort(graph); + }); + } + + @Test + void testComplexCycle() { + // Graph with cycle: 0 → 1 → 2 + // ↑ ↓ + // 4 ← 3 ← + List> graph = createGraph(5); + addEdge(graph, 0, 1); + addEdge(graph, 1, 2); + addEdge(graph, 2, 3); + addEdge(graph, 3, 4); + addEdge(graph, 4, 0); + + assertThrows(IllegalArgumentException.class, () -> { + TopologicalSortDFS.topologicalSort(graph); + }); + } + + @Test + void testIsDAGWithValidGraph() { + // Valid DAG: 0 → 1 → 2 + List> graph = createGraph(3); + addEdge(graph, 0, 1); + addEdge(graph, 1, 2); + + assertTrue(TopologicalSortDFS.isDAG(graph)); + } + + @Test + void testIsDAGWithCycle() { + // Graph with cycle: 0 → 1 → 2 → 0 + List> graph = createGraph(3); + addEdge(graph, 0, 1); + addEdge(graph, 1, 2); + addEdge(graph, 2, 0); + + assertFalse(TopologicalSortDFS.isDAG(graph)); + } + + @Test + void testIsDAGWithEmptyGraph() { + List> graph = new ArrayList<>(); + assertTrue(TopologicalSortDFS.isDAG(graph)); + } + + @Test + void testIsDAGWithSelfLoop() { + List> graph = createGraph(1); + addEdge(graph, 0, 0); + + assertFalse(TopologicalSortDFS.isDAG(graph)); + } + + @Test + void testNullGraph() { + List result = TopologicalSortDFS.topologicalSort(null); + assertTrue(result.isEmpty()); + } + + @Test + void testTaskSchedulingScenario() { + // Real-world scenario: Build system dependencies + // Task 0: Download dependencies + // Task 1: Compile source + // Task 2: Run tests + // Task 3: Package + // Task 4: Deploy + // Dependencies: 0 → 1 → 2 → 3 → 4 + // 0 → 2 (tests need dependencies) + List> graph = createGraph(5); + addEdge(graph, 0, 1); // Download before compile + addEdge(graph, 1, 2); // Compile before test + addEdge(graph, 2, 3); // Test before package + addEdge(graph, 3, 4); // Package before deploy + addEdge(graph, 0, 2); // Download before test + + List result = TopologicalSortDFS.topologicalSort(graph); + + // Verify all dependencies are respected + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(1) < result.indexOf(2)); + assertTrue(result.indexOf(2) < result.indexOf(3)); + assertTrue(result.indexOf(3) < result.indexOf(4)); + assertTrue(result.indexOf(0) < result.indexOf(2)); + } + + @Test + void testCoursePrerequisiteScenario() { + // Course prerequisites scenario + // Course 0: Intro to CS + // Course 1: Data Structures + // Course 2: Algorithms + // Course 3: Advanced Algorithms + // Course 4: Machine Learning + List> graph = createGraph(5); + addEdge(graph, 0, 1); // Intro → Data Structures + addEdge(graph, 1, 2); // Data Structures → Algorithms + addEdge(graph, 2, 3); // Algorithms → Advanced Algorithms + addEdge(graph, 2, 4); // Algorithms → Machine Learning + addEdge(graph, 1, 4); // Data Structures → Machine Learning + + List result = TopologicalSortDFS.topologicalSort(graph); + + // Verify course prerequisites + assertTrue(result.indexOf(0) < result.indexOf(1)); + assertTrue(result.indexOf(1) < result.indexOf(2)); + assertTrue(result.indexOf(2) < result.indexOf(3)); + assertTrue(result.indexOf(2) < result.indexOf(4)); + assertTrue(result.indexOf(1) < result.indexOf(4)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/nano b/src/test/java/com/thealgorithms/datastructures/graphs/nano new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/java/com/thealgorithms/graphs/KruskalTest.java b/src/test/java/com/thealgorithms/graphs/KruskalTest.java new file mode 100644 index 000000000000..059ee50b2428 --- /dev/null +++ b/src/test/java/com/thealgorithms/graphs/KruskalTest.java @@ -0,0 +1,156 @@ +package com.thealgorithms.graphs; + +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 com.thealgorithms.graphs.Kruskal.Edge; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Test cases for Kruskal's Algorithm + * + * @author Raghu0703 + */ +class KruskalTest { + + @Test + void testSimpleGraph() { + // Graph with 4 vertices + int vertices = 4; + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 10)); + edges.add(new Edge(0, 2, 6)); + edges.add(new Edge(0, 3, 5)); + edges.add(new Edge(1, 3, 15)); + edges.add(new Edge(2, 3, 4)); + + List mst = Kruskal.kruskalMST(vertices, edges); + + // MST should have exactly 3 edges (V-1) + assertEquals(3, mst.size()); + + // Total weight should be 19 (4 + 5 + 10) + assertEquals(19, Kruskal.getMSTWeight(mst)); + } + + @Test + void testDisconnectedComponents() { + // Graph with 5 vertices but only 3 can be connected + int vertices = 5; + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 1)); + edges.add(new Edge(1, 2, 2)); + edges.add(new Edge(3, 4, 3)); + + List mst = Kruskal.kruskalMST(vertices, edges); + + // MST should have 3 edges (not complete spanning tree due to disconnection) + assertEquals(3, mst.size()); + assertEquals(6, Kruskal.getMSTWeight(mst)); + } + + @Test + void testSingleEdge() { + int vertices = 2; + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 5)); + + List mst = Kruskal.kruskalMST(vertices, edges); + + assertEquals(1, mst.size()); + assertEquals(5, Kruskal.getMSTWeight(mst)); + } + + @Test + void testCompleteGraph() { + // Complete graph with 4 vertices + int vertices = 4; + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 1)); + edges.add(new Edge(0, 2, 4)); + edges.add(new Edge(0, 3, 3)); + edges.add(new Edge(1, 2, 2)); + edges.add(new Edge(1, 3, 5)); + edges.add(new Edge(2, 3, 6)); + + List mst = Kruskal.kruskalMST(vertices, edges); + + // MST should have 3 edges + assertEquals(3, mst.size()); + + // Total weight should be 6 (1 + 2 + 3) + assertEquals(6, Kruskal.getMSTWeight(mst)); + } + + @Test + void testGraphWithEqualWeights() { + int vertices = 3; + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 5)); + edges.add(new Edge(1, 2, 5)); + edges.add(new Edge(0, 2, 5)); + + List mst = Kruskal.kruskalMST(vertices, edges); + + assertEquals(2, mst.size()); + assertEquals(10, Kruskal.getMSTWeight(mst)); + } + + @Test + void testEmptyGraph() { + int vertices = 3; + List edges = new ArrayList<>(); + + List mst = Kruskal.kruskalMST(vertices, edges); + + assertTrue(mst.isEmpty()); + } + + @Test + void testInvalidVertexCount() { + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 10)); + + assertThrows(IllegalArgumentException.class, () -> { + Kruskal.kruskalMST(0, edges); + }); + + assertThrows(IllegalArgumentException.class, () -> { + Kruskal.kruskalMST(-5, edges); + }); + } + + @Test + void testNullEdges() { + assertThrows(IllegalArgumentException.class, () -> { + Kruskal.kruskalMST(5, null); + }); + } + + @Test + void testLargerGraph() { + // Graph with 6 vertices + int vertices = 6; + List edges = new ArrayList<>(); + edges.add(new Edge(0, 1, 4)); + edges.add(new Edge(0, 2, 4)); + edges.add(new Edge(1, 2, 2)); + edges.add(new Edge(1, 3, 5)); + edges.add(new Edge(2, 3, 8)); + edges.add(new Edge(2, 4, 10)); + edges.add(new Edge(3, 4, 2)); + edges.add(new Edge(3, 5, 6)); + edges.add(new Edge(4, 5, 3)); + + List mst = Kruskal.kruskalMST(vertices, edges); + + // MST should have 5 edges (6-1) + assertEquals(5, mst.size()); + + // Verify minimum total weight + assertEquals(16, Kruskal.getMSTWeight(mst)); + } +}