多维算法思考(二):关于八皇后问题解法的探讨

多维算法思考(二):关于八皇后问题解法的探讨

  八皇后问题是隶属于递归算法中的经典例题,正确的理解它是学习递归算法的关键所在。下面我将用三种方法来为大家讲解。

  方法一:

 1 #include<stdio.h>
 2 
 3 #define N 8
 4 int column[N+1];// 同栏是否有皇后,1表示有
 5 int rup[2*N+1];// 右上至左下是否有皇后
 6 int lup[2*N+1];// 左上至右下是否有皇后
 7 int queen[N+1]={0};
 8 int num;// 解答编号
 9 void backtrack(int);// 递回求解
10 
11 void show(int a[],int);
12 
13 int main(void)
14 {
15     int i,m,n;
16     num=0;
17     for(i=1;i<=N;i++)
18         column[i]=1;
19     for(i=1;i<=2*N;i++)
20         rup[i]=lup[i]=1;
21     backtrack(1);
22     return 0;
23 }
24 
25 void showAnswer()
26 {
27     int x,y;
28     printf("\n解答%d\n",++num);
29     for(y=1;y<=N;y++)
30     {
31         for(x=1;x<=N;x++)
32         {
33             if(queen[y]==x)
34                 printf("Q ");
35   else
36                 printf("* ");
37         }
38         printf("\n");
39     }
40 }
41 
42 void backtrack(int i)
43 {
44     int j;
45     if(i>N)
46     {
47         showAnswer();
48     }
49     else
50     {
51         for(j=1;j<=N;j++)
52         {
53             if(column[j]==1&&rup[i+j]==1&&lup[i-j+N]==1)
54             {
55                 queen[i]=j;
56                 // 设定为占用
57                 column[j]=rup[i+j]=lup[i-j+N]=0;
58                 backtrack(i+1);
59                 column[j]=rup[i+j]=lup[i-j+N]=1;
60             }
61         }
62     }
63 }

 

这种方法整体书写起来较为简单,但理解起来稍有点难度,需对递归流程有特别深刻的了解。为帮助大家学习和理解,我将其代码块核心块单独拿出来整理成例题,例题一,是为了帮助大家了解递归执行的次数。例题二,是为了帮助大家了解递归执行的顺序。

 

例题一:为了对比起见,我特意用循环也写了一个功能类似的代码,两者结果相同。

 

#include<stdio.h>
#include<stdlib.h>

void digui(int,int);
void xunhuan(int);

int a,b;    //递归
int p,q;   //循环

int main(void)
{
    int i;
    printf("递归求法:\n");
                  for(i=1;i<=8;i++)
    {
        a=0,b=0;  
        digui(1,i);
        printf("a=%d b=%d\n",a,b);
    }
    printf("\n循环求法:\n");
    for(i=1;i<=8;i++)
    {
        p=1,q=0;  
        xunhuan(i);
        printf("p=%d q=%d\n",p,q);
    }
}

void digui(int i,int m) 
{                               
       int j; 
       if(i>m)
           a++;
        else
        {
                for(j=1;j<=m;j++)
                {
                        b++;
                        digui(i+1,m);
                }
        }
}

void xuhuan(int m)
{
        int i;
        for(i=1;i<=m;i++)
        {
                p*=n;
                q+=p;
        }
}

 

测试结果:

显然,如果不加任何条件,递归执行的次数是相当庞大的,其中a达到  b达到 

i=8时,a突破了千万级别,按数学思考,在不加任何条件的情况下,每一行的8个位置均有可能有一个皇后,刚好为88次方。


例题二:(在不考虑左上,左下,右上,右下的情况下,当N=3时,执行的代码如下)

 

#include<stdio.h>

#define N 3
int column[N+1];// 同栏是否有皇后,1表示有
int queen[N+1]={0};
int num;// 解答编号
void backtrack(int);// 递回求解

void show(int a[],int);

int main(void)
{
    int i,m,n;
    num=0;
    for(i=1;i<=N;i++)
        column[i]=1;
    backtrack(1);
    return 0;
}

void showAnswer()
{
    int x,y;
    printf("\n解答%d\n",++num);
    for(y=1;y<=N;y++)
    {
        for(x=1;x<=N;x++)
        {
            if(queen[y]==x)
                printf("Q ");
            else
                printf("* ");
        }
        printf("\n");
    }
}
void backtrack(int i)
{
    /* 可通过printf("\n%3d\n",i)查看执行的顺序  */
  int j;
    if(i>N)
        showAnswer();
    else
        for(j=1;j<=N;j++)
            if(column[j]==1)
            {
                queen[i]=j;
                // 设定为占用
                column[j]=0;
                backtrack(i+1);
                column[j]=1;
            }
}

 

执行的顺序图如下:(以下backtrack简写为b

为了更直观的显示我们将showAnswer()稍加改动一下如下:

 

void showAnswer()
{
    int x,y;
    printf("\n解答%d\n",++num);
    for(y=1;y<=N;y++)
    {
        for(x=1;x<=N;x++)
        {
            if(queen[y]==x)
                printf("%3d",queen[y]);
        }
    }
    printf("\n");
}

 

输出结果如下图:

即为1,2,3的全排列,共有种解法。

 

方法二:

 

#include <stdio.h>

//检查该点有效返回1,否则返回0
int CheckPosition (int (*chess)[8], int row, int col)
{
    int IsTrue = 1;
    int i, j;
    //左上方,行数减,列数减
    for (i = row, j = col; IsTrue && i >= 0 && j >= 0; i--, j--)
    {
        if (chess[i][j] == 1) 
            IsTrue = 0;
    }
    //上方,列数不变
    for (i = row, j = col; IsTrue && i >= 0; i--)
    {
        if (chess[i][j] == 1)
            IsTrue = 0;
    }
    //右上方,行数减,列数加
    for (i = row, j = col; IsTrue && i >= 0 && j < 8; i--, j++)
    {
        if (chess[i][j] == 1)
            IsTrue = 0;
    }
    return IsTrue;
}

void DisChess (int (*chess)[8])
{
    int i, j;
    static  n = 0;
    printf("第%d种解法\r\n", ++n);
    for (i = 0; i < 8; i++)
    {
        for (j = 0; j < 8; j++)
        {
            if(chess[i][j]==1)
                printf("G ");
            else
                printf("* ");
        }
        printf("\n");
    }
    puts("\n");
}

void EightQueen (int (*chess)[8], int row)
{
    int col;

    if (row < 8)
    {
        for (col = 0; col < 8; col++)
        {
            if ((chess[row][col]=CheckPosition(chess, row, col))==1)
            {
                EightQueen(chess, row + 1);
                chess[row][col] = 0;
            }
        }
    }
    else
        DisChess(chess);        //显示
}

int main()
{   
    int chess[8][8] = {0};
    EightQueen(chess, 0);
    return 0;
}

 

相比于方法一,方法二更容易理解也更加简单,我们只需把约束条件放在CheckPosition(chess, row, col))函数中,通过循环来实现。但与此同时代码段会相应增加一些。方法一和方法二的主要区别在于数组的选择上。方法一,用的是一维数组,把合乎要求的存放于queen[]数组中,然后输出。方法二,用的是二维数组,首先把数组所有元素均置为0,然后通过约束条件把合乎要求的置为1,然后显示输出。

为了继承方法二的简单和容易理解等优点,同时又缩短代码的长度,方法三对其进行了部分优化代码如下:

方法三:

 1 #include<stdio.h>
 2 #include<math.h>
 3 
 4 int QueenCount = 8;//皇后数目。
 5 int QueenPositions[8];//每行放置皇后的位置。
 6 int total = 0;//放置方案的数目。
 7 void DisChess(int row);
 8 void Print();
 9 
10 void main()
11 {
12      DisChess(0);
13 }
14 
15 void DisChess(int row)//在第row行上放置一个皇后。
16 {
17      if (row >= QueenCount)
18      {
19          total++;//此时找到了一种放置方案。
20          Print();
21      }
22      for (int col = 0; col < QueenCount; col++)//在第row行上尝试每一个位置。
23      {
24           bool attack = false;
25           for (int iRow = 0; iRow < row; iRow++)
26           {
27         if (QueenPositions[iRow] == col ||abs(iRow - row) == abs(QueenPositions[iRow] - col))
28                {
29                     attack = true;
30                     break;
31                }
32           }
33           if (!attack)
34           {
35                QueenPositions[row] = col;
36                DisChess(row + 1);
37           }
38      }
39 }
40 
41 void Print()
42 {
43     int i,j;
44     printf("解法%d为:\n",total);
45     for(i=0;i<8;i++)
46     {
47         for(j=0;j<8;j++)
48         {
49             if(QueenPositions[i]==j)
50                 printf("Q ");
51             else
52                 printf("* ");
53         }
54         printf("\n");
55     }
56     printf("\n");
57 }

  介绍了八皇后的所有解法后,那么如何去查看某一种解法呢?这里我们以方法一为例。

首先我们用数组a[1000]保存每一种解法皇后的位置。我们知道方法一中ij均由18,这样我们可以用一个十位数来同时记录皇后的坐标值a[k++]=i*10+j。显示解法函数如下:

 

 1 void show(int a[],int n)
 2   {
 3       int i,j;
 4       for(i=0;i<N;i++)
 5       {
 6           for(j=0;j<N;j++)
 7           {
 8               if((i+1==a[n+i]/10)&&(j+1==a[n+i]%10))
 9                   printf("Q ");
10               else
11                   printf("* ");
12           }
13           printf("\n");
14       }
15   }

 

其中n为需要显示的解法编号在数组a中的位置(即需要显示的解法编号减去1后在乘以8

 

 

 

 

 

 

 

posted @ 2013-09-08 22:42  徐航  阅读(470)  评论(0编辑  收藏  举报