在国际象棋中,皇后是最强大的一枚棋子,可以吃掉与其在同一行、列和斜线的敌方棋子。比中国象棋里的车强几百倍,比她那没用的老公更是强的飞起(国王只能前后左右斜线走一格)。上图右边高大的棋子即为皇后。
八皇后问题是这样一个问题:将八个皇后摆在一张8*8的国际象棋棋盘上,使每个皇后都无法吃掉别的皇后,一共有多少种摆法?此问题在1848年由棋手马克斯·贝瑟尔提出,岂止是有年头,简直就是有年头,82年的拉菲分分钟被秒的渣都不剩。
八皇后问题是典型的回溯法解决的问题,我们以这个问题为例介绍回溯法。
所谓回溯法,名字高大上,思想很朴素。设想把你放在一个迷宫里,想要走出迷宫,最直接的办法是什么呢?没错,试。先选一条路走起,走不通就往回退尝试别的路,走不通继续往回退,直到找到出口或所有路都试过走不出去为止。
这里稍微用数学分析一下,用i,j表示当前正在检测的两列(i外层for循环,j内层for循环),那么a[i] ,a[j] 的值就分别表示当前检测列棋子摆放的位置即行(每列只有1个棋子)。
如果两个棋子对角线冲突(正反对角线冲突),则必然有:
abs(a[i]-a[j]) == i-j
优化整理后的冲突判断代码就出炉了,如下所示:
//位置冲突算法
bool Chongtu(int a[], int n)//a[]位置数组,n皇后个数
{
for (int i = 2; i <= n; ++i)//i:位置
for (int j = 1; j <= i-1; ++j)//j:位置
if ((a[i] == a[j]) || (abs(a[i]-a[j]) == i-j))//1:在一行;2:在对角线上
return false; //冲突
return true;//不冲突
}
关于内层for循环j<=i-1,因为判断第i个棋子是否冲突(摆放是否合理),我们只需要和前面i-1列校对就ok了。这样也保证了,i>j的恒成立。所以对角线冲突了简化了一下。
好了,该说明的都说明了,现在编写第一个八皇后代码~~~~
枚举法:
思想:八重枚举,枚举出所以摆放的情况(不管合理不合理),然后到第八层for里面判断当前枚举出来的情况是否合理~~~~
#include <stdio.h>
#include <math.h>
//位置冲突算法
bool Chongtu(int a[], int n)//a[]位置数组,n皇后个数
{
int i = 0, j = 0;
for (i = 2; i <= n; ++i)//i:位置
for (j = 1; j <= i-1; ++j)//j:位置
if ((a[i] == a[j]) || (abs(a[i]-a[j]) == i-j))//1:在一行;2:在对角线上
return false; //冲突
return true;//不冲突
}
//八皇后:枚举算法
void Queens8()
{
int a[9] = {0}; //用于记录皇后位置:(第0行0列我们不用)。如:a[3] = 4;表示第3列第4行位置有皇后
int i = 0,count = 0; //用于计数
for (a[1] = 1; a[1] <= 8; ++a[1])
for (a[2] = 1; a[2] <= 8; ++a[2])
for (a[3] = 1; a[3] <= 8; ++a[3])
for (a[4] = 1; a[4] <= 8; ++a[4])
for (a[5] = 1; a[5] <= 8; ++a[5])
for (a[6] = 1; a[6] <= 8; ++a[6])
for (a[7] = 1; a[7] <= 8; ++a[7])
for (a[8] = 1; a[8] <= 8; ++a[8])
{
if (!Chongtu(a,8))//如果冲突,则继续枚举
continue;
else
{
printf("第%d情况:",++count);
for (i = 1; i <= 8; ++i)
printf("%d ",a[i]);//打印某种情况
printf("\n");
}
}
}
//主函数
int main()
{
Queens8();
return 0;
}
回溯法
#include <stdio.h>
#include <math.h>
int a[9] = {0};
int n = 8, count = 0;
//位置冲突算法
bool Chongtu(int a[], int n)//a[]位置数组,n皇后个数
{
int i = 0, j = 0;
for (i = 2; i <= n; ++i)//i:位置
for (j = 1; j <= i-1; ++j)//j:位置
if ((a[i] == a[j]) || (abs(a[i]-a[j]) == i-j))//1:在一行;2:在对角线上
return false; //冲突
return true;//不冲突
}
//八皇后问题:回溯算法(递归版)
void Queens8(int k) //参数k:递归摆放第k个皇后
{
int i = 0;
if (k > n) //k>n:即k>8表示最后一个皇后摆放完毕
{
printf("第%d种情况:",++count);
for (i = 1; i <= n; ++i)
printf("%d ",a[i]);//打印情况
printf("\n");
}
else //8个皇后未全部摆放完毕
{
for (i = 1; i <= n; ++i)//摆放第k个皇后时(转下一行)
{ //依次从列顶端开始搜索,一直到列底端,直到找到合适位置,如果未找到,自动返回上层递归(回溯)
a[k] = i;
if (Chongtu(a,k))//不冲突
Queens8(k+1);//递归摆放下一个皇后
}
}
return;
}
//主函数
int main()
{
Queens8(1);//参数1:表示摆放第1个皇后
return 0;
}
// N皇后问题
#include <iostream>
using namespace std;
#define N 8
bool matrix[N + 1][N + 1] = {0};
bool IsLegal(bool matrix[N + 1][N + 1], const int &i, const int &j)
{
// 判断前面的i-1个棋子与matrix[i][j]是否冲突,i为1时合法
for (int m = 1; m <= i - 1; ++m) {
for (int n = 1; n <= N; ++n) { // 实际每一行只有一个棋子
if (matrix[m][n] == 1) {
if ( n == j || abs(i - m) == abs(j - n) ) // key, not bad
return false;
}
}
}
return true;
}
void Print(bool matrix[N + 1][N + 1])
{
static int count = 1;
printf("Case %d:\n", count++);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
matrix[i][j] == 1 ? printf("%c ", 2) : printf(". ");
}
cout << endl;
}
cout << endl;
}
void Trial(const int i)
{
// 进入本函数时,在N*N的棋盘前i-1行已放置了互不攻击的i-1个棋子
// 现从第i行起继续为后续棋子选择合适位置
if (i > N) // 输出当前的合法布局
Print(matrix);
else
for (int j = 1; j <= N; ++j) {
matrix[i][j] = 1;
if ( IsLegal(matrix, i, j) )
Trial(i + 1);
matrix[i][j] = 0;
}
}
int main(void)
{
Trial(1);
return 0;
}