当前位置:   article > 正文

(新手向)N皇后问题详解(DFS算法)

n皇后问题

非常经典的一道题:

N皇后问题: 
国际象棋中皇后的势力范围覆盖其所在的行、列以及两条对角线,现在考察如下问题:如何在n x n的棋盘上放置n个皇后,使得她们彼此互不攻击 。

免去麻烦我们这里假定n不是很大。。

(图片来自百度百科(这是8皇后问题的一种解法))

某leetcode大犇曾说过:“这个问题和解数独题目有一个很大的共同点,那就是:我都不会。”

好了下面开始分析:(废话警告)

初步判断这问题的特点有:

1.有个场地来放置单位。

2.各个单位之间有制约。

3.没有特殊的数学方法,得把某一个摆法摆出来才能判断是否可行。

于是萌新一般都会这么想:对于1,:我搞个二维数组来存。对于2:我搞个判定函数来一个一个判定。对于3:我暴力枚举。

那么算法框架大概就是:我对二维数组的所有情况进行枚举,然后对每种情况进行判定

over ,输个数字n,按下回车,双手离开键盘,等了老半天发现命令提示符只有个光标在跳动 |

如果是在做网题,说不定就判定是超时或者内存溢出,过不了。

然后就开始思考怎么优化:

对于1,我可以缩减吗····,改成一维数组,,,似乎徒增麻烦。

对于2,我可以有更巧妙的判断方法吗?·············有个鬼,还不得把每个棋子的每一行每一列每两个斜线都瞅一下。

对于3,我一定要把所有情况都举出来嘛?···(于是脑子里面开始摆起了棋子,模拟算法过程)

然后就会发现  比如第一行前两个格子一开始就摆了两个相邻的棋子,诶这不明摆着皇后互怼了吗!后面还继续枚举就太傻了吧···

这种傻事情做了不是白白增加耗时吗!

所以就思考如何在这种“傻情况”出现的时候就pass掉后面所有情况。。。

于是脑子陷入了一团乱麻。。。

打住!

咱们换个思考方式,既然直接想有点困难,不如我们想办法先做点处理,让这些玩意儿便于我们把弄。

再回头想想规则,皇后可以吃掉所处的一行一列以及斜线上的棋子,那也就是说,每一行都最多只能有一个棋子,每一列都最多只能有一个棋子,

那我们不妨把每一行看成一组!

那么这一组就只有n个可能性(一行n个位置 每一次只有一个位置被占用)

也就是单个一行有n种可能性

因为有n行

哈哈那么就瞬间只需要讨论n*n种可能性了!

 还记得我们之前的思考吗?:

  1. 对于3,我一定要把所有情况都举出来嘛?···(于是脑子里面开始摆起了棋子,模拟算法过程)
  2. 然后就会发现 比如第一行前两个格子一开始就摆了两个相邻的棋子,诶这不明摆着皇后互怼了吗!后面还继续枚举就太傻了吧···
  3.    这种傻事情做了不是白白增加耗时吗!
  4.    所以就思考如何在这种“傻情况”出现的时候就pass掉后面所有情况。。。

像这种情况:

(我的天这皇后画的...)

显然下面3,4,5····行都不用枚举了,直接pass掉

于是我们思路慢慢清晰了起来:

我们从第一行开始枚举,第一行第一格:

然后枚举第二行,也从第一格开始:

 判定一下,哇,不行,咋办呢?这情况下面所有行都没有枚举的必要了,但是本行还是可以继续的。。。于是我们开始枚举第二行第二个情况:

不行(斜线上互吃)。

继续第二行下一个情况:

行嘞!

那么我们就可以开第三行了:

不行(一列上吃)。

不行(斜线上吃)

 不行。

 还不行

 行嘞!

··················

好了,算法思路大概就出来了。

 我们就这样干下去,直到最后一行可行,那么我们就获得了一个可行解了,

然后我们想继续获得其他解,那么继续枚举下去,但是要退一步:

先从倒数第一行开始,我们枚举下一个,有解则输出,无解则继续。

倒数第一行结束了,咋办?

别忘了倒数第二行的情况还不一定枚举完了呢。

于是退到倒数第二行,继续如上操作。

··················


 干说着没用,撸代码:

  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. return 0;
  6. }

先准备好需要用的存储(这里用64个绰绰有余)

ans是用来存每一次的解 ans[1]表示第一行的那个元素的所在列的位置

  1. #include<iostream>
  2. using namespace std;
  3. bool colMark[64] = { 0 };//元素所在列 如果有了元素在第k列 那么colMark[k]就标记上true
  4. bool naMark[64] = { 0 };//捺,撇···顾名思义,表示元素所在的两个斜线o.o 原理同上
  5. bool pieMark[64] = { 0 };
  6. int total = 0; //解的总个数
  7. int N = 0;
  8. int ans[64] = { 0 };
  9. int main()
  10. {
  11. return 0;
  12. }

然后下面先写一个显示函数: 

  1. void showOneSolution()//用来显示一个解
  2. {
  3. total++;
  4. for (int i = 1; i<=N; ++i)
  5. {
  6. cout << ans[i] << " ";
  7. }
  8. cout << endl;
  9. }

下面是核心:

  1. void Dfs(int i) //可以百度学习 深度优先搜索
  2. {
  3. if (i > N) //判断是否已经枚举完了N行
  4. {
  5. showOneSolution(); //枚举完了就输出(此刻我们处于N+1行)
  6. return; //返回到第N行
  7. }
  8. for (int j = 1; j <= N; ++j) //这里的j表示 本行的第j列
  9. {
  10. if ((!colMark[j])&&(!naMark[j-i+N])&&(!pieMark[i+j])) //这里的"j-i+N" "i+j"建议自己体会
  11. {          //判断这个格子所在的两个斜线和所在列是否为空
  12. colMark[j] = true;
  13. naMark[j - i + N] = true;
  14. pieMark[i + j] = true;
  15. ans[i] = j;
  16. Dfs(i + 1);
  17. colMark[j] = false; //这个格子枚举完了要进入本行第j+1个格子(或者结束本行) 走之前别忘了恢复标记
  18. naMark[j - i + N] = false;
  19. pieMark[i + j] = false;
  20. }
  21. }
  22. }

 最后组装起来:(当然啦,还是建议自己撸代码,你会发现很多意想不到的问题~解决问题的同时也是进步)

  1. #include<iostream>
  2. using namespace std;
  3. bool colMark[64] = { 0 };
  4. bool naMark[64] = { 0 };
  5. bool pieMark[64] = { 0 };
  6. int total = 0;
  7. int N = 0;
  8. int ans[64] = { 0 };
  9. void showOneSolution()//用来显示一个解
  10. {
  11. total++;
  12. for (int i = 1; i<=N; ++i)
  13. {
  14. cout << ans[i] << " ";
  15. }
  16. cout << endl;
  17. }
  18. void Dfs(int i)
  19. {
  20. if (i > N) //判断是否已经枚举完了N行
  21. {
  22. showOneSolution(); //枚举完了就输出(此刻我们处于N+1行)
  23. return; //返回到第N行
  24. }
  25. for (int j = 1; j <= N; ++j) //这里的j表示 本行的第j列
  26. {
  27. if ((!colMark[j]) && (!naMark[j - i + N]) && (!pieMark[i + j]))
  28. {          //判断这个格子所在的两个斜线和所在列是否为空
  29. colMark[j] = true;
  30. naMark[j - i + N] = true;
  31. pieMark[i + j] = true;
  32. ans[i] = j;
  33. Dfs(i + 1);
  34. colMark[j] = false; // 走之前别忘了恢复标记
  35. naMark[j - i + N] = false;
  36. pieMark[i + j] = false;
  37. }
  38. }
  39. }
  40. int main()
  41. {
  42. cin >> N;
  43. Dfs(1);
  44. cout << total;
  45. system("pause");
  46. return 0;
  47. }

总结:

思路混乱不妨换个角度理一理。

在纸上面画一画算法过程会有不少帮助。

代码少出错还是得多撸。

想什么呢?!撸代码!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/825449
推荐阅读
相关标签
  

闽ICP备14008679号