八皇后详解

来源:互联网 发布:c语言布尔值 编辑:程序博客网 时间:2024/04/18 23:18

 八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

    解这题需要用到回溯法,先简单介绍递归和回溯。回溯与递归很像。它们的本质区别在于递归每次都能成功,沿着递归函数进去肯定能进入极值。举个求和的简单例子:

    //求1至n的和

    int sum(int n){

 if(1==n)

      return 1;

 else

      return n+sum(n-1);

    }

    所有数的和等于n加上前(n-1)个数的和,而前(n-1)个数又等于n-1加上前(n-2)个数的和,如此下去,前2个数的和等于2加上前1个数之和,前1个数的和就是1,程序运行至此就一直返回。

    在这个递归的例子中,每进入一个新函数,返回值最终都能成功。也就是说沿着函数一直进去肯定能找到n==1的情况。

    虽然回溯要想解决问题还得借助递归思想,但是回溯进入新的函数却不一定能找到答案,比如要从这张二叉树图片

中的根节点(0号)开始找到一个存放在11号叶结点的东西(之前你只知道东西放在某个叶结点而已),在没有任何帮助条件下,为了找到目标,你得挨个找,假设从左往右找。0号->1号->3号->7号,进到7号后你发现没有,这时你必须得返回至3号,然后往右移,进入8号,也没有,返回至3号,然后发现7号和8号找过了,于是从3号返回至1号,接着1号->4号->9号……就这么进去找,找不到就返回前一个再向右移,最后会进入到11号找到东西。

    这就是回溯法的思想,找不到就返回前一个,接着找,直到找到为止。

    八皇后的思路大致是这样的,挨行或者挨列放置皇后(这里先使用挨列),使其都不冲突。

    先在第一列放置皇后,记录放置的行号(行号从小到大挨个试,下同),再放置第二列(前提不与前面的皇后冲突),同样也记录行号,然后放置第三、第四列(前提也是不与前面的皇后冲突),这里注意一下,正在尝试第N列皇后的放置时说明前(N-1)列皇后放置是不冲突的。当发现某列无法放置皇后时,就返回前一列,行号加一(就跟上面二叉树例子一样,返回一步后,向右一个试探,这里按行号从小到大试探),继续试探。如果放置好了第八列的皇后就得到了一种放置的方法。接着行号加一或者返回前一列,继续试探,直到把所有的情况都试完。

现在把代码贴上来:注释足够多,讲得应该很清楚了。

//#include <iostream.h>

#include <stdio.h>

#define N 8

int site[N];//保存各列皇后放置的行数,下标表示列号(0-7),site[i]保存放置行号(1-8)

int iCount=0;//表示可行解的数目

void Queen(int n);//执行并放置第n列皇后的回溯函数,参数n表示第n列(从0开始)

bool isValid(int n);//判断第n列放置的皇后是否与前面的冲突,冲突返回false,有效返回true

void outPut();//找到一个可行解后,把可行解输出来

int main(){

 //从第0列开始放置皇后

 Queen(0);

 //任意输入结束程序

 //getchar();

 return 0;

}

void Queen(int n){

 int i;

 //N==n时,表示前面的8列(列号为0-7)都已经放好,把一组解输出

 if (N==n)

 {

  outPut();

  return;

 }

 //i表示行号,第n列皇后将放置的行号

 for (i=1;i<=N;i++)

 {

  site[n]=i;//这条语句保证了每一列皇后的放置都会从第1行试探到第N行

  //如果放置第n列成功,则继续尝试第n+1列

  if (isValid(n))

   Queen(n+1);

 }

}

bool isValid(int n){

 int j;

 for (j=0;j<n;j++)

 {

  if (site[n]==site[j]||(site[n]-site[j])==(n-j)||(site[n]-site[j])==(j-n))

   return false;

  //site[n]==site[j]表示放置的行号相同,冲突

  //因为是逐列操作,不存在放置的列号相同

  //site[n]-site[j]==n-j,斜率为1,表示在斜率为1的对角线上

  //site[n]-site[j]==j-n,斜率为-1,表示在斜率为-1的对角线上

 }

 return true;

}

void outPut(){

 int j;

 iCount++;

 printf("%-4d",iCount);

 for (j=0;j<N;j++)

 {

  printf("%3d",site[j]);

 }

 printf("/n");

}

上面的代码没有设置冲突状态,都是要判断时才临时算的,所以退回去时不需要更改原来的状态。相信大家也看过下面保存冲突与否的状态的代码。
//这代码是逐行放置皇后的,
#include <stdio.h>
bool col[8]={false};//竖直线(列线),用于记录列线冲突状态
bool Left[15]={false};//左对角线,用于记录左对角线冲突状态,拿笔画画就知道有15条左对角线了
bool Right[15]={false};//右对角线,用于记录右对角线冲突,同样也有15条右对角线
int site[8]={0};//保存每行放置皇后的列号
int sum=0;//记录可行解数
int row=0;//行号,本代码是逐行放置的,这里的row是可以当做Queen函数的参数的。
void Queen();//如果row作为Queen的参数,这条语句变成void Queen(int row);
int main(){
 Queen();//如果row作为Queen的参数,这条语句变成Queen(0);
 return 0;
}
//这里给出Queen函数的两种写法,趋向于第二种写法,因为把极值单独列出来很直观,思路容易跟上
void Queen(){//如果row作为Queen的参数,这条语句变成void Queen(int row){
 /*函数写法一
 //对于每行,都从第0列试到第7列,l表示列号
 for (int l=0;l<8;l++)
 {
  //col[l]判断列号是否相同我想应该很容易就理解,判断对角线的两个数组可能费劲点
  //Left[row+l],row表示行号,l表示列号,这样理解,从左上角开始,下移到行号的地方,然后
  //再右移到列号的地方,那个点所在的对角线就记录在Left[row+l]里
  //Right[row-l+7],理解成Right[row+7-l],从左上角开始,下移至行号的地方,然后移到最右,
  //接着左移列号个单位,那个点所在的对角线就记录在Right[row+7-l]了
  if (!col[l]&&!Left[row+l]&&!Right[row-l+7])
  {
   //判断这个点可以放置后,就记录列号,并修改状态
   site[row]=l;
   col[l]=true;
   Left[row+l]=true;
   Right[row-l+7]=true;
   //然后行号加1;
   row++;//如果row作为Queen的参数,这条语句不要
   //row==8时,说明前8行都放好了(行号从0开始)
   if (row==8)
   {
    //可行解加一
    sum++;
    printf("%-4d",sum);
    for (int i=0;i<8;i++)
     printf("%4d",site[i]);
    printf("/n");
   }else
    //如果row作为Queen的参数,这条语句变成Queen(row+1);
    Queen();//row<8时,接着放置下一行的皇后,
   //如果失败,则返回到前一行,返回后要立马恢复原来的状态。
   row--;//如果row作为Queen的参数,这条语句不要
   col[l]=false;
   Left[row+l]=false;
   Right[row-l+7]=false;
  }
 }
 *///函数写法一
 //函数写法二
 if (row==8)
 {
  sum++;
  printf("%-4d",sum);
  for (int i=0;i<8;i++)
   printf("%4d",site[i]+1);
  printf("/n");
  return;
 }
 int l;
 for (l=0;l<8;l++)
 {
  //site[row]=l写在这里也可以
  if (!col[l]&&!Left[row+l]&&!Right[row-l+7])
  {
   site[row]=l;
   col[l]=true;
   Left[row+l]=true;
   Right[row-l+7]=true;
   row++;
   Queen();
   //一返回上一级就得立马恢复原来的状态
   row--;
   col[l]=false;
   Left[row+l]=false;
   Right[row-l+7]=false;
  }
 }//写法二
}
好了,到这里相信大家对八皇后都很熟悉了。对回溯也有更深的了解了,不过还得多做些题,递归和回溯好用但不好理解。
稍微改改就可以得到任意皇后的排列代码了。
结果是:
皇后数 解个数
1 1
2 0
3 3
4 2
5 10
6 4
7 40
8 92
9 352
10 724
11 2680
12 14200


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhong36060123/archive/2009/07/16/4354570.aspx