mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Add nQueens bitwise solution.
This commit is contained in:
parent
20159312be
commit
bffacf0707
@ -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/)
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user