From e2ef46016d3a8e1dd69efa8228bb052a3dfda45e Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Wed, 16 May 2018 07:54:06 +0300 Subject: [PATCH] Add N-Queens. --- README.md | 17 ++- .../uncategorized/n-queens/QueenPosition.js | 39 +++++++ .../uncategorized/n-queens/README.md | 73 +++++++++++++ .../n-queens/__test__/QueensPosition.test.js | 16 +++ .../n-queens/__test__/nQueens.test.js | 38 +++++++ .../uncategorized/n-queens/nQueens.js | 103 ++++++++++++++++++ 6 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 src/algorithms/uncategorized/n-queens/QueenPosition.js create mode 100644 src/algorithms/uncategorized/n-queens/README.md create mode 100644 src/algorithms/uncategorized/n-queens/__test__/QueensPosition.test.js create mode 100644 src/algorithms/uncategorized/n-queens/__test__/nQueens.test.js create mode 100644 src/algorithms/uncategorized/n-queens/nQueens.js diff --git a/README.md b/README.md index dd2ab9fe..c7cc3ea2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ * [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm * **Uncategorized** * [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower) + * [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens) * Union-Find * Maze * Sudoku @@ -110,9 +111,15 @@ * [Maximum Subarray](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/maximum-subarray) * [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices * **Backtracking** + * [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens) * **Branch & Bound** -## Running Tests +## How to use this repository + +**Install all dependencies** +``` +npm i +``` **Run all tests** ``` @@ -135,12 +142,12 @@ Then just simply run the following command to test if your playground code works npm test -- -t 'playground' ``` -## Useful links +## Useful Information + +### References [▶ Data Structures and Algorithms on YouTube](https://www.youtube.com/playlist?list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -## Useful Information - ### Big O Notation Order of growth of algorithms specified in Big O notation. @@ -185,4 +192,4 @@ Below is the list of some of the most used Big O notations and their performance | **Heap sort** | n log(n) | n log(n) | n log(n) | 1 | No | | **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes | | **Quick sort** | n log(n) | n log(n) | n^2 | log(n) | No | -| **Shell sort** | n log(n) | depends on gap sequence | n (log(n))^2 | 1 | No | +| **Shell sort** | n log(n) | depends on gap sequence | n (log(n))^2 | 1 | No | diff --git a/src/algorithms/uncategorized/n-queens/QueenPosition.js b/src/algorithms/uncategorized/n-queens/QueenPosition.js new file mode 100644 index 00000000..9ad278bc --- /dev/null +++ b/src/algorithms/uncategorized/n-queens/QueenPosition.js @@ -0,0 +1,39 @@ +/** + * Class that represents queen position on the chessboard. + */ +export default class QueenPosition { + /** + * @param {number} rowIndex + * @param {number} columnIndex + */ + constructor(rowIndex, columnIndex) { + this.rowIndex = rowIndex; + this.columnIndex = columnIndex; + } + + /** + * @return {number} + */ + get leftDiagonal() { + // Each position on the same left (\) diagonal has the same difference of + // rowIndex and columnIndex. This fact may be used to quickly check if two + // positions (queens) are on the same left diagonal. + // @see https://youtu.be/xouin83ebxE?t=1m59s + return this.rowIndex - this.columnIndex; + } + + /** + * @return {number} + */ + get rightDiagonal() { + // Each position on the same right diagonal (/) has the same + // sum of rowIndex and columnIndex. This fact may be used to quickly + // check if two positions (queens) are on the same right diagonal. + // @see https://youtu.be/xouin83ebxE?t=1m59s + return this.rowIndex + this.columnIndex; + } + + toString() { + return `${this.rowIndex},${this.columnIndex}`; + } +} diff --git a/src/algorithms/uncategorized/n-queens/README.md b/src/algorithms/uncategorized/n-queens/README.md new file mode 100644 index 00000000..5c0d9c83 --- /dev/null +++ b/src/algorithms/uncategorized/n-queens/README.md @@ -0,0 +1,73 @@ +# N-Queens Problem + +The **eight queens puzzle** is the problem of placing eight chess queens +on an `8×8` chessboard so that no two queens threaten each other. +Thus, a solution requires that no two queens share the same row, +column, or diagonal. The eight queens puzzle is an example of the +more general *n queens problem* of placing n non-attacking queens +on an `n×n` chessboard, for which solutions exist for all natural +numbers `n` with the exception of `n=2` and `n=3`. + +For example, following is a solution for 4 Queen problem. + +![N Queens](https://cdncontribute.geeksforgeeks.org/wp-content/uploads/N_Queen_Problem.jpg) + +The expected output is a binary matrix which has 1s for the blocks +where queens are placed. For example following is the output matrix +for above 4 queen solution. + +``` +{ 0, 1, 0, 0} +{ 0, 0, 0, 1} +{ 1, 0, 0, 0} +{ 0, 0, 1, 0} +``` + +## Naive Algorithm + +Generate all possible configurations of queens on board and print a +configuration that satisfies the given constraints. + +``` +while there are untried conflagrations +{ + generate the next configuration + if queens don't attack in this configuration then + { + print this configuration; + } +} +``` + +## Backtracking Algorithm + +The idea is to place queens one by one in different columns, +starting from the leftmost column. When we place a queen in a +column, we check for clashes with already placed queens. In +the current column, if we find a row for which there is no +clash, we mark this row and column as part of the solution. +If we do not find such a row due to clashes then we backtrack +and return false. + +``` +1) Start in the leftmost column +2) If all queens are placed + return true +3) Try all rows in the current column. Do following for every tried row. + a) If the queen can be placed safely in this row then mark this [row, + column] as part of the solution and recursively check if placing + queen here leads to a solution. + b) If placing queen in [row, column] leads to a solution then return + true. + c) If placing queen doesn't lead to a solution then umark this [row, + column] (Backtrack) and go to step (a) to try other rows. +3) If all rows have been tried and nothing worked, return false to trigger + backtracking. +``` + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Eight_queens_puzzle) +- [GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-3-n-queen-problem/) +- [On YouTube by Abdul Bari](https://www.youtube.com/watch?v=xFv_Hl4B83A) +- [On YouTube by Tushar Roy](https://www.youtube.com/watch?v=xouin83ebxE) diff --git a/src/algorithms/uncategorized/n-queens/__test__/QueensPosition.test.js b/src/algorithms/uncategorized/n-queens/__test__/QueensPosition.test.js new file mode 100644 index 00000000..a842dce3 --- /dev/null +++ b/src/algorithms/uncategorized/n-queens/__test__/QueensPosition.test.js @@ -0,0 +1,16 @@ +import QueenPosition from '../QueenPosition'; + +describe('QueenPosition', () => { + it('should store queen position on chessboard', () => { + const position1 = new QueenPosition(0, 0); + const position2 = new QueenPosition(2, 1); + + expect(position2.columnIndex).toBe(1); + expect(position2.rowIndex).toBe(2); + expect(position1.leftDiagonal).toBe(0); + expect(position1.rightDiagonal).toBe(0); + expect(position2.leftDiagonal).toBe(1); + expect(position2.rightDiagonal).toBe(3); + expect(position2.toString()).toBe('2,1'); + }); +}); diff --git a/src/algorithms/uncategorized/n-queens/__test__/nQueens.test.js b/src/algorithms/uncategorized/n-queens/__test__/nQueens.test.js new file mode 100644 index 00000000..1f01b3aa --- /dev/null +++ b/src/algorithms/uncategorized/n-queens/__test__/nQueens.test.js @@ -0,0 +1,38 @@ +import nQueens from '../nQueens'; + +describe('nQueens', () => { + it('should not hae solution for 3 queens', () => { + const solutions = nQueens(3); + expect(solutions.length).toBe(0); + }); + + it('should solve n-queens problem for 4 queens', () => { + const solutions = nQueens(4); + expect(solutions.length).toBe(2); + + // First solution. + expect(solutions[0][0].toString()).toBe('0,1'); + expect(solutions[0][1].toString()).toBe('1,3'); + expect(solutions[0][2].toString()).toBe('2,0'); + expect(solutions[0][3].toString()).toBe('3,2'); + + // Second solution (mirrored). + expect(solutions[1][0].toString()).toBe('0,2'); + expect(solutions[1][1].toString()).toBe('1,0'); + expect(solutions[1][2].toString()).toBe('2,3'); + expect(solutions[1][3].toString()).toBe('3,1'); + }); + + it('should solve n-queens problem for 6 queens', () => { + const solutions = nQueens(6); + expect(solutions.length).toBe(4); + + // First solution. + expect(solutions[0][0].toString()).toBe('0,1'); + expect(solutions[0][1].toString()).toBe('1,3'); + expect(solutions[0][2].toString()).toBe('2,5'); + expect(solutions[0][3].toString()).toBe('3,0'); + expect(solutions[0][4].toString()).toBe('4,2'); + expect(solutions[0][5].toString()).toBe('5,4'); + }); +}); diff --git a/src/algorithms/uncategorized/n-queens/nQueens.js b/src/algorithms/uncategorized/n-queens/nQueens.js new file mode 100644 index 00000000..f0e7eb09 --- /dev/null +++ b/src/algorithms/uncategorized/n-queens/nQueens.js @@ -0,0 +1,103 @@ +import QueenPosition from './QueenPosition'; + +/** + * @param {QueenPosition[]} queensPositions + * @param {number} rowIndex + * @param {number} columnIndex + * @return {boolean} + */ +function isSafe(queensPositions, rowIndex, columnIndex) { + // New position to which the Queen is going to be placed. + const newQueenPosition = new QueenPosition(rowIndex, columnIndex); + + // Check if new queen position conflicts with any other queens. + for (let queenIndex = 0; queenIndex < queensPositions.length; queenIndex += 1) { + const currentQueenPosition = queensPositions[queenIndex]; + + if ( + // Check if queen has been already placed. + currentQueenPosition && + ( + // Check if there are any queen on the same column. + newQueenPosition.columnIndex === currentQueenPosition.columnIndex || + // Check if there are any queen on the same row. + newQueenPosition.rowIndex === currentQueenPosition.rowIndex || + // Check if there are any queen on the same left diagonal. + newQueenPosition.leftDiagonal === currentQueenPosition.leftDiagonal || + // Check if there are any queen on the same right diagonal. + newQueenPosition.rightDiagonal === currentQueenPosition.rightDiagonal + ) + ) { + // Can't place queen into current position since there are other queens that + // are threatening it. + return false; + } + } + + // Looks like we're safe. + return true; +} + +/** + * @param {QueenPosition[][]} solutions + * @param {QueenPosition[]} previousQueensPositions + * @param {number} queensCount + * @param {number} rowIndex + * @return {boolean} + */ +function nQueensRecursive(solutions, previousQueensPositions, queensCount, rowIndex) { + // Clone positions array. + const queensPositions = [...previousQueensPositions].map((queenPosition) => { + return !queenPosition ? queenPosition : new QueenPosition( + queenPosition.rowIndex, + queenPosition.columnIndex, + ); + }); + + if (rowIndex === queensCount) { + // We've successfully reached the end of the board. + // Store solution to the list of solutions. + solutions.push(queensPositions); + + // Solution found. + return true; + } + + // Let's try to put queen at row rowIndex into its safe column position. + for (let columnIndex = 0; columnIndex < queensCount; columnIndex += 1) { + if (isSafe(queensPositions, rowIndex, columnIndex)) { + // Place current queen to its current position. + queensPositions[rowIndex] = new QueenPosition(rowIndex, columnIndex); + + // Try to place all other queens as well. + nQueensRecursive(solutions, queensPositions, queensCount, rowIndex + 1); + + // Remove the queen from the row to avoid isSafe() returning false. + queensPositions[rowIndex] = null; + } + } + + return false; +} + + +/** + * @param {number} queensCount + * @return {QueenPosition[][]} + */ +export default function nQueens(queensCount) { + // Init NxN chessboard with zeros. + // const chessboard = Array(queensCount).fill(null).map(() => Array(queensCount).fill(0)); + + // This array will hold positions or coordinates of each of + // N queens in form of [rowIndex, columnIndex]. + const queensPositions = Array(queensCount).fill(null); + + /** @var {QueenPosition[][]} solutions */ + const solutions = []; + + // Solve problem recursively. + nQueensRecursive(solutions, queensPositions, queensCount, 0); + + return solutions; +}