8皇后问题
问题描述:
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。
该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:
在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
计算机发明后,有多种计算机语言可以解决此问题。
分析:
一、八重循环列举出所有的结果,在其中筛选满足条件的结果,不过显而易见,这样的话效率会很低,这里不再赘述
二、深度优先遍历搜索,以行为基准,在每一行找满足条件的点,若找到,则进入下一行继续找,若没有找到,则回溯到上一行切入点的后边继续找,直到在尾行找到满足条件的点为止,,则一组序列已经找到,继续回溯到上一行,找其他组的序列,若倒数第二行已经全部找完所有可能的序列,则继续回溯,继续dfs搜索…,直到找到所有组合为止
基于这种思想,可以用递归+回溯的思想或者迭代+回溯的思想进行解决
具体的算法细节及分析都在代码注释中表明!
代码:
//************递归+回溯
#include<iostream>
using namespace std;
#include<cmath>
int queen[8] = { 0 }; //用一维数组来维护皇后落子的点,例如,queen[1] = 5表示第2行第6列有一个落子点(从0开始)
int _count = 0;
// 检测冲突,若冲突返回真
// 行 --------- 列
// point_r ----- point_c 要检测的行和列(确定的点)
// row ------- queen[row] 前n行已经找出来的点,看point是否与之前找出来的行列冲突
bool IsAttack(int point_r, int point_c)
{
for (int row = 0; row < point_r; ++row)
{
//由于是在把前n-1行中确定的每个点 与 第n行中的point点进行比较,所以不需要进行同一行的判断,肯定不相等
//只需检测 point点 是否与 前n-1行中的每个点 列号相等 或者 斜率相等,即在同一对角线上
if (point_c == queen[row] || (abs(queen[row] - point_c) == point_r - row))
{
return true;
}
}
return false;
}
//找点,递归,回溯
void find(int row) //row为行号
{
for (int col = 0; col < 8; ++col) //遍历第row行中的每一列,确定当前的选中点
{
if (!IsAttack(row, col)) //不起冲突时
{
if (row == 7) //已经进行到第7行,且第7行中的点已经确定时,代表一种组合已经确定,计数器++;回溯,返回第6层找其他的组合
{
_count++;
return;
}
queen[row] = col;
find(row + 1); //递归,没在尾行,且已经确定当前行的具体点,进入下一行的查找
}
}
//最内层的循环确定,回溯到其他层,继续查找其他组合
}
int main()
{
find(0);
cout << _count << endl;
return 0;
}
//************迭代+回溯
#include<iostream>
using namespace std;
#include<cmath>
int queen[8] = { -1 };
int _count = 0;
bool IsAttack(int point_r, int point_c)
{
for (int row = 0; row < point_r; ++row)
{
if (point_c == queen[row] || abs(point_c - queen[row]) == point_r - row)
{
return true;
}
}
return false;
}
void find()
{
int row = 0, col = 0;
while (row < 8) //控制行
{
while (col < 8) //控制列
{
if (!IsAttack(row, col)) //当前的点不起冲突时
{
queen[row] = col;
if (row == 7) //检测当前点是否在尾行,即是否找到一组完整的解
{
_count++; //计数器计数
col++; //循环自增到不满足条件,跳转到if(col==8)这一代码块中进行回溯处理,用时间换代码冗余量
// queen[row] = -1;
}
else
{
col = 0; //去下一行
break;
}
}
else
{
col++; //当前点不满足,找当前行的下一个点
}
}
//回溯
if (col == 8) //当前待检测的点越界
{
row--; //回溯一行
if (row < 0) //若回溯出界,则程序运行结束
{
return;
}
else
{
col = queen[row];
col++; //在上一行已确定的点之后继续找
// queen[row] = -1;
continue;
}
}
row++;
}
}
int main()
{
find();
cout << _count << endl;
return 0;
}
n皇后问题
其实要是完全理解了8皇后的思路,就会发现这两个问题其实是一样的处理办法,甚至代码都是一样的,只不过将8改为n就好,不过,为了充门面,还是把源码贴出来叭…
代码
//************递归 + 回溯
#include<iostream>
using namespace std;
#include<cmath>
int n = 0; //n个皇后
int queen[10] = { -1 };
int _count = 0;
bool IsAttack(int point_r, int point_c)
{
for (int row = 0; row < point_r; row++)
{
if (point_c == queen[row] || abs(point_c - queen[row]) == point_r - row)
{
return true;
}
}
return false;
}
void find(int row)
{
for (int col = 0; col < n; col++)
{
if (!IsAttack(row, col))
{
if (row == n - 1)
{
_count++;
return;
}
queen[row] = col;
find(row + 1);
}
}
}
int main()
{
cin >> n;
find(0);
cout << _count << endl;
return 0;
}
// *********************迭代+回溯
#include<iostream>
using namespace std;
#include<cmath>
int n = 0; //n个皇后
int queen[10] = { -1 };
int _count = 0;
bool IsAttack(int point_r, int point_c)
{
for (int row = 0; row < point_r; row++)
{
if (point_c == queen[row] || abs(point_c - queen[row]) == point_r - row)
{
return true;
}
}
return false;
}
void find()
{
int row = 0, col = 0;
while (row < n)
{
while (col < n)
{
if (!IsAttack(row, col))
{
queen[row] = col;
if (row == n - 1)
{
_count++;
col++;
}
else
{
col = 0;
break;
}
}
else
{
col++;
}
}
if (col == 8)
{
row--;
if (row < 0)
{
return;
}
col = queen[row];
col++;
continue;
}
row++;
}
}
int main()
{
cin >> n;
find();
cout << _count << endl;
return 0;
}
2n皇后问题
问题描述
问题描述
给定一个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
具体思路
其实吧,2n皇后就是n皇后问题的加强版,不过是需要在n皇后的基础上在加上一层n皇后。
首先,用一个数组将有限制的地址存储,等待后面的判断
其次,类似n皇后一样,分别用两个一维数组将黑白皇后的落子序列存储起来,数组下标为对应的行号,通过下标访问的值为列号
然后,类似n皇后,先深度优先遍历搜索将一个皇后的落子序列平铺出来,在此基础上,对另一个皇后的所有落子情况进行深度优先遍历搜索,当搜索到尾行且此时落子存在时,表示当前的一种解法可以将两个皇后的子全部落入棋盘中,此时计数器自增,然后再进行类似的回溯,找其他解法,直到全部找出来位置
代码
#include<iostream>
using namespace std;
#include<cmath>
int n = 0; //2n皇后
int m[10][10]; //地图
int blackPos[10], whitePos[10]; //黑白皇后的选点集,下标表示行,通过下标访问的值为列
int _count = 0; //方法计数器
bool IsAttack(int pos[], int point_r, int point_c) //当前点是否冲突
{
for (int row = 0; row < point_r; row++)
{
if (point_c == pos[row] || abs(point_c - pos[row]) == point_r - row)
{
return true;
}
}
return false;
}
//黑皇后落子
void SetBlack(int row)
{
if (row == n)
{
//黑皇后已落了n个子,计数器+1
_count++;
return;
}
//深度优先遍历搜索 黑皇后选定落子序列
for (blackPos[row] = 0; blackPos[row] < n; blackPos[row]++)
{
if (!IsAttack(blackPos, row, blackPos[row]) && m[row][blackPos[row]] == 1 && blackPos[row] != whitePos[row])
{
SetBlack(row + 1);
}
}
}
//白皇后落子
void SetWhite(int row)
{
if (row == n)
{
//白皇后已经落了n个子,即白皇后的一种序列已经确定,在此基础上对黑皇后的落子集合进行搜索
SetBlack(0);
return;
}
//深度优先遍历搜索,查找白皇后落子的序列
for (whitePos[row] = 0; whitePos[row] < n; whitePos[row]++)
{
if (!IsAttack(whitePos, row, whitePos[row]) && m[row][whitePos[row]] == 1)
{
SetWhite(row + 1);
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> m[i][j];
}
}
SetWhite(0);
cout << _count << endl;
return 0;
}