N-Queen Problem | Local Search using Hill climbing with random neighbour
The N Queen is the problem of placing N chess queens on an N×N chessboard so that no two queens attack each other.
For example, the following is a solution for 8 Queen problem.
Input: N = 4
Output:
0 1 0 0
0 0 0 1
1 0 0 0
0 0 1 0
Explanation:
The Position of queens are:
1 – {1, 2}
2 – {2, 4}
3 – {3, 1}
4 – {4, 3}
As we can see that we have placed all 4 queens
in a way that no two queens are attacking each other.
So, the output is correct
Input: N = 8
Output:
0 0 0 0 0 0 1 0
0 1 0 0 0 0 0 0
0 0 0 0 0 1 0 0
0 0 1 0 0 0 0 0
1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 1 0 0 0
Approach: The idea is to use Hill Climbing Algorithm.
- While there are algorithms like Backtracking to solve N Queen problem, let’s take an AI approach in solving the problem.
- It’s obvious that AI does not guarantee a globally correct solution all the time but it has quite a good success rate of about 97% which is not bad.
- A description of the notions of all terminologies used in the problem will be given and are as follows:-
- Notion of a State – A state here in this context is any configuration of the N queens on the N X N board. Also, in order to reduce the search space let’s add an additional constraint that there can only be a single queen in a particular column. A state in the program is implemented using an array of length N, such that if state[i]=j then there is a queen at column index i and row index j.
- Notion of Neighbours – Neighbours of a state are other states with board configuration that differ from the current state’s board configuration with respect to the position of only a single queen. This queen that differs a state from its neighbour may be displaced anywhere in the same column.
- Optimisation function or Objective function – We know that local search is an optimization algorithm that searches the local space to optimize a function that takes the state as input and gives some value as an output. The value of the objective function of a state here in this context is the number of pairs of queens attacking each other. Our goal here is to find a state with the minimum objective value. This function has a maximum value of NC2 and a minimum value of 0.
Algorithm:
- Start with a random state(i.e, a random configuration of the board).
- Scan through all possible neighbours of the current state and jump to the neighbour with the highest objective value, if found any. If there does not exist, a neighbour, with objective strictly higher than the current state but there exists one with equal then jump to any random neighbour(escaping shoulder and/or local optimum).
- Repeat step 2, until a state whose objective is strictly higher than all it’s neighbour’s objectives, is found and then go to step 4.
- The state thus found after the local search is either the local optimum or the global optimum. There is no way of escaping local optima but adding a random neighbour or a random restart each time a local optimum is encountered increases the chances of achieving global optimum(the solution to our problem).
- Output the state and return.
- It is easily visible that the global optimum in our case is 0 since it is the minimum number of pairs of queens that can attack each other. Also, the random restart has a higher chance of achieving global optimum but we still use random neighbour because our problem of N queens does not has a high number of local optima and random neighbour is faster than random restart.
- Conclusion:
- Random Neighbour escapes shoulders but only has a little chance of escaping local optima.
- Random Restart both escapes shoulders and has a high chance of escaping local optima.
Below is the implementation of the Hill-Climbing algorithm:
CPP
// C++ implementation of the // above approach #include <iostream> #include <math.h> #define N 8 using namespace std; // A utility function that configures // the 2D array "board" and // array "state" randomly to provide // a starting point for the algorithm. void configureRandomly( int board[][N], int * state) { // Seed for the random function srand ( time (0)); // Iterating through the // column indices for ( int i = 0; i < N; i++) { // Getting a random row index state[i] = rand () % N; // Placing a queen on the // obtained place in // chessboard. board[state[i]][i] = 1; } } // A utility function that prints // the 2D array "board". void printBoard( int board[][N]) { for ( int i = 0; i < N; i++) { cout << " " ; for ( int j = 0; j < N; j++) { cout << board[i][j] << " " ; } cout << "\n" ; } } // A utility function that prints // the array "state". void printState( int * state) { for ( int i = 0; i < N; i++) { cout << " " << state[i] << " " ; } cout << endl; } // A utility function that compares // two arrays, state1 and state2 and // returns true if equal // and false otherwise. bool compareStates( int * state1, int * state2) { for ( int i = 0; i < N; i++) { if (state1[i] != state2[i]) { return false ; } } return true ; } // A utility function that fills // the 2D array "board" with // values "value" void fill( int board[][N], int value) { for ( int i = 0; i < N; i++) { for ( int j = 0; j < N; j++) { board[i][j] = value; } } } // This function calculates the // objective value of the // state(queens attacking each other) // using the board by the // following logic. int calculateObjective( int board[][N], int * state) { // For each queen in a column, we check // for other queens falling in the line // of our current queen and if found, // any, then we increment the variable // attacking count. // Number of queens attacking each other, // initially zero. int attacking = 0; // Variables to index a particular // row and column on board. int row, col; for ( int i = 0; i < N; i++) { // At each column 'i', the queen is // placed at row 'state[i]', by the // definition of our state. // To the left of same row // (row remains constant // and col decreases) row = state[i], col = i - 1; while (col >= 0 && board[row][col] != 1) { col--; } if (col >= 0 && board[row][col] == 1) { attacking++; } // To the right of same row // (row remains constant // and col increases) row = state[i], col = i + 1; while (col < N && board[row][col] != 1) { col++; } if (col < N && board[row][col] == 1) { attacking++; } // Diagonally to the left up // (row and col simultaneously // decrease) row = state[i] - 1, col = i - 1; while (col >= 0 && row >= 0 && board[row][col] != 1) { col--; row--; } if (col >= 0 && row >= 0 && board[row][col] == 1) { attacking++; } // Diagonally to the right down // (row and col simultaneously // increase) row = state[i] + 1, col = i + 1; while (col < N && row < N && board[row][col] != 1) { col++; row++; } if (col < N && row < N && board[row][col] == 1) { attacking++; } // Diagonally to the left down // (col decreases and row // increases) row = state[i] + 1, col = i - 1; while (col >= 0 && row < N && board[row][col] != 1) { col--; row++; } if (col >= 0 && row < N && board[row][col] == 1) { attacking++; } // Diagonally to the right up // (col increases and row // decreases) row = state[i] - 1, col = i + 1; while (col < N && row >= 0 && board[row][col] != 1) { col++; row--; } if (col < N && row >= 0 && board[row][col] == 1) { attacking++; } } // Return pairs. return ( int )(attacking / 2); } // A utility function that // generates a board configuration // given the state. void generateBoard( int board[][N], int * state) { fill(board, 0); for ( int i = 0; i < N; i++) { board[state[i]][i] = 1; } } // A utility function that copies // contents of state2 to state1. void copyState( int * state1, int * state2) { for ( int i = 0; i < N; i++) { state1[i] = state2[i]; } } // This function gets the neighbour // of the current state having // the least objective value // amongst all neighbours as // well as the current state. void getNeighbour( int board[][N], int * state) { // Declaring and initializing the // optimal board and state with // the current board and the state // as the starting point. int opBoard[N][N]; int opState[N]; copyState(opState, state); generateBoard(opBoard, opState); // Initializing the optimal // objective value int opObjective = calculateObjective(opBoard, opState); // Declaring and initializing // the temporary board and // state for the purpose // of computation. int NeighbourBoard[N][N]; int NeighbourState[N]; copyState(NeighbourState, state); generateBoard(NeighbourBoard, NeighbourState); // Iterating through all // possible neighbours // of the board. for ( int i = 0; i < N; i++) { for ( int j = 0; j < N; j++) { // Condition for skipping the // current state if (j != state[i]) { // Initializing temporary // neighbour with the // current neighbour. NeighbourState[i] = j; NeighbourBoard[NeighbourState[i]][i] = 1; NeighbourBoard[state[i]][i] = 0; // Calculating the objective // value of the neighbour. int temp = calculateObjective( NeighbourBoard, NeighbourState); // Comparing temporary and optimal // neighbour objectives and if // temporary is less than optimal // then updating accordingly. if (temp <= opObjective) { opObjective = temp; copyState(opState, NeighbourState); generateBoard(opBoard, opState); } // Going back to the original // configuration for the next // iteration. NeighbourBoard[NeighbourState[i]][i] = 0; NeighbourState[i] = state[i]; NeighbourBoard[state[i]][i] = 1; } } } // Copying the optimal board and // state thus found to the current // board and, state since c++ doesn't // allow returning multiple values. copyState(state, opState); fill(board, 0); generateBoard(board, state); } void hillClimbing( int board[][N], int * state) { // Declaring and initializing the // neighbour board and state with // the current board and the state // as the starting point. int neighbourBoard[N][N] = {}; int neighbourState[N]; copyState(neighbourState, state); generateBoard(neighbourBoard, neighbourState); do { // Copying the neighbour board and // state to the current board and // state, since a neighbour // becomes current after the jump. copyState(state, neighbourState); generateBoard(board, state); // Getting the optimal neighbour getNeighbour(neighbourBoard, neighbourState); if (compareStates(state, neighbourState)) { // If neighbour and current are // equal then no optimal neighbour // exists and therefore output the // result and break the loop. printBoard(board); break ; } else if (calculateObjective(board, state) == calculateObjective( neighbourBoard, neighbourState)) { // If neighbour and current are // not equal but their objectives // are equal then we are either // approaching a shoulder or a // local optimum, in any case, // jump to a random neighbour // to escape it. // Random neighbour neighbourState[ rand () % N] = rand () % N; generateBoard(neighbourBoard, neighbourState); } } while ( true ); } // Driver code int main() { int state[N] = {}; int board[N][N] = {}; // Getting a starting point by // randomly configuring the board configureRandomly(board, state); // Do hill climbing on the // board obtained hillClimbing(board, state); return 0; } |
Python3
# Python3 implementation of the # above approach from random import randint N = 8 # A utility function that configures # the 2D array "board" and # array "state" randomly to provide # a starting point for the algorithm. def configureRandomly(board, state): # Iterating through the # column indices for i in range (N): # Getting a random row index state[i] = randint( 0 , 100000 ) % N; # Placing a queen on the # obtained place in # chessboard. board[state[i]][i] = 1 ; # A utility function that prints # the 2D array "board". def printBoard(board): for i in range (N): print ( * board[i]) # A utility function that prints # the array "state". def printState( state): print ( * state) # A utility function that compares # two arrays, state1 and state2 and # returns True if equal # and False otherwise. def compareStates(state1, state2): for i in range (N): if (state1[i] ! = state2[i]): return False ; return True ; # A utility function that fills # the 2D array "board" with # values "value" def fill(board, value): for i in range (N): for j in range (N): board[i][j] = value; # This function calculates the # objective value of the # state(queens attacking each other) # using the board by the # following logic. def calculateObjective( board, state): # For each queen in a column, we check # for other queens falling in the line # of our current queen and if found, # any, then we increment the variable # attacking count. # Number of queens attacking each other, # initially zero. attacking = 0 ; # Variables to index a particular # row and column on board. for i in range (N): # At each column 'i', the queen is # placed at row 'state[i]', by the # definition of our state. # To the left of same row # (row remains constant # and col decreases) row = state[i] col = i - 1 ; while (col > = 0 and board[row][col] ! = 1 ) : col - = 1 if (col > = 0 and board[row][col] = = 1 ) : attacking + = 1 ; # To the right of same row # (row remains constant # and col increases) row = state[i] col = i + 1 ; while (col < N and board[row][col] ! = 1 ): col + = 1 ; if (col < N and board[row][col] = = 1 ) : attacking + = 1 ; # Diagonally to the left up # (row and col simultaneously # decrease) row = state[i] - 1 col = i - 1 ; while (col > = 0 and row > = 0 and board[row][col] ! = 1 ) : col - = 1 ; row - = 1 ; if (col > = 0 and row > = 0 and board[row][col] = = 1 ) : attacking + = 1 ; # Diagonally to the right down # (row and col simultaneously # increase) row = state[i] + 1 col = i + 1 ; while (col < N and row < N and board[row][col] ! = 1 ) : col + = 1 ; row + = 1 ; if (col < N and row < N and board[row][col] = = 1 ) : attacking + = 1 ; # Diagonally to the left down # (col decreases and row # increases) row = state[i] + 1 col = i - 1 ; while (col > = 0 and row < N and board[row][col] ! = 1 ) : col - = 1 ; row + = 1 ; if (col > = 0 and row < N and board[row][col] = = 1 ) : attacking + = 1 ; # Diagonally to the right up # (col increases and row # decreases) row = state[i] - 1 col = i + 1 ; while (col < N and row > = 0 and board[row][col] ! = 1 ) : col + = 1 ; row - = 1 ; if (col < N and row > = 0 and board[row][col] = = 1 ) : attacking + = 1 ; # Return pairs. return int (attacking / 2 ); # A utility function that # generates a board configuration # given the state. def generateBoard( board, state): fill(board, 0 ); for i in range (N): board[state[i]][i] = 1 ; # A utility function that copies # contents of state2 to state1. def copyState( state1, state2): for i in range (N): state1[i] = state2[i]; # This function gets the neighbour # of the current state having # the least objective value # amongst all neighbours as # well as the current state. def getNeighbour(board, state): # Declaring and initializing the # optimal board and state with # the current board and the state # as the starting point. opBoard = [[ 0 for _ in range (N)] for _ in range (N)] opState = [ 0 for _ in range (N)] copyState(opState, state); generateBoard(opBoard, opState); # Initializing the optimal # objective value opObjective = calculateObjective(opBoard, opState); # Declaring and initializing # the temporary board and # state for the purpose # of computation. NeighbourBoard = [[ 0 for _ in range (N)] for _ in range (N)] NeighbourState = [ 0 for _ in range (N)] copyState(NeighbourState, state); generateBoard(NeighbourBoard, NeighbourState); # Iterating through all # possible neighbours # of the board. for i in range (N): for j in range (N): # Condition for skipping the # current state if (j ! = state[i]) : # Initializing temporary # neighbour with the # current neighbour. NeighbourState[i] = j; NeighbourBoard[NeighbourState[i]][i] = 1 ; NeighbourBoard[state[i]][i] = 0 ; # Calculating the objective # value of the neighbour. temp = calculateObjective( NeighbourBoard, NeighbourState); # Comparing temporary and optimal # neighbour objectives and if # temporary is less than optimal # then updating accordingly. if (temp < = opObjective) : opObjective = temp; copyState(opState, NeighbourState); generateBoard(opBoard, opState); # Going back to the original # configuration for the next # iteration. NeighbourBoard[NeighbourState[i]][i] = 0 ; NeighbourState[i] = state[i]; NeighbourBoard[state[i]][i] = 1 ; # Copying the optimal board and # state thus found to the current # board and, state since c+= 1 doesn't # allow returning multiple values. copyState(state, opState); fill(board, 0 ); generateBoard(board, state); def hillClimbing(board, state): # Declaring and initializing the # neighbour board and state with # the current board and the state # as the starting point. neighbourBoard = [[ 0 for _ in range (N)] for _ in range (N) neighbourState = [ 0 for _ in range (N)] copyState(neighbourState, state); generateBoard(neighbourBoard, neighbourState); while True : # Copying the neighbour board and # state to the current board and # state, since a neighbour # becomes current after the jump. copyState(state, neighbourState); generateBoard(board, state); # Getting the optimal neighbour getNeighbour(neighbourBoard, neighbourState); if (compareStates(state, neighbourState)) : # If neighbour and current are # equal then no optimal neighbour # exists and therefore output the # result and break the loop. printBoard(board); break ; elif (calculateObjective(board, state) = = calculateObjective( neighbourBoard,neighbourState)): # If neighbour and current are # not equal but their objectives # are equal then we are either # approaching a shoulder or a # local optimum, in any case, # jump to a random neighbour # to escape it. # Random neighbour neighbourState[randint( 0 , 100000 ) % N] = randint( 0 , 100000 ) % N; generateBoard(neighbourBoard, neighbourState); # Driver code state = [ 0 ] * N board = [[ 0 for _ in range (N)] for _ in range (N)] # Getting a starting point by # randomly configuring the board configureRandomly(board, state); # Do hill climbing on the # board obtained hillClimbing(board, state); # This code is contributed by phasing17. |
Javascript
// JS implementation of the // above approach let N = 8 // A utility function that configures // the 2D array "board" and // array "state" randomly to provide // a starting point for the algorithm. function configureRandomly(board, state) { // Iterating through the // column indices for ( var i = 0; i < N; i++) { // Getting a random row index state[i] = Math.floor(Math.random() * 100000) % N; // Placing a queen on the // obtained place in // chessboard. board[state[i]][i] = 1; } } // A utility function that prints // the 2D array "board". function printBoard(board) { for ( var i = 0; i < N; i++) { process.stdout.write( " " ); for ( var j = 0; j < N; j++) { process.stdout.write(board[i][j] + " " ); } process.stdout.write( "\n" ); } } // A utility function that prints // the array "state". function printState( state) { for ( var i = 0; i < N; i++) { process.stdout.write( " " + state[i] + " " ); } process.stdout.write( "\n" ); } // A utility function that compares // two arrays, state1 and state2 and // returns true if equal // and false otherwise. function compareStates(state1, state2) { for ( var i = 0; i < N; i++) { if (state1[i] != state2[i]) { return false ; } } return true ; } // A utility function that fills // the 2D array "board" with // values "value" function fill(board, value) { for ( var i = 0; i < N; i++) { for ( var j = 0; j < N; j++) { board[i][j] = value; } } } // This function calculates the // objective value of the // state(queens attacking each other) // using the board by the // following logic. function calculateObjective( board, state) { // For each queen in a column, we check // for other queens falling in the line // of our current queen and if found, // any, then we increment the variable // attacking count. // Number of queens attacking each other, // initially zero. var attacking = 0; // Variables to index a particular // row and column on board. var row, col; for ( var i = 0; i < N; i++) { // At each column 'i', the queen is // placed at row 'state[i]', by the // definition of our state. // To the left of same row // (row remains constant // and col decreases) row = state[i], col = i - 1; while (col >= 0 && board[row][col] != 1) { col--; } if (col >= 0 && board[row][col] == 1) { attacking++; } // To the right of same row // (row remains constant // and col increases) row = state[i], col = i + 1; while (col < N && board[row][col] != 1) { col++; } if (col < N && board[row][col] == 1) { attacking++; } // Diagonally to the left up // (row and col simultaneously // decrease) row = state[i] - 1, col = i - 1; while (col >= 0 && row >= 0 && board[row][col] != 1) { col--; row--; } if (col >= 0 && row >= 0 && board[row][col] == 1) { attacking++; } // Diagonally to the right down // (row and col simultaneously // increase) row = state[i] + 1, col = i + 1; while (col < N && row < N && board[row][col] != 1) { col++; row++; } if (col < N && row < N && board[row][col] == 1) { attacking++; } // Diagonally to the left down // (col decreases and row // increases) row = state[i] + 1, col = i - 1; while (col >= 0 && row < N && board[row][col] != 1) { col--; row++; } if (col >= 0 && row < N && board[row][col] == 1) { attacking++; } // Diagonally to the right up // (col increases and row // decreases) row = state[i] - 1, col = i + 1; while (col < N && row >= 0 && board[row][col] != 1) { col++; row--; } if (col < N && row >= 0 && board[row][col] == 1) { attacking++; } } // Return pairs. return Math.floor(attacking / 2); } // A utility function that // generates a board configuration // given the state. function generateBoard( board, state) { fill(board, 0); for ( var i = 0; i < N; i++) { board[state[i]][i] = 1; } } // A utility function that copies // contents of state2 to state1. function copyState( state1, state2) { for ( var i = 0; i < N; i++) { state1[i] = state2[i]; } } // This function gets the neighbour // of the current state having // the least objective value // amongst all neighbours as // well as the current state. function getNeighbour(board, state) { // Declaring and initializing the // optimal board and state with // the current board and the state // as the starting point. var opBoard = new Array(N); for ( var i = 0; i < N; i++) opBoard[i] = new Array(N).fill(0); var opState = new Array(N).fill(0); copyState(opState, state); generateBoard(opBoard, opState); // Initializing the optimal // objective value var opObjective = calculateObjective(opBoard, opState); // Declaring and initializing // the temporary board and // state for the purpose // of computation. var NeighbourBoard = new Array(N).fill( new Array(N).fill(0)); var NeighbourState = new Array(N).fill(0); copyState(NeighbourState, state); generateBoard(NeighbourBoard, NeighbourState); // Iterating through all // possible neighbours // of the board. for ( var i = 0; i < N; i++) { for ( var j = 0; j < N; j++) { // Condition for skipping the // current state if (j != state[i]) { // Initializing temporary // neighbour with the // current neighbour. NeighbourState[i] = j; NeighbourBoard[NeighbourState[i]][i] = 1; NeighbourBoard[state[i]][i] = 0; // Calculating the objective // value of the neighbour. var temp = calculateObjective( NeighbourBoard, NeighbourState); // Comparing temporary and optimal // neighbour objectives and if // temporary is less than optimal // then updating accordingly. if (temp <= opObjective) { opObjective = temp; copyState(opState, NeighbourState); generateBoard(opBoard, opState); } // Going back to the original // configuration for the next // iteration. NeighbourBoard[NeighbourState[i]][i] = 0; NeighbourState[i] = state[i]; NeighbourBoard[state[i]][i] = 1; } } } // Copying the optimal board and // state thus found to the current // board and, state since c++ doesn't // allow returning multiple values. copyState(state, opState); fill(board, 0); generateBoard(board, state); } function hillClimbing(board, state) { // Declaring and initializing the // neighbour board and state with // the current board and the state // as the starting point. var neighbourBoard = new Array(N); for ( var i = 0; i < N; i++) neighbourBoard[i] = new Array(N).fill(0); var neighbourState = new Array(N).fill(0) copyState(neighbourState, state); generateBoard(neighbourBoard, neighbourState); do { // Copying the neighbour board and // state to the current board and // state, since a neighbour // becomes current after the jump. copyState(state, neighbourState); generateBoard(board, state); // Getting the optimal neighbour getNeighbour(neighbourBoard, neighbourState); if (compareStates(state, neighbourState)) { // If neighbour and current are // equal then no optimal neighbour // exists and therefore output the // result and break the loop. printBoard(board); break ; } else if (calculateObjective(board, state) == calculateObjective( neighbourBoard, neighbourState)) { // If neighbour and current are // not equal but their objectives // are equal then we are either // approaching a shoulder or a // local optimum, in any case, // jump to a random neighbour // to escape it. // Random neighbour neighbourState[(Math.floor(Math.random() * 100000) % N)] = Math.floor(Math.random() * 100000) % N; generateBoard(neighbourBoard, neighbourState); } } while ( true ); } // Driver code var state = new Array(N).fill(0); var board = new Array(N); for ( var i = 0; i < N; i++) board[i] = new Array(N).fill(0); // Getting a starting point by // randomly configuring the board configureRandomly(board, state); // Do hill climbing on the // board obtained hillClimbing(board, state); // This code is contributed by phasing17. |
Output:
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0
Complexity Analysis
- The time complexity for this algorithm can be divided into three parts:
- Calculating Objective – The calculation of objective involves iterating through all queens on board and checking the no. of attacking queens, which is done by our calculateObjective function in O(N2) time.
- Neighbour Selection and Number of neighbours – The description of neighbours in our problem gives a total of N(N-1) neighbours for the current state. The selection procedure is best fit and therefore requires iterating through all neighbours, which is again O(N2).
- Search Space – Search space of our problem consists of a total of NN states, corresponding to all possible configurations of the N Queens on board. Note that this is after taking into account the additional constraint of one queen per column.
- Calculating Objective – The calculation of objective involves iterating through all queens on board and checking the no. of attacking queens, which is done by our calculateObjective function in O(N2) time.
- Therefore, the worst-case time complexity of our algorithm is O(NN). But, this worst-case occurs rarely in practice and thus we can safely consider it to be as good as any other algorithm there is for the N Queen problem. Hence, the effective time complexity consists of only calculating the objective for all neighbours up to a certain depth(no of jumps the search makes), which does not depend on N. Therefore, if the depth of search is d then the time complexity is O(N2 * N2 * d), which is O(d*N4).
Please Login to comment...