八皇后问题
是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
题意条件:
即在一个8*8的二维数字中选择8个不同行,不同列,不同正对角线,反对角线的位置。
题解思路:
- 这里采用一种简单的递归的想法,即放一个便判断当前所放位置的合理性,如果不合理则进行回溯,返回上一步的状态。
- 放置的方法可以考虑一整行一整行放置,这样做的好处是不用考虑同一行重复的情况。
- 当一种方法找出来后,接下来寻找下一种方法,此时也应当回溯,并对下一个位置进行判断。
- 由这种思路自然相当结束条件就是当每一行都找到第8个位置时退出递归。
题解步骤:
- 设置数组并全置0(0表示此位置没有棋子,1表示此位置有棋子)
- 可以先初步考虑递归函数的参数列表:数组的传递是必须的,因为是按行进行判断加点所以当前处理行的行数传递,可写做 void eight(int (*arr)[N],int n);
- 因为行在参数列表中已经控制,所以函数内部直接控制列即可。
- 在列内部,先对其赋值,然后去进行判断是否满足条件,满足就进入下一层递归,不满足就退出并对当前位置进行归0处理
- 考虑打印条件,当处理完第8行时,下一次递归,可用作打印,即行下标为8时,打印
- 打印完毕后返回上一层递归,此时此位置信息无用,归零(和不满足时处理一样,所以可以进行代码合并),循环进行下一个位置判断
- 此时代码当每行都进行到第8列时,会不断回溯处理知道退出。
- 再来考虑判断条件,实际上因为是按行处理,所以只需要判断此位置以上的行的信息和本位置信息的冲突。即判断此位置上半列,此位置左上对角线,右上对角线,即可。有冲突,直接返回0,满足条件返回1。
- 至此,代码完成。
- 将所有的8用define N来进行宏定义可实现N皇后的处理。
代码实例:
这里用C或C++语言是一样的。
#define N 8
int sum = 1;
void print(int(*arr)[N])
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
cout << arr[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
//n:当前行 j:当前列
int panduan(int(*brr)[N], int n, int j)
{
//判断其正上方
for (int i = 0; i < n; i++)
{
if (brr[i][j] == 1)
return 0;
}
//判断左上正对角线
for (int i = 1; (n - i) >= 0 && (j - i) >= 0; i++)//行
{
if (brr[n-i][j-i] == 1)
{
return 0;
}
}
//判断右上对象线
for (int i = 1; (n - i) >= 0 && (j + i) < N; i++)
{
if (brr[n-i][j+i] == 1)
{
return 0;
}
}
return 1;
}
//arr:存储数组 n:当前处理行数
void eight(int (*arr)[N],int n)
{
//判断是否放满
if (n == N)
{
cout << "第" << sum++ << "种" << endl;
print(arr);
}
else//开始放入
{
for (int j = 0; j < N; j++)//列
{
arr[n][j] = 1;
if (panduan(arr, n, j) == 1)
{
//当前行插入正确,进入下一行
eight(arr, n + 1);
}
arr[n][j] = 0;
}
}
}
int main()
{
int arr[N][N] = { 0 };
//处理赋值
eight(arr, 0);
return 0;
}
结果显示: