八皇后问题
题目:
在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。
Input
无输入。
Output
按给定顺序和格式输出所有八皇后问题的解(见Sample Output)。
Sample Input
Sample Output
No. 1
1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0
Hint
此题可使用函数递归调用的方法求解。
四个方法(矩阵维护法、递归法、迭代法、手动堆栈法)
一、 题目分析
背景:八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 ≥ 1 或 n1 ≥ 4 时问题有解。
八皇后问题最早是由国际西洋棋棋手马克斯·贝瑟尔于1848年提出。之后陆续有数学家对其进行研究,其中包括高斯和康托,并且将其推广为更一般的n皇后摆放问题。八皇后问题的第一个解是在1850年由弗朗兹·诺克给出的。诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。1874年,S.冈德尔提出了一个通过行列式来求解的方法,这个方法后来又被J.W.L.格莱舍加以改进。
思路:本题,我们只用讨论8*8的情况。很显然,我们可以用穷举法举出所有的矩阵情况,这不失为一种方法。同时,也很容易看出许多情况是不可能出现的,甚至是可以用边界值排除的。为了提高算法精炼程度,我们理应进行优化。
但在根本上,这还是可以看作是一个有条件的穷举算法。
二、 数据结构
static int gEightQueen[8] = { 0 }, gCount = 0;
一个数组记录每行皇后位置,数组序号代表行数。
整形变量代表可能出现的情形数目。
数据结构很简单,作者并没有将所有矩阵保留记录,而是选择性输出。
所以,在空间复杂度上,这个算法是极优的。
三、 算法及代码说明
1、 主函数
int main(int argc, char*argv[]) //主函数。
{
eight_queen(0);
cout << "total=" << gCount << endl;
return 0;
}
2、 运算主体
void eight_queen(int index)
{
int loop;
for (loop = 0; loop < 8; loop++)
{
if (check_pos_valid(index, loop))
{
gEightQueen[index] = loop;
if (7 == index) //递归结束条件,行数为8.
{
gCount++, print();
gEightQueen[index] = 0; //作用同下
return;
}
eight_queen(index + 1); //递归调用
gEightQueen[index] = 0; //进行一种情形后,将上一层调用函数数组元素归零,继续下一种情况。
}
}
}
3、 检验并输出
int check_pos_valid(int loop, int value)//检查是否存在有多个皇后在同一行/列/对角线的情况
{
int index; //跟上一个函数参数名重合,并且命名不形象,是该程序设计上的一大败笔。
int data;
for (index = 0; index < loop; index++)
{
data = gEightQueen[index];
if (value == data) //排除同列
return 0;
if ((index + data) == (loop + value)) // 排除斜线
return 0;
if ((index - data) == (loop - value)) //这个检验方式很有特点,包括两种斜线,还简练 ,值得学习。
return 0;
}
return 1;
}
void print()//输出每一种情况下棋盘中皇后的摆放情况
{
for (int i = 0; i < 8; i++)
{
int inner;
for (inner = 0; inner < gEightQueen[i]; inner++)
cout << "0";
cout <<"#";
for (inner = gEightQueen[i] + 1; inner < 8; inner++)
cout << "0";
cout << endl;
}
cout << "==========================n";
}
算法:
主程序:采用递归调用替代普通的循环,每一种情况对应一个递归调用。而且,利用返回的层级不同,避开已有的情况。
判断:除了形参的命名上有些失误,本身的判断语句简练的包括了三种可能的重复。
输出:利用数据结构本身的便利性,输出时只用了解皇后位置。
四、 算法分析
1、空间复杂度:这个程序,其最优异的点应该就在空间的把握上,简单的一维数组和整形,避免了二维数组的大量使用。抓住了本题的核心,只储存了皇后的位置,使输出很简单。
2、时间复杂度:可以从性能测试看出,递归调用要1964次,每次调用中要进行循环尝试,每次循环尝试要进行一次循环检查,每次检查的次数取决于index(行数)的大小,可知为15398。设每行尝试n次,循环尝试m次,检查p次,每一次运行次数是n*m*p,大体是在n的三次方量级。递归算法并没有相较于直接循环有质的飞跃。
下图是性能数据:
(1)平面输出图
(2)调用图
4、 对比
#include<iostream>
using namespace std;
int queen[9]={-1,-1,-1,-1,-1,-1,-1,-1,-1};
int count=0;
bool available(int pointi,int pointj){//判断某个皇后是否与已有皇后冲突
for(int i=1;i<pointi;i++){
if(pointj==queen[i])return false;//同一列拒绝
if((pointi-i)==(pointj-queen[i]))return false;//同一主对角线拒绝
if((pointi-i)+(pointj-queen[i])==0)return false;//同一副对角线拒绝
}
return true;
}
void findSpace(int queenNumber){//在第queenNumber行找能放皇后的位置
for(int i=1;i<9;i++){//从1~8遍历这一行的八个空位
if(available(queenNumber,i)){
//如果可以放这个位置就记录下第queenNumber个皇后的位置
queen[queenNumber]=i;
if(queenNumber==8){//如果八个皇后都放满了统计一下
count++;
return;
}
int nextNumber=queenNumber+1;//还有皇后没放递归放下一个皇后
findSpace(nextNumber);
}
}
queen[--queenNumber]=-1;//如果这一行没有可放的位置说明上一行皇后放的位置不行,要为上一个皇后寻找新的可放位置
return;
}
int main(){
findSpace(1);//从(1,1)开始递归好理解
cout<<count<<endl;
return 0;
}
/*---------------------
作者:codes_first
来源:CSDN
原文:https://blog.csdn.net/codes_first/article/details/78474226
版权声明:本文为博主原创文章,转载请附上博文链接
5、 其他方法
用循环代替递归,有同样效果,但是运行次数会更多。