A discussion in The 2nd Monitor made me realize I had never 'solved' the N-Queens problem. Additionally, as I read up on it, I realized that the 64-squares in a chess board would work well if represented as bits in a 64-bit long.... That was a nice thought, but it became complicated, and really, 8-size chess boards are quick to process.
Instead, I considered representing each queen as a bit in an integer, and each integer represents a row on the board. Using a 'long' I could have a 64/64 board, conceptually.
With that representation, I could solve any size board up to 32, and that makes it a decent N-Queen solver. Switching to longs would make it a 64-size solver.
Still, I also realized soon that anything beyond about 15 size boards is pretty slow to brute.
Regardless, Here is an N-Queen solver, with some utility methods to make it friendly. The algorithm I use relies on only adding a queen to the board (one queen in each row) when that queen is added to an un-protected square.
It calculates what squares are protected in each row by using bit-wise manipulation.
Hoping for reviews that focus on:
- performance suggestions
- Java 8 utilization
I am aware that heuristic approaches to the N-Queen problem are faster, but answers that rely on heuristics to solve it are not as interesting to me as solutions which improve the brute-force approach.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class NQueens {
public static class Position {
private final int row;
private final int column;
private Position(int row, int column) {
this.row = row;
this.column = column;
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
@Override
public String toString() {
return String.format("(%d,%d)", row, column);
}
}
public static class Solution {
public final Position[] solution;
private Solution(Position[] solution) {
super();
this.solution = solution;
}
public Position[] getPositions() {
return Arrays.copyOf(solution, solution.length);
}
private char[] padChars(char[] chars) {
char[] ret = new char[chars.length * 2 - 1];
Arrays.fill(ret, ' ');
for (int i = 0; i < chars.length; i++) {
ret[i * 2] = chars[i];
}
return ret;
}
@Override
public String toString() {
int size = solution.length;
char[][] board = new char[size][size];
for (int i = 0; i < size; i++) {
board[i] = new char[size];
Arrays.fill(board[i], '\u02d1');
}
for (Position p : solution) {
board[p.getRow()][p.getColumn()] = 'Q';
}
char[] border = new char[size];
Arrays.fill(border, '-');
StringBuilder sb = new StringBuilder((size + 2) * (size + 3));
sb.append(" ").append(padChars(border)).append(" \n");
for (int i = 0; i < size; i++) {
sb.append("|").append(padChars(board[i])).append("|\n");
}
sb.append(" ").append(padChars(border)).append(" \n");
return sb.toString();
}
}
public static Solution[] solve(final int size) {
final List<Solution> solutions = new ArrayList<>();
// each candidate is a 1-bit in a different column.
final int[] candidates = new int[size];
for (int i = 0; i < size; i++) {
candidates[i] = 1 << i;
}
// recursive call
search(0, new int[size], candidates, solutions);
return solutions.toArray(new Solution[solutions.size()]);
}
private static void search(final int depth, final int[] queens,
final int[] candidates, final List<Solution> solutions) {
if (depth == queens.length) {
// solved it.
Position[] locations = new Position[queens.length];
for (int i = 0; i < queens.length; i++) {
locations[i] = new Position(i, queens[i]);
}
solutions.add(new Solution(locations));
return;
}
int mask = 0;
for (int i = 0; i < depth; i++) {
int queen = candidates[queens[i]];
mask |= queen; // previous queens cover this column....
mask |= queen >>> (depth - i); // previous queens cover this
// diagonal to the right.
mask |= queen << (depth - i); // previous queens cover this diagonal
// to the left.
}
for (int i = 0; i < candidates.length; i++) {
if ((candidates[i] & mask) == 0) {
// then we are not in the same column, row, or diagonal as
// another queen.
queens[depth] = i;
search(depth + 1, queens, candidates, solutions);
}
}
}
private static final void timeFunction(String name, Runnable task) {
long start = System.nanoTime();
task.run();
long time = System.nanoTime() - start;
System.out.printf("Ran task: %s in %.3fms%n", name, time / 1000000.0);
}
public static void main(String[] args) {
timeFunction("WarmUp 100x5", () -> {
for (int i = 0; i < 100; i++) {
solve(5);
}
});
timeFunction("WarmUp 100x8", () -> {
for (int i = 0; i < 100; i++) {
solve(8);
}
});
IntStream.rangeClosed(1, 14).forEach(
size -> timeFunction("Size " + size,
() -> report(size, solve(size))));
Solution[] solutions = solve(8);
for (int i = 0; i < solutions.length; i++) {
System.out.printf("Soltion %d of %d for size %d:%n%s%n", i + 1,
solutions.length, 8, solutions[i]);
}
}
private static void report(int size, Solution[] solve) {
System.out.printf("Solved %d size with %d solutions%n", size,
solve.length);
}
}
The program generates output like:
Ran task: WarmUp 100x5 in 4.989ms
Ran task: WarmUp 100x8 in 12.117ms
Solved 1 size with 1 solutions
Ran task: Size 1 in 0.376ms
Solved 2 size with 0 solutions
Ran task: Size 2 in 0.246ms
Solved 3 size with 0 solutions
Ran task: Size 3 in 0.244ms
Solved 4 size with 2 solutions
Ran task: Size 4 in 0.241ms
Solved 5 size with 10 solutions
Ran task: Size 5 in 0.171ms
Solved 6 size with 4 solutions
Ran task: Size 6 in 0.135ms
Solved 7 size with 40 solutions
Ran task: Size 7 in 0.156ms
Solved 8 size with 92 solutions
Ran task: Size 8 in 0.230ms
Solved 9 size with 352 solutions
Ran task: Size 9 in 0.493ms
Solved 10 size with 724 solutions
Ran task: Size 10 in 1.712ms
Solved 11 size with 2680 solutions
Ran task: Size 11 in 9.026ms
Solved 12 size with 14200 solutions
Ran task: Size 12 in 53.223ms
Solved 13 size with 73712 solutions
Ran task: Size 13 in 308.354ms
Solved 14 size with 365596 solutions
Ran task: Size 14 in 2739.035ms
and
Soltion 1 of 92 for size 8:
- - - - - - - -
|Q ˑ ˑ ˑ ˑ ˑ ˑ ˑ|
|ˑ ˑ ˑ ˑ Q ˑ ˑ ˑ|
|ˑ ˑ ˑ ˑ ˑ ˑ ˑ Q|
|ˑ ˑ ˑ ˑ ˑ Q ˑ ˑ|
|ˑ ˑ Q ˑ ˑ ˑ ˑ ˑ|
|ˑ ˑ ˑ ˑ ˑ ˑ Q ˑ|
|ˑ Q ˑ ˑ ˑ ˑ ˑ ˑ|
|ˑ ˑ ˑ Q ˑ ˑ ˑ ˑ|
- - - - - - - -
Soltion 2 of 92 for size 8:
- - - - - - - -
|Q ˑ ˑ ˑ ˑ ˑ ˑ ˑ|
|ˑ ˑ ˑ ˑ ˑ Q ˑ ˑ|
|ˑ ˑ ˑ ˑ ˑ ˑ ˑ Q|
|ˑ ˑ Q ˑ ˑ ˑ ˑ ˑ|
|ˑ ˑ ˑ ˑ ˑ ˑ Q ˑ|
|ˑ ˑ ˑ Q ˑ ˑ ˑ ˑ|
|ˑ Q ˑ ˑ ˑ ˑ ˑ ˑ|
|ˑ ˑ ˑ ˑ Q ˑ ˑ ˑ|
- - - - - - - -