Skip to content

Commit bffacf0

Browse files
committedAug 20, 2018
Add nQueens bitwise solution.
1 parent 2015931 commit bffacf0

File tree

4 files changed

+156
-60
lines changed

4 files changed

+156
-60
lines changed
 

‎src/algorithms/uncategorized/n-queens/README.md

+42-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,46 @@ and return false.
6565
backtracking.
6666
```
6767

68+
## Bitwise Solution
69+
70+
Bitwise algorithm basically approaches the problem like this:
71+
72+
- Queens can attack diagonally, vertically, or horizontally. As a result, there
73+
can only be one queen in each row, one in each column, and at most one on each
74+
diagonal.
75+
- Since we know there can only one queen per row, we will start at the first row,
76+
place a queen, then move to the second row, place a second queen, and so on until
77+
either a) we reach a valid solution or b) we reach a dead end (ie. we can't place
78+
a queen such that it is "safe" from the other queens).
79+
- Since we are only placing one queen per row, we don't need to worry about
80+
horizontal attacks, since no queen will ever be on the same row as another queen.
81+
- That means we only need to check three things before placing a queen on a
82+
certain square: 1) The square's column doesn't have any other queens on it, 2)
83+
the square's left diagonal doesn't have any other queens on it, and 3) the
84+
square's right diagonal doesn't have any other queens on it.
85+
- If we ever reach a point where there is nowhere safe to place a queen, we can
86+
give up on our current attempt and immediately test out the next possibility.
87+
88+
First let's talk about the recursive function. You'll notice that it accepts
89+
3 parameters: `leftDiagonal`, `column`, and `rightDiagonal`. Each of these is
90+
technically an integer, but the algorithm takes advantage of the fact that an
91+
integer is represented by a sequence of bits. So, think of each of these
92+
parameters as a sequence of `N` bits.
93+
94+
Each bit in each of the parameters represents whether the corresponding location
95+
on the current row is "available".
96+
97+
For example:
98+
- For `N=4`, column having a value of `0010` would mean that the 3rd column is
99+
already occupied by a queen.
100+
- For `N=8`, ld having a value of `00011000` at row 5 would mean that the
101+
top-left-to-bottom-right diagonals that pass through columns 4 and 5 of that
102+
row are already occupied by queens.
103+
104+
Below is a visual aid for `leftDiagonal`, `column`, and `rightDiagonal`.
105+
106+
![](http://gregtrowbridge.com/content/images/2014/Jul/Screenshot-from-2014-06-17-19-46-20.png)
107+
68108
## References
69109

70110
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
@@ -73,5 +113,5 @@ and return false.
73113
- [On YouTube by Tushar Roy](https://www.youtube.com/watch?v=xouin83ebxE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)
74114
- Bitwise Solution
75115
- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
76-
- [GREG TROWBRIDGE](http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/)
77-
- [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)
116+
- [Solution by Greg Trowbridge](http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/)
117+

‎src/algorithms/uncategorized/n-queens/__test__/nQeensBitwise.test.js

-26
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import nQueensBitwise from '../nQueensBitwise';
2+
3+
describe('nQueensBitwise', () => {
4+
it('should have solutions for 4 to N queens', () => {
5+
expect(nQueensBitwise(4)).toBe(2);
6+
expect(nQueensBitwise(5)).toBe(10);
7+
expect(nQueensBitwise(6)).toBe(4);
8+
expect(nQueensBitwise(7)).toBe(40);
9+
expect(nQueensBitwise(8)).toBe(92);
10+
expect(nQueensBitwise(9)).toBe(352);
11+
expect(nQueensBitwise(10)).toBe(724);
12+
expect(nQueensBitwise(11)).toBe(2680);
13+
});
14+
});
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,101 @@
1-
export default function (n) {
2-
// Keeps track of the # of valid solutions
3-
let count = 0;
4-
5-
// Helps identify valid solutions
6-
const done = (2 ** n) - 1;
7-
8-
// Checks all possible board configurations
9-
const innerRecurse = (ld, col, rd) => {
10-
// All columns are occupied,
11-
// so the solution must be complete
12-
if (col === done) {
13-
count += 1;
14-
return;
15-
}
16-
17-
// Gets a bit sequence with "1"s
18-
// whereever there is an open "slot"
19-
let poss = ~(ld | rd | col);
20-
21-
// Loops as long as there is a valid
22-
// place to put another queen.
23-
while (poss & done) {
24-
const bit = poss & -poss;
25-
poss -= bit;
26-
innerRecurse((ld | bit) >> 1, col | bit, (rd | bit) << 1);
27-
}
28-
};
29-
30-
innerRecurse(0, 0, 0);
31-
32-
return count;
1+
/**
2+
* Checks all possible board configurations.
3+
*
4+
* @param {number} boardSize - Size of the squared chess board.
5+
* @param {number} leftDiagonal - Sequence of N bits that show whether the corresponding location
6+
* on the current row is "available" (no other queens are threatening from left diagonal).
7+
* @param {number} column - Sequence of N bits that show whether the corresponding location
8+
* on the current row is "available" (no other queens are threatening from columns).
9+
* @param {number} rightDiagonal - Sequence of N bits that show whether the corresponding location
10+
* on the current row is "available" (no other queens are threatening from right diagonal).
11+
* @param {number} solutionsCount - Keeps track of the number of valid solutions.
12+
* @return {number} - Number of possible solutions.
13+
*/
14+
function nQueensBitwiseRecursive(
15+
boardSize,
16+
leftDiagonal = 0,
17+
column = 0,
18+
rightDiagonal = 0,
19+
solutionsCount = 0,
20+
) {
21+
// Keeps track of the number of valid solutions.
22+
let currentSolutionsCount = solutionsCount;
23+
24+
// Helps to identify valid solutions.
25+
// isDone simply has a bit sequence with 1 for every entry up to the Nth. For example,
26+
// when N=5, done will equal 11111. The "isDone" variable simply allows us to not worry about any
27+
// bits beyond the Nth.
28+
const isDone = (2 ** boardSize) - 1;
29+
30+
// All columns are occupied (i.e. 0b1111 for boardSize = 4), so the solution must be complete.
31+
// Since the algorithm never places a queen illegally (ie. when it can attack or be attacked),
32+
// we know that if all the columns have been filled, we must have a valid solution.
33+
if (column === isDone) {
34+
return currentSolutionsCount + 1;
35+
}
36+
37+
// Gets a bit sequence with "1"s wherever there is an open "slot".
38+
// All that's happening here is we're taking col, ld, and rd, and if any of the columns are
39+
// "under attack", we mark that column as 0 in poss, basically meaning "we can't put a queen in
40+
// this column". Thus all bits position in poss that are '1's are available for placing
41+
// queen there.
42+
let availablePositions = ~(leftDiagonal | rightDiagonal | column);
43+
44+
// Loops as long as there is a valid place to put another queen.
45+
// For N=4 the isDone=0b1111. Then if availablePositions=0b0000 (which would mean that all places
46+
// are under threatening) we must stop trying to place a queen.
47+
while (availablePositions & isDone) {
48+
// firstAvailablePosition just stores the first non-zero bit (ie. the first available location).
49+
// So if firstAvailablePosition was 0010, it would mean the 3rd column of the current row.
50+
// And that would be the position will be placing our next queen.
51+
//
52+
// For example:
53+
// availablePositions = 0b01100
54+
// firstAvailablePosition = 100
55+
const firstAvailablePosition = availablePositions & -availablePositions;
56+
57+
// This line just marks that position in the current row as being "taken" by flipping that
58+
// column in availablePositions to zero. This way, when the while loop continues, we'll know
59+
// not to try that location again.
60+
//
61+
// For example:
62+
// availablePositions = 0b0100
63+
// firstAvailablePosition = 0b10
64+
// 0b0110 - 0b10 = 0b0100
65+
availablePositions -= firstAvailablePosition;
66+
67+
/*
68+
* The operators >> 1 and 1 << simply move all the bits in a bit sequence one digit to the
69+
* right or left, respectively. So calling (rd|bit)<<1 simply says: combine rd and bit with
70+
* an OR operation, then move everything in the result to the left by one digit.
71+
*
72+
* More specifically, if rd is 0001 (meaning that the top-right-to-bottom-left diagonal through
73+
* column 4 of the current row is occupied), and bit is 0100 (meaning that we are planning to
74+
* place a queen in column 2 of the current row), (rd|bit) results in 0101 (meaning that after
75+
* we place a queen in column 2 of the current row, the second and the fourth
76+
* top-right-to-bottom-left diagonals will be occupied).
77+
*
78+
* Now, if add in the << operator, we get (rd|bit)<<1, which takes the 0101 we worked out in
79+
* our previous bullet point, and moves everything to the left by one. The result, therefore,
80+
* is 1010.
81+
*/
82+
currentSolutionsCount += nQueensBitwiseRecursive(
83+
boardSize,
84+
(leftDiagonal | firstAvailablePosition) >> 1,
85+
column | firstAvailablePosition,
86+
(rightDiagonal | firstAvailablePosition) << 1,
87+
solutionsCount,
88+
);
89+
}
90+
91+
return currentSolutionsCount;
92+
}
93+
94+
/**
95+
* @param {number} boardSize - Size of the squared chess board.
96+
* @return {number} - Number of possible solutions.
97+
* @see http://gregtrowbridge.com/a-bitwise-solution-to-the-n-queens-problem-in-javascript/
98+
*/
99+
export default function nQueensBitwise(boardSize) {
100+
return nQueensBitwiseRecursive(boardSize);
33101
}

0 commit comments

Comments
 (0)
Please sign in to comment.