N Queen是在N×N棋盘上放置N个国际象棋皇后的问题,这样就不会有两个女王互相攻击。
例如,以下是8 Queen问题的解决方案。
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
方法:想法是使用爬山算法。
- 虽然有诸如Backtracking之类的算法可以解决N Queen问题,但让我们采用AI方法来解决问题。
- 显然,AI不能一直保证全球范围内正确的解决方案,但它的成功率相当不错,约为97%,这还不错。
- 将给出问题中使用的所有术语的概念的描述,如下所示:
- 状态的概念–在此上下文中,状态是NXN板上N个皇后的任何配置。另外,为了减少搜索空间,我们添加一个附加约束,即特定列中只能有一个女王/王后。程序中的状态是使用长度为N的数组实现的,因此,如果state [i] = j,则列索引i和行索引j处都有一个女王/王后。
- 邻居的概念–某个州的邻居是指其他国家/地区的板配置,仅在单个女王/王后的位置上与当前状态的板配置不同。状态不同于其邻居的女王/王后可以在同一列的任何位置移动。
- 优化函数或目标函数–我们知道局部搜索是一种优化算法,可以搜索局部空间以优化将状态作为输入并提供一些值作为输出的函数。在此上下文中,状态的目标函数的值是互相攻击的皇后对的数量。我们的目标是找到具有最小目标值的状态。该函数的最大值为NC2,最小值为0。
算法:
- 从随机状态开始(即,电路板的随机配置)。
- 扫描当前状态的所有可能邻居,并跳至具有最高目标值的邻居(如果发现)。如果不存在,则其目标严格高于当前状态,但存在一个相等的邻居,然后跳转到任意随机邻居(避开肩膀和/或局部最优)。
- 重复步骤2,直到找到一个目标严格高于所有邻居目标的州,然后转到步骤4。
- 因此,在局部搜索之后发现的状态是局部最优或全局最优。无法逃避局部最优,但是每次遇到局部最优时添加随机邻居或随机重新启动会增加获得全局最优的机会(解决我们的问题)。
- 输出状态并返回。
- 显而易见,在我们的情况下,全局最优值为0,因为这是可以互相攻击的皇后对的最小数量。同样,随机重启有更高的机会达到全局最优,但是我们仍然使用随机邻居,因为我们的N个皇后问题不具有大量的局部最优,而且随机邻居比随机重启快。
- 结论:
- 随机邻居逃脱了肩膀,但逃脱局部最优的机会很小。
- 随机重启既可以逃脱肩膀,也有很大的机会逃脱局部最优。
以下是Hill-Climbing算法的实现:
CPP
// C++ implementation of the
// above approach
#include
#include
#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 simoultaneously
// 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 simoultaneously
// 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;
}
输出:
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
复杂度分析
- 该算法的时间复杂度可分为三个部分:
- 计算目标–目标的计算涉及遍历船上所有皇后区并检查编号。攻击皇后的数量,这是由我们的calculateObjective函数在O(N 2 )时间完成的。
- 邻居选择和邻居数量–对我们问题中邻居的描述给出了当前状态下总共N(N-1)个邻居。选择过程是最合适的,因此需要遍历所有邻居,也就是O(N 2 ) 。
- 搜索空间–我们问题的搜索空间总共包括N N 状态,对应于船上N个皇后区的所有可能配置。请注意,这是在考虑到每列一个女王/王后的附加约束之后。
- 因此,我们算法的最坏情况下的时间复杂度为O(N N ) 。但是,这种最坏情况在实践中很少发生,因此我们可以放心地认为它与N Queen问题所具有的任何其他算法一样好。因此,有效时间复杂度仅包括计算所有邻居的目标,直至达到一定深度(搜索不产生跳跃),这不依赖于N。因此,如果搜索深度为d,则时间复杂度为O(N 2 * N 2 * d) ,即O(d * N 4 )。