目录
N皇后问题
n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后之间不能相互攻击到彼此,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数 n,请你输出所有的满足条件的棋子摆法,以及一共有多少种能够满足条件的棋子摆法。
数据范围
1<=n<=20
分析
B站上关于N皇后问题的求解视频:
C++代码(递归回溯法)
#include<iostream>
using namespace std;
#define Max 20 //定义棋盘的最大值
#define MaxCount 1000 //定义最大的结果个数
int N;
int direction_x[8] = {0, 1, 1, 1, 0, -1, -1, -1}, direction_y[8] = {1, 1, 0, -1, -1, -1, 0, 1}; //控制8个方向
char queen[Max][Max]; //皇后位置棋盘
int attack[Max][Max]; //皇后可攻击状态棋盘
char solve[MaxCount][Max][Max]; //存储N皇后问题的全部解法
int solve_count = 0; //solve数组的第一维
/* 定义函数put_queen(),实现在(x,y)处放置皇后,对attack数组进行更新 */
void update_attack(int attack[][Max], int x, int y)
{
attack[x][y] = 1; //先将皇后所在位置(x,y)处置为1,表示不可再放置皇后
int nx,ny;
//通过两层循环,将该皇后可能攻击到的位置进行标记
for(int l=1; l<N; l++) //从皇后位置向1~N-1个距离延伸
{
for(int k=0; k<8; k++) //8个方向
{
nx = x + l * direction_x[k];
ny = y + l * direction_y[k];
if(nx>=0 && nx<N && ny>=0 && ny<N) //不越界
{
attack[nx][ny] = 1; //可以被皇后攻击
}
}
}
}
/*
回溯法求解N皇后的函数
k表示当前处理的行
n表示N皇后问题
queen存储皇后的位置
attack标记皇后的攻击位置
solve存储N皇后问题的全部解法*/
void backtrack(int k, int n, char queen[][Max], int attack[][Max])
{
if(k == n) //找到一个解
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
solve[solve_count][i][j] = queen[i][j]; //将结果queen存储至solve
}
}
solve_count++;
return;
}
//遍历0~n-1列,在循环中,回溯试探皇后可以放置的位置
for(int i=0; i<n; i++)
{
if(attack[k][i] == 0) //判断当前第k行第i列是否可以放置皇后
{
int temp[Max][Max];
for(int a=0; a<n; a++) //备份attack数组
{
for(int b=0; b<n; b++)
{
temp[a][b] = attack[a][b];
}
}
queen[k][i] = '*'; //放置皇后在该处
update_attack(attack, k, i); //更新attack数组
backtrack(k+1, n, queen, attack); //递归试探第k+1行的皇后放置情况(借助递归,进行回溯)
for(int a=0; a<n; a++) //恢复attack数组
{
for(int b=0; b<n; b++)
{
attack[a][b] = temp[a][b];
}
}
queen[k][i] = 'o'; //恢复queen数组
}
}
}
int main()
{
cout<<"请输入皇后个数:"<<endl;
cin>>N; //皇后个数为N
//初始化棋盘和皇后可攻击状态棋盘(*代表皇后所在棋盘位置、o代表普通棋盘位置;0表示不可被攻击,1表示可以被攻击)
for(int i=0; i<N; i++)
{
for(int j=0; j<N; j++)
{
queen[i][j] = 'o';
attack[i][j] = 0;
}
}
backtrack(0, N, queen, attack);
cout<<N<<"皇后问题的解的个数为:"<<solve_count<<endl;
cout<<"具体的解为:"<<endl;
for(int i=0; i<solve_count; i++)
{
for(int m=0; m<N; m++)
{
for(int n=0; n<N; n++)
{
cout<<solve[i][m][n]<<" ";
}
cout<<endl;
}
cout<<endl;
}
return 0;
}
非递归回溯法
非递归方法的一个重要问题是,何时回溯及如何回溯的问题。程序首先对N行中的每一行进行探测,寻找该行中可以放置皇后的位置,具体方法是对该行的每一列进行探测,看是否可以放置皇后,如果可以,则在该列放置一个皇后,然后继续探测下一行的皇后位置。如果在某一行已经探测完所有的列都没有找到可以放置皇后的列,此时就应该回溯,把上一行皇后的位置往右移一列,如果上一行皇后移动后也找不到位置,则继续回溯直至前面的某一行找到皇后的位置或回溯到第一行,如果第一行皇后也无法找到可以放置皇后的位置,则说明已经找到所有的解,程序终止。如果该行已经是最后一行,则探测完该行后,如果找到放置皇后的位置,则说明找到一个结果,打印出来。但是此时并不能在此处结束程序,因为我们要找的是所有N皇后问题所有的解,此时应该清除该行的皇后,从当前放置皇后列数的下一列继续探测。
C++代码(非递归回溯法)
#include<iostream>
#include<ctime>
#include<cmath>
using namespace std;
#define Max 20 //定义棋盘的最大值
int a[Max];
int show(int S) //定义输出函数
{
int i,p,q;
int b[Max][Max]={0}; //定义并初始化b[][]输出数组
for(i=1; i<=S; i++) //按横列i顺序输出a[i]数组坐标
{
b[i][a[i]] = 1;
printf("(%d,%d)\t",i,a[i]);
}
printf("\n");
for(p=1;p<=S;p++) //按棋盘的横列p顺序标明皇后的位置
{
for(q=1;q<=S;q++)
{
if(b[p][q]==1) //在第p行第q列放置一个皇后棋子
printf("●");
else
printf("○");
}
printf("\n");
}
return 0;
}
int check(int k) //定义check函数
{
int i;
for(i=1; i<k; i++) //将第k行与前面的第1~~k-1行进行判断
{
if((a[i]==a[k]) || (a[i]-a[k]==k-i) || (a[i]-a[k]==i-k)) //检查是否有多个皇后在同一条直线上
{
return 0;
}
}
return 1;
}
void check_m(int num) //定义函数
{
int k = 1, count = 0;
printf("The possible configuration of N queens are:\n");
a[k] = 1;
while(k > 0)
{
if(k<=num && a[k]<=num)//从第k行第一列的位置开始,为后续棋子选择合适位子
{
if(check(k) == 0) //第k行的a[k]列不能放置皇后
{
a[k]++; //继续探测当前行的下一列:a[k]+1
}
else
{
k++; //第K行的位置已经确定了,继续寻找第k+1行皇后的位置
a[k] = 1; //从第一列开始查找
}
}
else
{
if(k>num) //若满足输出数组的要求则输出该数组
{
count++;
printf("[%d]: ",count);
show(num); //调用输出函数show()
}
//如果k>num会执行下面两行代码,因为虽然找到了N皇后问题的一个解,但是要找的是所有解,需要回溯,从当前放置皇后的下一列继续探测
//如果a[k]>num也会执行下面两行代码,就是说在当前行没有找到可以放置皇后的位置,于是回溯,从上一行皇后位置的下一列继续探测
k--; //棋子位置不符合要求,则退回前一步
a[k]++; //继续试探下一列位置
}
}
printf("The count is: %d \n",count);
}
int main()
{
clock_t start,finish;
int n;
printf("请输入皇后个数:");
scanf("%d",&n);
start = clock();
printf("\n使用非递归回溯法解决 %d 皇后问题时的运行情况:\n",n);
check_m(n);
finish = clock();
printf("计算时间 %.2f ms\n", (double) (finish - start));
system("pause");
return 0;
}
使用位运算求解N皇后问题
位运算求解N皇后代码
#include<iostream>
#include<list>
using namespace std;
//方案链表
list<int> sol;
//棋盘大小
int n;
//棋盘摆满时的数字
int uplimit;
void print()
{
cout<<endl;
list<int>::iterator i;
for(i = sol.begin(); i != sol.end(); i++)
{
int temp = *i, count = 0;
while(temp != 1)
{
count++;
temp = temp >> 1;
}
for(int j = 0; j < count; j++)
cout<<"o"<<" ";
cout<<"*"<<" ";
for(int j = count+1; j < n; j++)
cout<<"o"<<" ";
cout<<endl;
}
cout<<endl;
}
//r:皇后对行占有情况,
//ld:从左上角到右下角对角线皇后占有情况,
//rd:右上角到左下角对角线皇后占有情况
void doAns(int r, int ld, int rd)
{
if(r != uplimit) //当r==uplimit时,表示每一行都有皇后
{
//还没摆完,取能放的位置
//r|ld|rd进行的是或运算,求出第r行哪个位置是可利用的(可利用位的值是0),
//然后取反(可利用位的值是1)与uplimit进行与运算,是为了保证pos的位数
int pos = uplimit & ~(r | ld | rd);
//若还有位置,如00001011表示能放在5、7、8位
while(pos != 0)
{
//取低位
int p = pos & (~pos + 1); //即 p = pos & -pos
//更新位置,将这个可利用位去除
pos -= p;
//答案入
sol.push_back(p);
//更新禁手,并进行下一层更新
//两个对角线方向的禁位对下一行的影响需要平移一位
doAns(r | p, (ld | p)<<1, (rd | p)>>1); //(ld | p)<<1和(rd | p)>>1是将对角线左/右移
//答案出
sol.pop_back();
}
}
else
print();
}
int main()
{
while(cin>>n)
{
//如果n为8,则uplimit为11111111,表示每一行都有皇后了
uplimit = (1 << n) - 1;
doAns(0, 0, 0);
}
return 0;
}