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


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

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

      方法一:

     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("
    解答%d
    ",++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("
    ");
    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("递归求法:
    ");
                      for(i=1;i<=8;i++)
        {
            a=0,b=0;  
            digui(1,i);
            printf("a=%d b=%d
    ",a,b);
        }
        printf("
    循环求法:
    ");
        for(i=1;i<=8;i++)
        {
            p=1,q=0;  
            xunhuan(i);
            printf("p=%d q=%d
    ",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("
    解答%d
    ",++num);
        for(y=1;y<=N;y++)
        {
            for(x=1;x<=N;x++)
            {
                if(queen[y]==x)
                    printf("Q ");
                else
                    printf("* ");
            }
            printf("
    ");
        }
    }
    void backtrack(int i)
    {
        /* 可通过printf("
    %3d
    ",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("
    解答%d
    ",++num);
        for(y=1;y<=N;y++)
        {
            for(x=1;x<=N;x++)
            {
                if(queen[y]==x)
                    printf("%3d",queen[y]);
            }
        }
        printf("
    ");
    }

     

    输出结果如下图:

    即为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种解法
    ", ++n);
        for (i = 0; i < 8; i++)
        {
            for (j = 0; j < 8; j++)
            {
                if(chess[i][j]==1)
                    printf("G ");
                else
                    printf("* ");
            }
            printf("
    ");
        }
        puts("
    ");
    }
    
    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为:
    ",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("
    ");
    55     }
    56     printf("
    ");
    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("
    ");
    14       }
    15   }

     

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

     

     

     

     

  • 相关阅读:
    day35—JavaScript操作元素(创建、删除)
    day34—JavaScript实现DOM操作
    day33—前端开发的模块化和组件化
    day32—CSS多列布局学习
    day31—CSS Reset 与页面居中布局
    JVM(18)之 Class文件
    JVM(17)之 准备-解析-初始化
    JVM(16)之 双亲委派模型
    JVM(15)之 类加载器
    JVM(14)之 类加载机制
  • 原文地址:https://www.cnblogs.com/xuhang/p/3308955.html
Copyright © 2020-2023  润新知