Problem
八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。
Solution
八个皇后中任意两个不能处在同一行,所以每个皇后必须占据一行,及一列。我们采用回溯法的思想去解。首先摆放好第1行皇后的位置,然后在不冲突的情况下摆放第2行皇后的位置。到第i行时,如果没有一个位置可以无冲突的摆放皇后,则回溯到第i-1行,寻找第i-1行皇后的下一个可以摆放的位置。
回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
* 找到一个可能存在的正确的答案
* 在尝试了所有可能的分步方法后宣告该问题没有答案
总结一下,用回溯的方法解决8皇后问题的步骤为:
1)从第一列开始,为皇后找到安全位置,然后跳到下一列
2)如果在第n列出现死胡同,如果该列为第一列,棋局失败,否则后退到上一列,再进行回溯
3)如果在第8列上找到了安全位置,则棋局成功。
在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。
Source code
#include <iostream>
using namespace std;
int num = 1;
bool IsOk(int i, int j, int n, bool **map)
{
for (int k = 0; k < i; k++)
for (int h = 0; h < n; h++)
{
if (map[k][h])
{
if (h == j)//同一列
return false;
if (abs(k - i) == abs(h - j))//对角线
return false;
}
}
return true;
}
void print(bool **map, int n)
{
cout << "solution :" << num++ << " :\n";
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (map[i][j])
cout << "Q" << " ";
else
cout << "." << " ";
}
cout << endl;
}
}
void Search(int i, int n, bool **map)//寻找第i行queen可以摆放的位置
{
if (i >= n )//递归出口
print(map, n);
else
{
for (int j = 0; j < n; j++)
{
map[i][j] = true;//依次尝试i行queen的所有可能位置
if (IsOk(i, j, n, map))
Search(i + 1, n, map);
//else
map[i][j] = false;
//查找第i行元素的下一列位置:两种情况。A:IsOk(i, j, n, map)= false.B.递归回溯时,下一行找不到合适的位置or已经将map[i][j]=1情况下的所有可能位置都遍历完。
//此处不加else,递归回溯时,如果加了else此时的判断条件并不是IsOk(i, j, n, map)= false,而是IsOk(i-1, j, n, map)= true.是IsOk(i+1, j, n, map)= false.
//当递归返回时并不会执行map[i][j]=false语句,因此达不到回溯效果
}
}
//前面i-1行的queen已经无冲突的摆好
}
int main(void)
{
int n;
cout << "Input the number of queens!" << endl;
cin >> n;
//initial map
bool **map = new bool *[n];
for (int i = 0; i < n; i++)
map[i] = new bool[n];
for (int i = 0; i < n;i++)
for (int j = 0; j < n; j++)
map[i][j] = false;
Search(0, n, map);
return 0;
}