• N皇后问题(分支限界法)


    问题描述:

    在 n * n 格的棋盘上放置彼此不受攻击的 n 个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题等价于在 n * n 的棋盘上放置 n 个皇后,任何 2个皇后不放在同一行或同一列或同一斜线上。

    设计一个解 n 皇后问题的队列式分支限界法,计算在 n * n 个方格上放置彼此不受攻击的 n 个皇后的一个放置方案。

    数据输入:

    第一行有1个正整数 n 。

    结果输出:

    第一行是n个皇后的放置方案。

    思路:

    1.题目分析:

    对于每一个放置点而言,它需要考虑四个方向上是否已经存在皇后。分别是行、列,45度斜线和135度斜线。

    行:每一行只放一个皇后,直到我们把最后一个皇后放到最后一行的合适位置,则算法结束。

    列:列相同的约束条件,只需判断 j 是否相等即可。

    45度斜线和135度斜线:约束条件——当前棋子和已放置好的棋子不能存在行数差的绝对值等于列数差的绝对值的情况,若存在则说明两个棋子在同一条斜线上

    2.算法选择:

    用分支限界法来解决 n 皇后问题。对于该问题它满足两种树(子集树、排列树)结构。这里由于每一个合适的放置点出现最优解的概率是相等的,因此不需要使用优先队列。stl 提供了一个已经封装好的队列queue。

    A.子集树。我们把行约束条件和其它约束条件放在一起,即认为每一行,棋子都有n个位置可以放置。于是它的空间结构如下:

     

    (假设n = 3,这个图只画出了两层)

    B.排列树。我们把行约束条件单独拿出来,也就是我们认为总共有八个皇后,这八个皇后必须都要放上去,但是放的位置不同,显然这是一个排列树。于是它的空间结构如下:

     

    3.算法设计

    1).数据存储

    对于每一个棋子节点,它需要储存两个信息,一个是该棋子所处的层数,另一个就是这个棋子它是由哪些棋子扩展而来的,也就是它的路径。

    1. //定义一个节点类
    2. struct Node{
    3. int number;//棋子所处的层数,也就是棋盘上的i
    4. vector<int>x;//保存该棋子以及在它之前的棋子所在的列数j。也就是它的所有父辈节点的信息
    5. };

     比如图中的节点 m ,它储存的信息就是number = 3,x[1]=  1;x[2] = 2;x[3] = 3。指明了前面棋子的列信息。

    2).定义一个Queen的类,封装一些相关的信息,比如皇后个数和最优解的数组

    1. class Queen{
    2. friend int nQueen(int);
    3. public:
    4. bool Place(Node q,int n);
    5. void Research();
    6. int n;//皇后个数
    7. int *bestx;//最优解
    8. };

    4.细节处理

    1).当前所处层数的判断:每一层的结尾都压入一个 number =  -1 的节点,每一次在取出队列中的节点时进行判断如果该节点的 number = -1,那么当前层数 t 加 1,并且再压入一个 number = -1的节点。然后重新往队列取出下一个节点。

    2).算法终止条件:如果当前取出的节点 number == n,表明已经处理到最后一层了,并且最后一层满足约束条件的棋子位置已经都存在队列中了。这时我们把当前节点的数组 x 赋值给 bestx ,然后结束算法。

    Code:

    1. #include
    2. using namespace std;
    3. //定义一个节点类
    4. struct Node{
    5. int number;
    6. vector<int>x;//保存当前解
    7. };
    8. //定义一个Queen的类
    9. class Queen{
    10. friend int nQueen(int);
    11. public:
    12. bool Place(Node q,int n);
    13. void Research();
    14. int n;//皇后个数
    15. int *bestx;//最优解
    16. };
    17. //判断是否能够放置的函数
    18. bool Queen::Place(Node q,int n){
    19. for(int j = 1;j < n;j ++ )
    20. if((abs(n-j) == abs(q.x[j] - q.x[n])) || (q.x[j] == q.x[n]))
    21. return false;
    22. return true;
    23. }
    24. void Queen::Research(){
    25. queueQ;//活节点队列
    26. Node sign;
    27. sign.number = -1;
    28. Q.push(sign);//同层节点尾部标志
    29. int t = 1;//当前节点所处的层
    30. Node Ew;//当前扩展节点
    31. Ew.number = 0;
    32. //搜索子集空间树
    33. while(1){
    34. //检查所有的孩子节点
    35. for(int k = 1;k <= n;k ++ ){
    36. //把当前扩展节点的值赋给下一个节点
    37. Node q;
    38. q.number = t;
    39. q.x.push_back(0);//第一个位置为0
    40. for(int i = 1;i < t;i ++ ) q.x.push_back(Ew.x[i]);
    41. q.x.push_back(k);
    42. if(Place(q,t))
    43. Q.push(q);
    44. }
    45. //取下一扩展节点,取出,赋值给Ew
    46. Ew = Q.front();
    47. Q.pop();
    48. if(Ew.number == -1){
    49. //同层节点尾部标记
    50. t ++ ;//进入下一层
    51. Q.push(sign);//增加标记
    52. //继续往下去下一个节点
    53. Ew = Q.front();
    54. Q.pop();
    55. }
    56. if(Ew.number == n){//找到最后一层的节点
    57. for(int i = 0;i <= n;i ++ ) bestx[i] = Ew.x[i];
    58. break;
    59. }
    60. }
    61. }
    62. int nQueen(int n){
    63. Queen X;
    64. X.n = n;
    65. X.bestx = new int[n+1];
    66. for(int i = 0;i <= n;i ++ ) X.bestx[i] = 0;
    67. X.Research();
    68. for(int i = 1;i <= n;i ++ ) cout<" ";
    69. }
    70. int main(){
    71. int N;
    72. cin>>N;
    73. nQueen(N);
    74. return 0;
    75. }

    代码运行截图:

     

     


     

  • 相关阅读:
    [附源码]java毕业设计农村留守儿童帮扶系统
    第二十四章《学生信息管理系统》第2节:系统功能实现
    如何用 Prometheus Operator 监控 K8s 集群外服务?
    全面总结 Vue 3.0 的新特性!手把手教你如何入门Vue3.0(适合小白的保姆级教程)【尚硅谷vuejs3.0笔记】
    STM32CubeMX学习笔记(47)——USB接口使用(MSC基于内部Flash模拟U盘)
    【开源项目】Branchless UTF-8 Decoder 无分支的 UTF-8 解码器
    STL—string
    ubuntu16.4 anaconda安装TensorRT
    【CSS布局】—— flex(弹性)布局
    labelme标注格式的数据集转COCO格式脚本
  • 原文地址:https://blog.csdn.net/m0_65508678/article/details/128179223