Add nQueens bitwise solution.

This commit is contained in:
Oleksii Trekhleb 2018-08-20 18:12:56 +03:00
parent 20159312be
commit bffacf0707
4 changed files with 150 additions and 54 deletions

View File

@ -65,6 +65,46 @@ and return false.
backtracking.
```
## Bitwise Solution
Bitwise algorithm basically approaches the problem like this:
- Queens can attack diagonally, vertically, or horizontally. As a result, there
can only be one queen in each row, one in each column, and at most one on each
diagonal.
- Since we know there can only one queen per row, we will start at the first row,
place a queen, then move to the second row, place a second queen, and so on until
either a) we reach a valid solution or b) we reach a dead end (ie. we can't place
a queen such that it is "safe" from the other queens).
- Since we are only placing one queen per row, we don't need to worry about
horizontal attacks, since no queen will ever be on the same row as another queen.
- That means we only need to check three things before placing a queen on a
certain square: 1) The square's column doesn't have any other queens on it, 2)
the square's left diagonal doesn't have any other queens on it, and 3) the
square's right diagonal doesn't have any other queens on it.
- If we ever reach a point where there is nowhere safe to place a queen, we can
give up on our current attempt and immediately test out the next possibility.
First let's talk about the recursive function. You'll notice that it accepts
3 parameters: `leftDiagonal`, `column`, and `rightDiagonal`. Each of these is
technically an integer, but the algorithm takes advantage of the fact that an
integer is represented by a sequence of bits. So, think of each of these
parameters as a sequence of `N` bits.
Each bit in each of the parameters represents whether the corresponding location
on the current row is "available".
For example:
- For `N=4`, column having a value of `0010` would mean that the 3rd column is
already occupied by a queen.
- For `N=8`, ld having a value of `00011000` at row 5 would mean that the
top-left-to-bottom-right diagonals that pass through columns 4 and 5 of that
row are already occupied by queens.
Below is a visual aid for `leftDiagonal`, `column`, and `rightDiagonal`.
![](http://gregtrowbridge.com/content/images/2014/Jul/Screenshot-from-2014-06-17-19-46-20.png)
## References
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
@ -73,5 +113,5 @@ and return false.
- [On YouTube by Tushar Roy](https://www.youtube.com/watch?v=xouin83ebxE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)
- Bitwise Solution
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
- [GREG TROWBRIDGE](http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/)
- [Backtracking Algorithms in MCPL using Bit Patterns and Recursion](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.51.7113&rep=rep1&type=pdf)
- [Solution by Greg Trowbridge](http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/)

View File

@ -1,26 +0,0 @@
import nQueensBitwise from '../nQueensBitwise';
describe('nQueensBitwise', () => {
it('should have solutions for 4 to N queens', () => {
const solutionFor4 = nQueensBitwise(4);
expect(solutionFor4).toBe(2);
const solutionFor5 = nQueensBitwise(5);
expect(solutionFor5).toBe(10);
const solutionFor6 = nQueensBitwise(6);
expect(solutionFor6).toBe(4);
const solutionFor7 = nQueensBitwise(7);
expect(solutionFor7).toBe(40);
const solutionFor8 = nQueensBitwise(8);
expect(solutionFor8).toBe(92);
const solutionFor9 = nQueensBitwise(9);
expect(solutionFor9).toBe(352);
const solutionFor10 = nQueensBitwise(10);
expect(solutionFor10).toBe(724);
});
});

View File

@ -0,0 +1,14 @@
import nQueensBitwise from '../nQueensBitwise';
describe('nQueensBitwise', () => {
it('should have solutions for 4 to N queens', () => {
expect(nQueensBitwise(4)).toBe(2);
expect(nQueensBitwise(5)).toBe(10);
expect(nQueensBitwise(6)).toBe(4);
expect(nQueensBitwise(7)).toBe(40);
expect(nQueensBitwise(8)).toBe(92);
expect(nQueensBitwise(9)).toBe(352);
expect(nQueensBitwise(10)).toBe(724);
expect(nQueensBitwise(11)).toBe(2680);
});
});

View File

@ -1,33 +1,101 @@
export default function (n) {
// Keeps track of the # of valid solutions
let count = 0;
/**
* Checks all possible board configurations.
*
* @param {number} boardSize - Size of the squared chess board.
* @param {number} leftDiagonal - Sequence of N bits that show whether the corresponding location
* on the current row is "available" (no other queens are threatening from left diagonal).
* @param {number} column - Sequence of N bits that show whether the corresponding location
* on the current row is "available" (no other queens are threatening from columns).
* @param {number} rightDiagonal - Sequence of N bits that show whether the corresponding location
* on the current row is "available" (no other queens are threatening from right diagonal).
* @param {number} solutionsCount - Keeps track of the number of valid solutions.
* @return {number} - Number of possible solutions.
*/
function nQueensBitwiseRecursive(
boardSize,
leftDiagonal = 0,
column = 0,
rightDiagonal = 0,
solutionsCount = 0,
) {
// Keeps track of the number of valid solutions.
let currentSolutionsCount = solutionsCount;
// Helps identify valid solutions
const done = (2 ** n) - 1;
// Helps to identify valid solutions.
// isDone simply has a bit sequence with 1 for every entry up to the Nth. For example,
// when N=5, done will equal 11111. The "isDone" variable simply allows us to not worry about any
// bits beyond the Nth.
const isDone = (2 ** boardSize) - 1;
// Checks all possible board configurations
const innerRecurse = (ld, col, rd) => {
// All columns are occupied,
// so the solution must be complete
if (col === done) {
count += 1;
return;
}
// All columns are occupied (i.e. 0b1111 for boardSize = 4), so the solution must be complete.
// Since the algorithm never places a queen illegally (ie. when it can attack or be attacked),
// we know that if all the columns have been filled, we must have a valid solution.
if (column === isDone) {
return currentSolutionsCount + 1;
}
// Gets a bit sequence with "1"s
// whereever there is an open "slot"
let poss = ~(ld | rd | col);
// Gets a bit sequence with "1"s wherever there is an open "slot".
// All that's happening here is we're taking col, ld, and rd, and if any of the columns are
// "under attack", we mark that column as 0 in poss, basically meaning "we can't put a queen in
// this column". Thus all bits position in poss that are '1's are available for placing
// queen there.
let availablePositions = ~(leftDiagonal | rightDiagonal | column);
// Loops as long as there is a valid
// place to put another queen.
while (poss & done) {
const bit = poss & -poss;
poss -= bit;
innerRecurse((ld | bit) >> 1, col | bit, (rd | bit) << 1);
}
};
// Loops as long as there is a valid place to put another queen.
// For N=4 the isDone=0b1111. Then if availablePositions=0b0000 (which would mean that all places
// are under threatening) we must stop trying to place a queen.
while (availablePositions & isDone) {
// firstAvailablePosition just stores the first non-zero bit (ie. the first available location).
// So if firstAvailablePosition was 0010, it would mean the 3rd column of the current row.
// And that would be the position will be placing our next queen.
//
// For example:
// availablePositions = 0b01100
// firstAvailablePosition = 100
const firstAvailablePosition = availablePositions & -availablePositions;
innerRecurse(0, 0, 0);
// This line just marks that position in the current row as being "taken" by flipping that
// column in availablePositions to zero. This way, when the while loop continues, we'll know
// not to try that location again.
//
// For example:
// availablePositions = 0b0100
// firstAvailablePosition = 0b10
// 0b0110 - 0b10 = 0b0100
availablePositions -= firstAvailablePosition;
return count;
/*
* The operators >> 1 and 1 << simply move all the bits in a bit sequence one digit to the
* right or left, respectively. So calling (rd|bit)<<1 simply says: combine rd and bit with
* an OR operation, then move everything in the result to the left by one digit.
*
* More specifically, if rd is 0001 (meaning that the top-right-to-bottom-left diagonal through
* column 4 of the current row is occupied), and bit is 0100 (meaning that we are planning to
* place a queen in column 2 of the current row), (rd|bit) results in 0101 (meaning that after
* we place a queen in column 2 of the current row, the second and the fourth
* top-right-to-bottom-left diagonals will be occupied).
*
* Now, if add in the << operator, we get (rd|bit)<<1, which takes the 0101 we worked out in
* our previous bullet point, and moves everything to the left by one. The result, therefore,
* is 1010.
*/
currentSolutionsCount += nQueensBitwiseRecursive(
boardSize,
(leftDiagonal | firstAvailablePosition) >> 1,
column | firstAvailablePosition,
(rightDiagonal | firstAvailablePosition) << 1,
solutionsCount,
);
}
return currentSolutionsCount;
}
/**
* @param {number} boardSize - Size of the squared chess board.
* @return {number} - Number of possible solutions.
* @see http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/
*/
export default function nQueensBitwise(boardSize) {
return nQueensBitwiseRecursive(boardSize);
}