0.问题引入

N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击),问有多少种摆法。

题目链接:https://www.luogu.org/problemnew/show/P1219

1、普通回溯

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

算法思想:

1. 在第k(1≤k≤N)行选择一个位置,判断这个位置是否可以摆,可以摆就进入第 k+1 行,不可以就试下一个位置;

2. 如果一直试到本行最后一个都不行,说明前面k-1行有位置选得不恰当,回到第 k-1 行,试 k-1 行的下一个位置。

3. 反复执行1,2,到最后一行摆上棋子时,说明找到了一个解。

一个问题能用回溯法求解,它的解具有$N$元组的形式,我们使用用$N$元组$(x_1,x_2,...,x_n)$表示问题的解,其中$x_i$表示第$i$行的皇后所处的列号。

核心代码:

  1. //row,col表示当前尝试摆放皇后的行号与列好
  2. bool check(int row, int col) {
  3. for (int i = ; i < row; i++) {
  4. if (x[i] == col)//列冲突
  5. return false;
  6. if (abs(row - i) == abs(col - x[i]))//对角线冲突
  7. return false;
  8. }
  9. return true;
  10. }
  11. void DFS(int k) {
  12. if (k == N + ) {
  13. //获得了一个解
  14. cnt++;
  15. return;
  16. }
  17. for (int i = ; i <= N; i++) {
  18. if (check(k, i)) {
  19. x[k] = i;//标注第k行上第i个位置摆上了皇后
  20. DFS(k + );
  21. }
  22. }
  23. }

1.1 递归实现:

N=11,12的时就顶不住了,嗝屁了。

  1. #include <iostream>
  2. #include <math.h>
  3. using namespace std;
  4.  
  5. int x[];
  6. int N, cnt;
  7.  
  8. bool check(int row, int col) {
  9. //回溯,不会受到后面行的影响
  10. for (int i = ; i < row; i++) {
  11. if (x[i] == col)return false;
  12. if (abs(row - i) == abs(col - x[i]))return false;
  13. }
  14. return true;
  15. }
  16. void DFS(int k) {
  17. if (k == N + ) {
  18. cnt++;
  19. if (cnt <= ) {
  20. for (int i = ; i <= N; i++) {
  21. cout << x[i] << " ";
  22. }
  23. cout << endl;
  24. }
  25. return;
  26. }
  27. for (int i = ; i <= N; i++) {
  28. if (check(k, i)) {
  29. x[k] = i;
  30. DFS(k + );
  31. }
  32. }
  33. }
  34.  
  35. int main() {
  36. cin >> N;
  37. DFS();
  38. cout << cnt << endl;
  39. return ;
  40. }

1.2 非递归实现:

算法优化一般不从这里考虑,因为非递归虽然是会快一点,但也只是那么一点而已,数据量小几乎没有区别,两个都跑不过去。

  1. #include <iostream>
  2. #include <math.h>
  3. using namespace std;
  4.  
  5. int x[];
  6. int N, cnt;
  7.  
  8. bool check(int row, int col) {
  9. //回溯,不会受到后面行的影响
  10. for (int i = ; i < row; i++) {
  11. if (x[i] == col)return false;
  12. if (abs(row - i) == abs(col - x[i]))return false;
  13. }
  14. return true;
  15. }
  16.  
  17. void queen(){
  18. //i表示第几册,j表示在第i层搜索位置
  19. int i = , j = ;
  20. while (i <= N){
  21. while (j <= N){
  22. //如果当前位置合法
  23. if (check(i, j)) {
  24. //把x[i]暂时赋值成j
  25. x[i] = j;
  26. j = ;
  27. break;
  28. }
  29. else
  30. j++;
  31. }
  32. //第i行没有找到可以放置皇后的位置
  33. if (x[i] == ){
  34. //如果回溯到了第0行,说明完成了
  35. if (i == )
  36. break;
  37. //回溯
  38. else{
  39. i--;
  40. j = x[i] + ;//j为上一行的皇后位置+1
  41. x[i] = ;//上一行清零
  42. continue;
  43. }
  44. }
  45. //如果找到了第N层,输出出来
  46. if (i == N){
  47. cnt++;
  48. if (cnt <= ) {
  49. for (int i = ; i <= N; i++) {
  50. cout << x[i] << " ";
  51. }
  52. cout << endl;
  53. }
  54. j = x[i] + ;
  55. x[i] = ;
  56. continue;
  57. }
  58. i++;
  59. }
  60. }
  61. int main() {
  62. cin >> N;
  63. //DFS(1);
  64. queen();
  65. cout << cnt << endl;
  66. return ;
  67. }

2、减半优化

其实仔细看解就不难发现,每个结果总有另一个与之对称。我们可以利用棋盘的对称, 只用回溯一半 。效率能提升50%。

对于第一层,只下该行的前一半的位置即可。但是对于奇数的N,计算出来的结果会将第一行下在中间位置的解算了两遍。所以要单独处理一下。

效率提升不到50%(奇数的情况),并不算多,题目的测试数据只到13,勉强跑过了,但优化还没结束。

  1. #include <iostream>
  2. #include <vector>
  3. #include <math.h>
  4. using namespace std;
  5.  
  6. int x[];
  7. vector<int> v[];
  8. int N, cnt;
  9. int flag, oddCnt;
  10.  
  11. bool check(int row, int col) {
  12. //回溯,不会受到后面行的影响
  13. for (int i = ; i < row; i++) {
  14. if (x[i] == col)return false;
  15. if (abs(row - i) == abs(col - x[i]))return false;
  16. }
  17. return true;
  18. }
  19. void DFS(int k) {
  20. if (k == N + ) {
  21. if (flag&&x[] == (N + ) / ) {
  22. oddCnt++;
  23. if (oddCnt % == )cnt++;
  24. }
  25. else
  26. cnt++;
  27. if (cnt <= ) {
  28. for (int i = ; i <= N; i++) {
  29. cout << x[i] << " ";
  30. v[cnt - ].push_back(x[i]);
  31. }
  32. cout << endl;
  33. }
  34. return;
  35. }
  36. int len = (k == ) ? (N + flag) / : N;
  37. for (int i = ; i <= len; i++) {
  38. if (check(k, i)) {
  39. x[k] = i;
  40. DFS(k + );
  41. }
  42. }
  43. }
  44.  
  45. int main() {
  46. cin >> N;
  47. if (N & )flag = ;
  48. DFS();
  49. for (int i = cnt, j = cnt - ; i < && j >= ; i++, j--) {
  50. for (int k = N - ; k >= ; k--) {
  51. cout << v[j][k] << " ";
  52. }
  53. cout << endl;
  54. }
  55.  
  56. cout << cnt* << endl;
  57. return ;
  58. }

3、优化判断

以本图为例:

每条橙色对角线的行列之差是相同的。

每条蓝色对角线的行列之和是相同的。

用两个bool数组用来记录行列之和为 i 的正斜线、行列之差为 i 的反斜线是否已经被占据。考虑到行列之差可能为负数,棋盘坐标 [x,y] 对应下标 [ x - y + n ]。

再用一个数组记录第 i 列是否有元素。

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int N, cnt,a[];
  5. //正对角线、副对角线、行
  6. bool x1[], x2[], y[];
  7.  
  8. void DFS(int k) {
  9. if (k == N + ) {
  10. cnt++;
  11. if (cnt <= ) {
  12. for (int i = ; i <= N; i++) {
  13. cout << a[i] << " ";
  14. }
  15. cout << endl;
  16. }
  17. return;
  18. }
  19. for (int i = ; i <= N; i++) {
  20. //这里x2下标不能用abs,那样是不对的
  21. if (!x1[i + k] && !x2[k - i + N] && !y[i]) {
  22. a[k] = i;
  23. x1[i + k] = ;
  24. x2[k - i + N] = ;
  25. y[i] = ;
  26. DFS(k + );
  27. x1[i + k] = ;
  28. x2[k - i + N] = ;
  29. y[i] = ;
  30. }
  31. }
  32. }
  33.  
  34. int main() {
  35. cin >> N;
  36. DFS();
  37. cout << cnt << endl;
  38. return ;
  39. }

当N较大时,算法会耗费大量的次数在无用的回溯上,时间还是没有显著提高。

4、位运算优化

警告:以下代码可能引起不适,请60岁以下用户在家长陪同下阅读。

位运算是计算机最快的操作,我们可以用数的二进制位表示各纵列、对角线是否可以放置皇后。

看讲解的:https://blog.csdn.net/Hackbuteer1/article/details/6657109 博主讲的很清楚了。

  1. #include <iostream>
  2. #include <queue>
  3. using namespace std;
  4.  
  5. int n, limit, cnt;
  6. int x[], k = ;
  7. //行,左对角线,右对角线
  8. void DFS(int row,int left,int right) {
  9. if (row != limit) {
  10. //row|left|right表示这一行的所有禁止位置,取反再和limit按位与,得到的是该行可以放的几个位置
  11. int pos = limit & ~(row | left | right);
  12. //每一个可以摆的位置,都要做一次
  13. while (pos) {
  14. //找到的可以放皇后的位置(pos二进制最右边的一个1)
  15. int p = pos & -pos;// pos & (~pos+1);
  16. //把这一位置0,表示不为空
  17. pos &= pos - ;//pos=pos-p;
  18. //把p所在row,left,right的位都置1。
  19. //(left | p)<< 1 是因为这一行由左对角线造成的禁止位在下一行要右移一下;right同理
  20. DFS(row | p, (left | p) << , (right | p) >> );
  21. }
  22. }
  23. else {
  24. cnt++;
  25. }
  26. }
  27.  
  28. int main() {
  29. cin >> n;
  30. limit = ( << n) - ;
  31. DFS(, , );
  32. cout << cnt << endl;
  33. return ;
  34. }
  1. #include <iostream>
  2. #include <queue>
  3. using namespace std;
  4.  
  5. int n, limit, cnt;
  6. int x[], k = ;
  7. //行,左对角线,右对角线
  8. void DFS(int row,int left,int right) {
  9. if (row != limit) {
  10. int pos = limit & ~(row | left | right);
  11. while (pos) {
  12. //找到的可以放皇后的位置
  13. int p = pos & -pos;// pos & (~pos+1);
  14. pos &= pos - ;
  15. if (cnt < ) {
  16. int t = p, num = ;
  17. while (t != ) {
  18. num++;
  19. t >>= ;
  20. }
  21. x[k++] = num;
  22. }
  23. DFS(row | p, (left | p) << , (right | p) >> );
  24. if (cnt < ) k--;
  25. }
  26. }
  27. else {
  28. if (cnt < ) {
  29. for (int i = ; i <= n; i++) {
  30. cout << x[i] << " ";
  31. }
  32. cout << endl;
  33. }
  34. cnt++;
  35. }
  36. }
  37.  
  38. int main() {
  39. cin >> n;
  40. limit = ( << n) - ;
  41. DFS(, , );
  42. cout << cnt << endl;
  43. return ;
  44. }

果然名不虚传~

N皇后问题 各种优化的更多相关文章

  1. N皇后-位运算优化

    N皇后问题 时间限制: 5 Sec  内存限制: 128 MB 题目描述 魔法世界历史上曾经出现过一个伟大的罗马共和时期,出于权力平衡的目的,当时的政治理论家波利比奥斯指出:“事涉每个人的权利,绝不应 ...

  2. 回溯算法-C#语言解决八皇后问题的写法与优化

    结合问题说方案,首先先说问题: 八皇后问题:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 嗯,这个问题已经被使用各种语言解 ...

  3. PAT (Advanced Level) 1128~1131:1128N皇后 1129 模拟推荐系统(set<Node>优化) 1130 中缀表达式

    1128 N Queens Puzzle(20 分) 题意:N皇后问题.按列依次给定N个皇后的行号,问N个皇后是否能同时不存在行冲突.列冲突和主副对角线冲突. 分析: 1.根据题意一定不存在列冲突,所 ...

  4. N皇后解法以及位运算优化

    N皇后解法以及位运算优化 观察棋盘,要求皇后之间不能处在同行同列同一条斜线,求使得每行都有一个皇后的放置方法共有多少种. 每尝试放置一个皇后,都可以把该位置所在的行.列标号用一个数组标记,含义表示该行 ...

  5. 8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,循环控制及其优化

    上两篇博客 8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案 8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,数据结构“栈”实现 研究了递归方法实现回溯,解决N皇后问题,下面我们来 ...

  6. P1219 八皇后 含优化 1/5

    题目描述 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 上面的布局可以用序列2 4 6 1 3 ...

  7. N皇后问题的二进制优化详细思路

    题目啊常规解法(DFS)在此就不赘述了... 直接进入正题. 众所周知,N皇后是NP完全类问题,n稍微大了点求解过程就会变得很长. 算法方面很难再有质的效率突破,但这不妨在其他细节上下下功夫. 揆诸常 ...

  8. 【位运算经典应用】 N皇后问题

    说到位运算的经典应用,不得不说N皇后问题. 学过程序设计的都知道N皇后问题,没听过也没关系.很简单,最传统的的N皇后问题是这个样子的,给你一个n * n大小的board,让你放n个皇后(国际象棋),要 ...

  9. 八皇后,回溯与递归(Python实现)

    八皇后问题是十九世纪著名的数学家高斯1850年提出 .以下为python语句的八皇后代码,摘自<Python基础教程>,代码相对于其他语言,来得短小且一次性可以打印出92种结果.同时可以扩 ...

随机推荐

  1. 【游记】CCHO TY国初划水记

    没想到第一篇游记竟然是化学国初(其实是上次SXACM时候懒得写 DAY0 一下午做了5个小时的校车,服务区水真贵 肝了4个小时模拟题,颠到崩溃. 下榻在距离山大附不远的一个酒店,高三人好多哇,我们年级 ...

  2. SpringBoot中动态加载(热部署)

    在常规的Java Web开发过程中,在修改完代码后,往往需要重启Tomcat来使得我们的修改生效,在SpringBoot中也需要从新启动SpringBoot来将修改部署.如果我们不希望重启tomcat ...

  3. Java基础——String类(一)

    一.String 类代表字符串 Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 字符串是常量:它们的值在创建之后不能更改.字符串缓冲区支持可变的字符串 ...

  4. Mysql系统知识梳理

    1 数据库分类 MySQL Oracle redis 2 MySQL 存储引擎有哪些 ENGINE=InnoDB 提供事务安全表,支持外键. MyISAM Memory数据存入内存中,如果内存出现异常 ...

  5. python 系列文章汇总(持续更新...)

    引言 不知不觉已经写了好几篇 python 相关的随笔了,从刚开始的门外汉到现在已经对 python 有一些入门了,时间也已经过去了一个多月. 写博客真是好处多多,不仅能提供整理自己学习的知识点,梳理 ...

  6. WebServer搭建过程

    第一步,新建Web网站项目 第二步: 右击项目解决方案,选择添加新建项 选择Web服务项 第三步,在asmx方法中将Hello World注释掉,然后编写自己的方法,这里根据项目的不同可以做不同的处理 ...

  7. HDU3625(SummerTrainingDay05-N 第一类斯特林数)

    Examining the Rooms Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

  8. Python 进阶必备函数

    1. lambda 表达式 匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数.通俗来说呢,就是它可以让我们的函数,可以不需要函数名. 正常情况下,我们定义一 ...

  9. LOJ#505. 「LibreOJ β Round」ZQC 的游戏(最大流)

    题意 题目链接 Sol 首先把第一个人能吃掉的食物删掉 然后对每个人预处理出能吃到的食物,直接限流跑最大流就行了 判断一下最后的最大流是否等于重量和 注意一个非常恶心的地方是需要把除1外所有人都吃不到 ...

  10. KOTLIN-1(常用网址)

    ---恢复内容开始--- 1.官网:http://kotlinlang.org/ 2.官方文档:https://kotlinlang.org/docs/reference 3.kotlin源码:htt ...