终于搞定了用递归(dfs)来写n皇后和2n皇后问题,赶紧来总结一下,省的忘了= - =
什么是n皇后问题
首先要从8皇后问题说起
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
——来自百度百科
简单来说就是一个棋盘上要放皇后,而放这个皇后的要求则是他的同行同列以及同一斜线下都不能有其他皇后。一共有8个皇后,求有多少种放法。
思路
要想解n皇后问题,使用递归的方法十分好理解。理一下思路:
用一个一维数组f[i]来存放皇后的地方
下标i表示行,f[i]表示在i行上的皇后的列
于是一个皇后的坐标可以表示为(i,f[i])
好的,开始分析
从棋盘的第一行第一列开始递归(h=1,l=1)
先判断这个坐标能不能放棋子(根据题意决定,有可能棋盘的固定地方不让放棋子)
如果能放棋子,那么判断这个地方能不能放皇后(同一行同一列以及同一斜线下有没有其他皇后)
如果能放皇后,那么放下皇后,h+1后递归下一行的每一列棋盘
如果不能放皇后,那就看下一列咯l+1
用for就能写出来
void dfs(int h,int f[])//行和皇后数组
{
if(h>n)//递归出口,当行超过最后一行之后代表每一行皇后都放好了
count++;
else
{
for(int l=1;l<=n;l++)//从第一列开始判断
{
if(!check[h][l])//根据题意判断是否能放棋子
continue;
else if(check(f,h,l))//判断是否能放皇后
{
f[h]=l;//记录皇后位置
dfs(h+1,f);//递归进下一行
}
}
}
return;
}
好的,核心算法的一半已经写出来了,接下来就是如何判断这个地方能不能放皇后
因为是一维数组,f[i]第i行只能存一个皇后,所以不存在行冲突
接下来只要判断每一行有没有另一个皇后跟他在同一列
只用判断i行之前的就行,因为i行以下也没有放新的皇后
判断完行冲突,再看对角线冲突
一句话就可以判断,两个位置的行数相减的绝对值等于两个列数相减的绝对值表示他们处在一条斜线上。
所以判断函数就写出来了
bool check(int f[],int h,int l)
{
for(int i=1;i<h;i++)//从第一行到h行之前就可
{
if(f[i]==l||abs(i-h)==abs(f[i]-l))//判断行冲突和斜线冲突
return false;
}
return true;
}
来两道例题练练手
例题一:
算法训练 王、后传说
时间限制:1.0s 内存限制:256.0MB
提交此题
问题描述
地球人都知道,在国际象棋中,后如同太阳,光芒四射,威风八面,它能控制横、坚、斜线位置。
看过清宫戏的中国人都知道,后宫乃步步惊心的险恶之地。各皇后都有自己的势力范围,但也总能找到相安无事的办法。
所有中国人都知道,皇权神圣,伴君如伴虎,触龙颜者死…
现在有一个n*n的皇宫,国王占据他所在位置及周围的共9个格子,这些格子皇后不能使用(如果国王在王宫的边上,占用的格子可能不到9个)。当然,皇后也不会攻击国王。
现在知道了国王的位置(x,y)(国王位于第x行第y列,x,y的起始行和列为1),请问,有多少种方案放置n个皇后,使她们不能互相攻击。
输入格式
一行,三个整数,皇宫的规模及表示国王的位置
输出格式
一个整数,表示放置n个皇后的方案数
样例输入
8 2 2
样例输出
10
数据规模和约定
n<=12
n皇后问题,从题意也可以读出x,y周围9个位置不能放棋子
所以是否能放棋子的条件就有了
上代码
#include<iostream>
#include<cmath>
using namespace std;
int x, y, n;//x,y表示国王
int ccount = 0;//全局命名count会冲突,所以ccount
bool check(int f[], int h,int l)
{
for (int i = 1; i < h; i++)
{
if (f[i] == l || abs(i - h) == abs(f[i] - l))
return false;
}
return true;
}
void dfs(int f[], int h)
{
if (h > n)
ccount++;
else
{
for (int l = 1; l <= n; l++)
{
if ((h == x || h == x - 1 || h == x + 1) && (l == y || l == y - 1 || l == y + 1))
//国王以及国王周围不能放棋子
continue;
if (check(f, h, l))
{
f[h] = l;
dfs(f, h + 1);
}
}
}
return;
}
int main()
{
cin >> n >> x >> y;
int f[13];
dfs(f, 1);
cout << ccount << endl;
return 0;
}
2N皇后问题
2N皇后问题跟N皇后问题不一样的地方就在于,2N皇后要放两种皇后,有黑皇后,白皇后
两个黑皇后不能在同行同列同斜线,两个白皇后也同理
所以思路其实也很简单,先放一种皇后,放到最后一行后,再放另一种皇后,放到最后一行后得出一个解
两个一维数组储存皇后位置
b[i],w[i]
黑皇后和白皇后
我们先放白皇后,在放黑皇后
在放黑皇后的时候,能否放置棋子的条件只要增加一个条件就行:
这个位置有没有放白皇后
增加这个判断之后就行了,再判断题意是否能在此放棋子
两种条件都满足后
再判断能不能放黑皇后就行了
递归代码如下:
void bdfs(int b[], int h)
{
if (h > n)//白皇后黑皇后都放好后,放法加一
ccount++;
else
{
for (int l = 1; l <= n; l++)
{
if (!check[h][l] || w[h] == l)//多加一条判断白皇后位置
continue;
else if (bcheck(b, h, l))
{
b[h] = l;
bdfs(b, h + 1);
}
}
}
return;
}
void wdfs(int w[], int h)
{
if (h > n)
{
bdfs(b, 1);//在最后一行的白皇后放下后,开始从头放黑皇后
}
else
{
for (int l = 1; l <= n; l++)
{
if (!check[h][l])
continue;
else if (wcheck(w, h, l))
{
w[h] = l;
wdfs(w, h + 1);
}
}
}
return;
}
皇后判断的方法跟N皇后是一样的,就看h之前的行有没有冲突皇后
斜线有没有冲突皇后, 都没有就能放
下面上例题
例题二:
基础练习 2n皇后问题
时间限制:1.0s 内存限制:512.0MB
提交此题 锦囊1 锦囊2
问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
我们自己输入的矩阵就是能放的棋盘,实际上还是放棋子的条件
上代码
#include<iostream>
#include<cmath>
using namespace std;
int check[9][9] = { 0 };//判断棋盘
int w[9];//白皇后位置
int ccount = 0;
int b[9];//黑皇后位置
int n;
bool checki(int c[], int h, int l)
{
for (int i = 1; i < h; i++)
{
if (c[i] == l || abs(i - h) == abs(c[i] - l))
return false;
}
return true;
}
void bdfs(int b[], int h)
{
if (h > n)//黑白皇后都放好后放法加一
ccount++;
else
{
for (int l = 1; l <= n; l++)
{
if (!check[h][l] || w[h] == l)//多判断一条白皇后位置
continue;
else if (checki(b, h, l))
{
b[h] = l;
bdfs(b, h + 1);
}
}
}
return;
}
void wdfs(int w[], int h)
{
if (h > n)//白皇后放好后开始放黑皇后
{
bdfs(b, 1);
}
else
{
for (int l = 1; l <= n; l++)
{
if (!check[h][l])//判断棋盘是否能放棋子
continue;
else if (checki(w, h, l))
{
w[h] = l;
wdfs(w, h + 1);
}
}
}
return;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
cin >> check[i][j];
}
wdfs(w, 1);
cout << ccount << endl;
return 0;
}
总结:
N皇后以及2N皇后问题用递归可以很快的写出来,但是缺点很明显,N大了递归时间复杂度爆炸大。也有可能炸栈内存。所以在N小的时候推荐用递归写法,至于非递归写法,改天在多理几下好了QAQ