diff --git a/src/algorithms/uncategorized/n-queens/README.md b/src/algorithms/uncategorized/n-queens/README.md index 74031df0..077e26d7 100644 --- a/src/algorithms/uncategorized/n-queens/README.md +++ b/src/algorithms/uncategorized/n-queens/README.md @@ -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/) + diff --git a/src/algorithms/uncategorized/n-queens/__test__/nQeensBitwise.test.js b/src/algorithms/uncategorized/n-queens/__test__/nQeensBitwise.test.js deleted file mode 100644 index 2730104b..00000000 --- a/src/algorithms/uncategorized/n-queens/__test__/nQeensBitwise.test.js +++ /dev/null @@ -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); - }); -}); diff --git a/src/algorithms/uncategorized/n-queens/__test__/nQueensBitwise.test.js b/src/algorithms/uncategorized/n-queens/__test__/nQueensBitwise.test.js new file mode 100644 index 00000000..87dea41a --- /dev/null +++ b/src/algorithms/uncategorized/n-queens/__test__/nQueensBitwise.test.js @@ -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); + }); +}); diff --git a/src/algorithms/uncategorized/n-queens/nQueensBitwise.js b/src/algorithms/uncategorized/n-queens/nQueensBitwise.js index dd64d054..05dd40bc 100644 --- a/src/algorithms/uncategorized/n-queens/nQueensBitwise.js +++ b/src/algorithms/uncategorized/n-queens/nQueensBitwise.js @@ -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); }