八皇后问题
1. 递归回溯法——判断该位置是否是前面几行已标记的点
B战 懒猫老师讲的(上一篇博文中我所用到的方法)。
算法思路:从第一行开始当某一行皇后位置不与前面所有皇后位置冲突那么记录该行皇后位置并调用递归函数进入下一行,摆放下一个皇后,逐个位置摆放,若该行所有位置都被其他皇后占领,那么就回溯到上一行重新摆放上一行皇后直至所有皇后都不冲突那么记录一次方法然后回溯寻找其他摆放方法。在同一条上对角线的数值都相同,因此我们可以利用该规律设计判断上对角线是否冲突的函数,类似的,下对角线则是由列号减去行号可得在同一条下对角线的数值相等。为了方便后面的程序中我把下对角线的数值都加7为正数(实际效果不变)
#include<stdio.h>
int place[8]={0};//皇后位置
bool flag[8]={1,1,1,1,1,1,1,1};//定义列
bool d1[15]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};/*定义上对角线(共有15个对角线,
因此定义一个长度为15的bool型数组,初值为1代表该对角线没有被皇后占领,
若被皇后占领则赋值为0*/
bool d2[15]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} ;//定义下对角线
int number=0;//记录输出次数
void print()//定义输出函数
{
int col,i,j;
number++;//每调用一次输出函数number自加一次,记录摆放方法个数
printf("No.%2d\n",number);
int table[8][8]={0};//设置一个8*8的棋盘
for (i=0;i<8;i++)
{
table[i][place[i]]=1;//将每一行皇后所在位置赋值为1
}
for (i=0;i<8;i++)
{
for (j=0;j<8;j++)
{
printf("%d|",table[i][j]);
}printf("\n");
}
}
int queen(int n )//定义递归回溯函数
{
int col;
for (col=0;col<8;col++)
{
if (flag[col]&&d1[n-col+7]&&d2[n+col])//判断皇后是否冲突
{
place[n]=col;//放置皇后
flag[col]=false;
d1[n-col+7]=false;
d2[n+col]=false;//将该皇后所在的行、列、对角线设置为被占领
if(n<7) {queen(n+1);}//当行数小于7时;递归调用下一行
else{print();}//调用输出函数
flag[col]=true;//回溯
d1[n-col+7]=true;
d2[n+col]=true;
}
}
return number;
}
int main()
{
number=queen(0);//从第0行开始执行
printf("共有%d种摆放方法",number);//输出方法的个数
return 0;}
利用递归回溯的方法来解决八皇后问题的程序步骤较少且可读性强,代码量少,不用尝试所有错误的摆放方法,遇到错误就回溯,效率较高。但递归算法占用空间更大,且每调用一次函数都要在内存栈中分配一定的空间,而栈的空间是有限的,若递归调用的层次太多就容易造成栈的溢出。
2. 非递归回溯法——判断该位置四周有无皇后
位置冲突算法:建立一个一维数组a[9](a[0]不用),a[1]~a[9]八个数分别代表棋盘的一行,其中的数值代表该行皇后所在的列数,若任意两个数的数值相等则表明在同一列(冲突),若任意两个数所在的行数差值的绝对值等于列数的差值的绝对值(在同一对角线上冲突)
#include <stdio.h>
#include <math.h>
bool Chongtu( int a[], int n) {
int i = 0 , j = 0 ;
for (i = 2 ; i <= n; ++i)
for (j = 1 ; j <= i- 1 ; ++j)
if ((a[i] == a[j]) || (abs(a[i]-a[j]) ==abs(i-j)))//1:在一列;2:在对角线上。每一行都要与前面所有行进行判断是否冲突
return false ; //冲突
return true ;} //不冲突
//八皇后问题:迭代法(回溯)
void Queens8(){
int n = 8 ; //8个皇后
int count = 0 ;//记录当前第几情况
int a[ 9 ] = { 0 }; //存放皇后位置,(第0行0列不用,便于直观看出皇后位置)
int i = 0 ,k = 1 ; //初始化k为第一行
a[ 1 ] = 0 ; //初始化a[1] = 0
while (k > 0 )
{
a[k] += 1 ; //a[k]位置,摆放一个皇后
while ((a[k] <= n) && (!Chongtu(a,k))) //如果a[k](即皇后摆放位置)没有到k行最后,且摆放冲突。
a[k] += 1 ; //将皇后向后移一位直至不冲突或a[k]>n超出范围则结束循环
if (a[k] <= n) //皇后摆放位置没有到达该行最后一个位置
{
if (k == n) //8行皇后全部摆放完毕
{
printf( "第%d种情况:\n" ,++count);
for (i = 1 ; i <= n; ++i) //打印该种摆放方法情况
{
for (int j=1;j<=8;j++)
{
if (j!=a[i])
{
printf("0|");
}
else{printf("1|");}
}
printf("\n");
}
printf("\n");
}
else //皇后还未摆放完毕
{
k += 1 ; //继续摆放下一行
a[k] = 0 ; //此行初始化a[k] = 0;表示第k行,从第一行开始摆放皇后
}
}
else //回溯:当a[k]>8进入else,表示在第k列中没有找到合适的摆放位置
k -= 1 ; //回溯到k-1步:k表示第几个皇后,a[k]表示第k个皇后摆放的位置
}
return ;
}
//主函数
int main()
{
Queens8();
return 0 ;
}
回溯法实质上是利用迭代实现递归回溯的方法,该方法运行效率高,但是代码量更大也更加复杂。