八皇后问题

八皇后问题,是一个古老而著名的问题。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。

思路

对于八皇后的求解可采用回溯算法,从上至下依次在每一行放置皇后,进行搜索,若在某一行的任意一列放置皇后均不能满足要求,则不再向下搜索,而进行回溯,回溯至有其他列可放置皇后的一行,再向下搜索,直到搜索至最后一行,找到可行解,输出。

可以使用递归函数实现上述回溯算法。

然而网上给出的代码,太过于粗糙,我用自己的大白话给大家解释一下。

首先呢,该选取什么样是数据结构?

最直接的方式是选用二维数组,用一个二维数组表示一张棋盘,简单而直观,下面看一下二维数组递归求解的代码,我会给出详细飞注解。

1.    #include <stdlib.h>
2.    #include <stdio.h>
3.   
4.    int m[8][8] = {0};//表示棋盘,初始为0,表示未放置皇后,1表示该位置放置了一个皇后
5.    int num = 0;//解数目
6.   
7.    //对于棋盘前row-1行已放置好皇后
8.    //检查在第row行、第column列放置一枚皇后是否可行

//直观上,我们该怎样检查,在某一个位置放置一个旗子是否正确?

//由于我们是默认的递归方式,已经确保了每一行中只能放置一个旗子(只能有一个1,不是吗?仔细想想)

//那在仔细想想,我们其他的检查条件是什么?,是不是,检查每一列,斜向上(两种方式)是否只有一个1?

//好,看一下代码中如何体现这种思想?
9.    bool check(int row,int column)
10.   {
11.       if(row==1) return true;
12.       int i,j;
13.       //纵向只能有一枚皇后
14.       for(i=0;i<=row-2;i++)
15.       {
16.           if(m[i][column-1]==1) return false;
17.       }
18.       //左上至右下只能有一枚皇后

//row-column是什么意思?右斜向下,row,和column之间有什么关系?发现规律了吗?

19.       i = row-2;
20.       j = i-(row-column);
21.       while(i>=0&&j>=0)
22.       {
23.           if(m[i][j]==1) return false;
24.           i--;
25.           j--;
26.       }
27.       //右上至左下只能有一枚皇后

//右上至左下,他们的row和column之间有什么规律?发现规律了吗
28.       i = row-2;
29.       j = row+column-i-2;
30.       while(i>=0&&j<=7)
31.       {
32.           if(m[i][j]==1) return false;
33.           i--;
34.           j++;
35.       }
36.       return true;
37.   }
38.  
39.   //当已放置8枚皇后,为可行解时,输出棋盘

//两行for循环,解决问题
40.   void output()
41.   {
42.       int i,j;
43.       num++;
44.       printf("answer %d:\n",num);
45.       for(i=0;i<8;i++)
46.       {
47.           for(j=0;j<8;j++) printf("%d ",m[i][j]);
48.           printf("\n");
49.       }
50.   }
51.  
52.   //采用递归函数实现八皇后回溯算法
53.   //该函数求解当棋盘前row-1行已放置好皇后,在第row行放置皇后
54.   void solve(int row)
55.   {
56.       int j;
57.       //考虑在第row行的各列放置皇后
58.       for (j=0;j<8;j++)
59.       {
60.           //在其中一列放置皇后
61.           m[row-1][j] = 1;
62.           //检查在该列放置皇后是否可行
63.           if (check(row,j+1)==true)
64.           {
65.               //若该列可放置皇后,且该列为最后一列,则找到一可行解,输出
66.               if(row==8) output();
67.               //若该列可放置皇后,则向下一行,继续搜索、求解
68.               else solve(row+1);
69.           }
70.           //取出该列的皇后,进行回溯,在其他列放置皇后

//这一行是回溯,仔细想想
71.           m[row-1][j] = 0;
72.       }
73.   }
74.  
75.   //主函数
76.   int main()
77.   {
78.       //求解八皇后问题
79.       solve(1);
80.       return 0;
81.   }

代码看到这里,相信大部分人都已经看明白了。

然而,网上还有更简单的版本,能不能用一个以为数组表示棋盘呢?答案是肯定的

来,那看一下,更简洁的版本

  a)定义全局堆栈和打印函数

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static int gEightQueen[8] = {0};  
  2. //这个数组是什么意思?加入一个棋盘中,每一个棋子的位置可以用row和column表示
  3. //row可以用递归的深度表示,column自然可以用一个数组表示,column=gEightQueen[row],
  4. //在下面代码中,row=index;
  1. static int gCount = 0;  
  2. //gCount表示找到个数。
  3.   
  4. void print()  
  5. {  
  6.     int outer;  
  7.     int inner;  
  8.   
  9.     for(outer = 0; outer <8; outer ++){  
  10.         for(inner = 0; inner < gEightQueen[outer]; inner ++)  
  11.             printf("* ");  
  12.   
  13.         printf("# ");  
  14.   
  15.         for(inner = gEightQueen[outer] + 1; inner < 8; inner ++)  
  16.             printf("* ");  
  17.   
  18.         printf("\n");  
  19.     }  
  20.   
  21.     printf("=====================================\n");  
  22. }  
     b)添加位置合法性的函数判断

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int check_pos_valid(int loop, int value)  
  2. {  
  3.     int index;  
  4.     int data;  
  5.   
  6.     for(index = 0; index < loop; index ++){  
  7.         data = gEightQueen[index];  
  8.   
  9. //有了上面的铺垫,if的判断条件,是什么意思呢?检查每一列是否只有一个1,不是吗?
  10.         if(value == data)  
  11.             return 0;  
  12.   //是不是和二维数组判断条件,有些相似?
  13.         if((index + data) == (loop + value))  
  14.             return 0;  
  15.   //这个相信都能看懂
  16.         if((index - data) == (loop - value))  
  17.             return 0;  
  18.     }  
  19.   
  20.     return 1;  
  21. }  
     c) 八皇后遍历

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void eight_queen(int index)  
  2. {  
  3.     int loop;  
  4.   
  5.     for(loop = 0; loop < 8; loop++){  
  6.         if(check_pos_valid(index, loop)){  
  7.             gEightQueen[index] = loop;  
  8.   
  9.             if(7 == index){  
  10.                 gCount ++, print();  
  11.                 gEightQueen[index] = 0;  
  12.                 return;  
  13.             }  
  14.               
  15.             eight_queen(index + 1);  
  16.             gEightQueen[index] = 0;  
  17.         }  
  18.     }  
  19. }  

总结:

    (1)迭代递归是编程的难点,需要自己好好实践,看别人写一百遍,不如自己写一遍

    (2)递归的时候务必注意函数return的出口

    (3)递归函数中语句的顺序不要随意更换

    (4)递归函数中注意数据的保存和恢复

    (5)递归函数也要验证,可以用程序验证法,也可以用其他函数的结果来验证


ps:

    下面是完整的代码,大家可以直接保存成queue.cpp,直接编译运行即可。可以打印出所有92种情况,

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. static int gEightQueen[8] = {0};  
  5. static int gCount = 0;  
  6.   
  7.   
  8. void print()  
  9. {  
  10.     int outer;  
  11.     int inner;  
  12.   
  13.     for(outer = 0; outer <8; outer ++){  
  14.         for(inner = 0; inner < gEightQueen[outer]; inner ++)  
  15.             printf("* ");  
  16.   
  17.         printf("# ");  
  18.   
  19.         for(inner = gEightQueen[outer] + 1; inner < 8; inner ++)  
  20.             printf("* ");  
  21.   
  22.         printf("\n");  
  23.     }  
  24.   
  25.     printf("=====================================\n");  
  26. }  
  27.   
  28. int check_pos_valid(int loop, int value)  
  29. {  
  30.     int index;  
  31.     int data;  
  32.   
  33.     for(index = 0; index < loop; index ++){  
  34.         data = gEightQueen[index];  
  35.   
  36.         if(value == data)  
  37.             return 0;  
  38.   
  39.         if((index + data) == (loop + value))  
  40.             return 0;  
  41.   
  42.         if((index - data) == (loop - value))  
  43.             return 0;  
  44.     }  
  45.   
  46.     return 1;  
  47. }  
  48.   
  49.   
  50.   
  51. void eight_queen(int index)  
  52. {  
  53.     int loop;  
  54.   
  55.     for(loop = 0; loop < 8; loop++){  
  56.         if(check_pos_valid(index, loop)){  
  57.             gEightQueen[index] = loop;  
  58.   
  59.             if(7 == index){  
  60.                 gCount ++, print();  
  61.                 gEightQueen[index] = 0;  
  62.                 return;  
  63.             }  
  64.               
  65.             eight_queen(index + 1);  
  66.             gEightQueen[index] = 0;  
  67.         }  
  68.     }  
  69. }  
  70.   
  71.   
  72.   
  73. int main(int argc, char* argv[])  
  74. {  
  75.     eight_queen(0);  
  76.     printf("total = %d\n", gCount);  
  77.     return 1;  
  78. }  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值