Add tests, enhance class & function documentation for KnightsTour (#5591)

This commit is contained in:
Hardik Pawar
2024-10-07 18:36:59 +05:30
committed by GitHub
parent 2592a088e7
commit fa7d357451
3 changed files with 133 additions and 66 deletions

View File

@ -4,33 +4,26 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/*
* Problem Statement: -
Given a N*N board with the Knight placed on the first block of an empty board. Moving according
to the rules of chess knight must visit each square exactly once. Print the order of each cell in
which they are visited.
Example: -
Input : N = 8
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
/**
* The KnightsTour class solves the Knight's Tour problem using backtracking.
*
* Problem Statement:
* Given an N*N board with a knight placed on the first block, the knight must
* 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:
* Input: N = 8 (8x8 chess board)
* Output: The sequence of numbers representing the order in which the knight visits each square.
*/
public final class 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;
// Possible moves for a knight in chess
private static final int[][] MOVES = {
{1, -2},
{2, -1},
@ -40,36 +33,40 @@ public final class KnightsTour {
{-2, 1},
{-2, -1},
{-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];
total = (BASE - 4) * (BASE - 4);
for (int r = 0; r < BASE; r++) {
for (int c = 0; c < BASE; c++) {
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) {
return true;
}
@ -80,29 +77,37 @@ public final class KnightsTour {
return false;
}
// Sort neighbors by Warnsdorff's rule (fewest onward moves)
neighbor.sort(Comparator.comparingInt(a -> a[2]));
for (int[] nb : neighbor) {
row = nb[0];
column = nb[1];
grid[row][column] = count;
if (!orphanDetected(count, row, column) && solve(row, column, count + 1)) {
int nextRow = nb[0];
int nextCol = nb[1];
grid[nextRow][nextCol] = count;
if (!orphanDetected(count, nextRow, nextCol) && solve(nextRow, nextCol, count + 1)) {
return true;
}
grid[row][column] = 0;
grid[nextRow][nextCol] = 0; // Backtrack
}
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<>();
for (int[] m : MOVES) {
int x = m[0];
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);
neighbour.add(new int[] {row + y, column + x, num});
}
@ -110,19 +115,34 @@ public final class KnightsTour {
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;
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++;
}
}
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) {
List<int[]> neighbor = neighbors(row, column);
for (int[] nb : neighbor) {
@ -133,17 +153,4 @@ public final class KnightsTour {
}
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();
}
}
}