mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-13 06:23:00 +08:00
Add nQueens bitwise solution.
This commit is contained in:
parent
20159312be
commit
bffacf0707
@ -65,6 +65,46 @@ and return false.
|
|||||||
backtracking.
|
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
|
## References
|
||||||
|
|
||||||
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
|
- [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)
|
- [On YouTube by Tushar Roy](https://www.youtube.com/watch?v=xouin83ebxE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)
|
||||||
- Bitwise Solution
|
- Bitwise Solution
|
||||||
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
|
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
|
||||||
- [GREG TROWBRIDGE](http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/)
|
- [Solution by 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)
|
|
||||||
|
@ -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
|
* Checks all possible board configurations.
|
||||||
let count = 0;
|
*
|
||||||
|
* @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
|
// Helps to identify valid solutions.
|
||||||
const done = (2 ** n) - 1;
|
// 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
|
// All columns are occupied (i.e. 0b1111 for boardSize = 4), so the solution must be complete.
|
||||||
const innerRecurse = (ld, col, rd) => {
|
// Since the algorithm never places a queen illegally (ie. when it can attack or be attacked),
|
||||||
// All columns are occupied,
|
// we know that if all the columns have been filled, we must have a valid solution.
|
||||||
// so the solution must be complete
|
if (column === isDone) {
|
||||||
if (col === done) {
|
return currentSolutionsCount + 1;
|
||||||
count += 1;
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets a bit sequence with "1"s
|
// Gets a bit sequence with "1"s wherever there is an open "slot".
|
||||||
// whereever 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
|
||||||
let poss = ~(ld | rd | col);
|
// "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
|
// Loops as long as there is a valid place to put another queen.
|
||||||
// place to put another queen.
|
// For N=4 the isDone=0b1111. Then if availablePositions=0b0000 (which would mean that all places
|
||||||
while (poss & done) {
|
// are under threatening) we must stop trying to place a queen.
|
||||||
const bit = poss & -poss;
|
while (availablePositions & isDone) {
|
||||||
poss -= bit;
|
// firstAvailablePosition just stores the first non-zero bit (ie. the first available location).
|
||||||
innerRecurse((ld | bit) >> 1, col | bit, (rd | bit) << 1);
|
// 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