mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-07 09:45:04 +08:00
Add tests, enhance class & function documentation for KnightsTour (#5591)
This commit is contained in:
@ -607,6 +607,7 @@
|
|||||||
* [ArrayCombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java)
|
* [ArrayCombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java)
|
||||||
* [CombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CombinationTest.java)
|
* [CombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CombinationTest.java)
|
||||||
* [FloodFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/FloodFillTest.java)
|
* [FloodFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/FloodFillTest.java)
|
||||||
|
* [KnightsTourTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java)
|
||||||
* [MazeRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java)
|
* [MazeRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java)
|
||||||
* [MColoringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MColoringTest.java)
|
* [MColoringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MColoringTest.java)
|
||||||
* [NQueensTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/NQueensTest.java)
|
* [NQueensTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/NQueensTest.java)
|
||||||
|
@ -4,33 +4,26 @@ import java.util.ArrayList;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Problem Statement: -
|
* The KnightsTour class solves the Knight's Tour problem using backtracking.
|
||||||
|
*
|
||||||
Given a N*N board with the Knight placed on the first block of an empty board. Moving according
|
* Problem Statement:
|
||||||
to the rules of chess knight must visit each square exactly once. Print the order of each cell in
|
* Given an N*N board with a knight placed on the first block, the knight must
|
||||||
which they are visited.
|
* move according to chess rules and visit each square on the board exactly once.
|
||||||
|
* The class outputs the sequence of moves for the knight.
|
||||||
Example: -
|
*
|
||||||
|
* Example:
|
||||||
Input : N = 8
|
* Input: N = 8 (8x8 chess board)
|
||||||
|
* Output: The sequence of numbers representing the order in which the knight visits each square.
|
||||||
Output:
|
|
||||||
0 59 38 33 30 17 8 63
|
|
||||||
37 34 31 60 9 62 29 16
|
|
||||||
58 1 36 39 32 27 18 7
|
|
||||||
35 48 41 26 61 10 15 28
|
|
||||||
42 57 2 49 40 23 6 19
|
|
||||||
47 50 45 54 25 20 11 14
|
|
||||||
56 43 52 3 22 13 24 5
|
|
||||||
51 46 55 44 53 4 21 12
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
public final class KnightsTour {
|
public final class KnightsTour {
|
||||||
private KnightsTour() {
|
private KnightsTour() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The size of the chess board (12x12 grid, with 2 extra rows/columns as a buffer around a 8x8 area)
|
||||||
private static final int BASE = 12;
|
private static final int BASE = 12;
|
||||||
|
|
||||||
|
// Possible moves for a knight in chess
|
||||||
private static final int[][] MOVES = {
|
private static final int[][] MOVES = {
|
||||||
{1, -2},
|
{1, -2},
|
||||||
{2, -1},
|
{2, -1},
|
||||||
@ -40,36 +33,40 @@ public final class KnightsTour {
|
|||||||
{-2, 1},
|
{-2, 1},
|
||||||
{-2, -1},
|
{-2, -1},
|
||||||
{-1, -2},
|
{-1, -2},
|
||||||
}; // Possible moves by knight on chess
|
};
|
||||||
private static int[][] grid; // chess grid
|
|
||||||
private static int total; // total squares in chess
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
// Chess grid representing the board
|
||||||
|
static int[][] grid;
|
||||||
|
|
||||||
|
// Total number of cells the knight needs to visit
|
||||||
|
static int total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the chess board to its initial state.
|
||||||
|
* Initializes the grid with boundary cells marked as -1 and internal cells as 0.
|
||||||
|
* Sets the total number of cells the knight needs to visit.
|
||||||
|
*/
|
||||||
|
public static void resetBoard() {
|
||||||
grid = new int[BASE][BASE];
|
grid = new int[BASE][BASE];
|
||||||
total = (BASE - 4) * (BASE - 4);
|
total = (BASE - 4) * (BASE - 4);
|
||||||
|
|
||||||
for (int r = 0; r < BASE; r++) {
|
for (int r = 0; r < BASE; r++) {
|
||||||
for (int c = 0; c < BASE; c++) {
|
for (int c = 0; c < BASE; c++) {
|
||||||
if (r < 2 || r > BASE - 3 || c < 2 || c > BASE - 3) {
|
if (r < 2 || r > BASE - 3 || c < 2 || c > BASE - 3) {
|
||||||
grid[r][c] = -1;
|
grid[r][c] = -1; // Mark boundary cells
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int row = 2 + (int) (Math.random() * (BASE - 4));
|
|
||||||
int col = 2 + (int) (Math.random() * (BASE - 4));
|
|
||||||
|
|
||||||
grid[row][col] = 1;
|
|
||||||
|
|
||||||
if (solve(row, col, 2)) {
|
|
||||||
printResult();
|
|
||||||
} else {
|
|
||||||
System.out.println("no result");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return True when solvable
|
/**
|
||||||
private static boolean solve(int row, int column, int count) {
|
* Recursive method to solve the Knight's Tour problem.
|
||||||
|
*
|
||||||
|
* @param row The current row of the knight
|
||||||
|
* @param column The current column of the knight
|
||||||
|
* @param count The current move number
|
||||||
|
* @return True if a solution is found, False otherwise
|
||||||
|
*/
|
||||||
|
static boolean solve(int row, int column, int count) {
|
||||||
if (count > total) {
|
if (count > total) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -80,29 +77,37 @@ public final class KnightsTour {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort neighbors by Warnsdorff's rule (fewest onward moves)
|
||||||
neighbor.sort(Comparator.comparingInt(a -> a[2]));
|
neighbor.sort(Comparator.comparingInt(a -> a[2]));
|
||||||
|
|
||||||
for (int[] nb : neighbor) {
|
for (int[] nb : neighbor) {
|
||||||
row = nb[0];
|
int nextRow = nb[0];
|
||||||
column = nb[1];
|
int nextCol = nb[1];
|
||||||
grid[row][column] = count;
|
grid[nextRow][nextCol] = count;
|
||||||
if (!orphanDetected(count, row, column) && solve(row, column, count + 1)) {
|
if (!orphanDetected(count, nextRow, nextCol) && solve(nextRow, nextCol, count + 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
grid[row][column] = 0;
|
grid[nextRow][nextCol] = 0; // Backtrack
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns List of neighbours
|
/**
|
||||||
private static List<int[]> neighbors(int row, int column) {
|
* Returns a list of valid neighboring cells where the knight can move.
|
||||||
|
*
|
||||||
|
* @param row The current row of the knight
|
||||||
|
* @param column The current column of the knight
|
||||||
|
* @return A list of arrays representing valid moves, where each array contains:
|
||||||
|
* {nextRow, nextCol, numberOfPossibleNextMoves}
|
||||||
|
*/
|
||||||
|
static List<int[]> neighbors(int row, int column) {
|
||||||
List<int[]> neighbour = new ArrayList<>();
|
List<int[]> neighbour = new ArrayList<>();
|
||||||
|
|
||||||
for (int[] m : MOVES) {
|
for (int[] m : MOVES) {
|
||||||
int x = m[0];
|
int x = m[0];
|
||||||
int y = m[1];
|
int y = m[1];
|
||||||
if (grid[row + y][column + x] == 0) {
|
if (row + y >= 0 && row + y < BASE && column + x >= 0 && column + x < BASE && grid[row + y][column + x] == 0) {
|
||||||
int num = countNeighbors(row + y, column + x);
|
int num = countNeighbors(row + y, column + x);
|
||||||
neighbour.add(new int[] {row + y, column + x, num});
|
neighbour.add(new int[] {row + y, column + x, num});
|
||||||
}
|
}
|
||||||
@ -110,19 +115,34 @@ public final class KnightsTour {
|
|||||||
return neighbour;
|
return neighbour;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the total count of neighbors
|
/**
|
||||||
private static int countNeighbors(int row, int column) {
|
* Counts the number of possible valid moves for a knight from a given position.
|
||||||
|
*
|
||||||
|
* @param row The row of the current position
|
||||||
|
* @param column The column of the current position
|
||||||
|
* @return The number of valid neighboring moves
|
||||||
|
*/
|
||||||
|
static int countNeighbors(int row, int column) {
|
||||||
int num = 0;
|
int num = 0;
|
||||||
for (int[] m : MOVES) {
|
for (int[] m : MOVES) {
|
||||||
if (grid[row + m[1]][column + m[0]] == 0) {
|
int x = m[0];
|
||||||
|
int y = m[1];
|
||||||
|
if (row + y >= 0 && row + y < BASE && column + x >= 0 && column + x < BASE && grid[row + y][column + x] == 0) {
|
||||||
num++;
|
num++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if it is orphan
|
/**
|
||||||
private static boolean orphanDetected(int count, int row, int column) {
|
* Detects if moving to a given position will create an orphan (a position with no further valid moves).
|
||||||
|
*
|
||||||
|
* @param count The current move number
|
||||||
|
* @param row The row of the current position
|
||||||
|
* @param column The column of the current position
|
||||||
|
* @return True if an orphan is detected, False otherwise
|
||||||
|
*/
|
||||||
|
static boolean orphanDetected(int count, int row, int column) {
|
||||||
if (count < total - 1) {
|
if (count < total - 1) {
|
||||||
List<int[]> neighbor = neighbors(row, column);
|
List<int[]> neighbor = neighbors(row, column);
|
||||||
for (int[] nb : neighbor) {
|
for (int[] nb : neighbor) {
|
||||||
@ -133,17 +153,4 @@ public final class KnightsTour {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the result grid
|
|
||||||
private static void printResult() {
|
|
||||||
for (int[] row : grid) {
|
|
||||||
for (int i : row) {
|
|
||||||
if (i == -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
System.out.printf("%2d ", i);
|
|
||||||
}
|
|
||||||
System.out.println();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.thealgorithms.backtracking;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class KnightsTourTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// Call the reset method in the KnightsTour class
|
||||||
|
KnightsTour.resetBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGridInitialization() {
|
||||||
|
for (int r = 0; r < 12; r++) {
|
||||||
|
for (int c = 0; c < 12; c++) {
|
||||||
|
if (r < 2 || r > 12 - 3 || c < 2 || c > 12 - 3) {
|
||||||
|
assertEquals(-1, KnightsTour.grid[r][c], "Border cells should be -1");
|
||||||
|
} else {
|
||||||
|
assertEquals(0, KnightsTour.grid[r][c], "Internal cells should be 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCountNeighbors() {
|
||||||
|
// Manually place a knight at (3, 3) and mark nearby cells to test counting
|
||||||
|
KnightsTour.grid[3][3] = 1; // Knight is here
|
||||||
|
KnightsTour.grid[5][4] = -1; // Block one potential move
|
||||||
|
|
||||||
|
int neighborCount = KnightsTour.countNeighbors(3, 3);
|
||||||
|
assertEquals(3, neighborCount, "Knight at (3, 3) should have 3 neighbors (one blocked)");
|
||||||
|
|
||||||
|
KnightsTour.grid[4][1] = -1; // Block another move
|
||||||
|
neighborCount = KnightsTour.countNeighbors(3, 3);
|
||||||
|
assertEquals(3, neighborCount, "Knight at (3, 3) should have 3 neighbors (two blocked)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNeighbors() {
|
||||||
|
// Test the list of valid neighbors for a given cell (3, 3)
|
||||||
|
List<int[]> neighbors = KnightsTour.neighbors(3, 3);
|
||||||
|
assertEquals(4, neighbors.size(), "Knight at (3, 3) should have 8 valid neighbors");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSolveSuccessful() {
|
||||||
|
// Test if the solve method works for a successful knight's tour
|
||||||
|
KnightsTour.grid[2][2] = 1; // Start the knight at (2, 2)
|
||||||
|
boolean result = KnightsTour.solve(2, 2, 2);
|
||||||
|
assertTrue(result, "solve() should successfully complete a Knight's tour");
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user