n皇后问题-回溯法求解[通俗易懂]

n皇后问题-回溯法求解[通俗易懂]n皇后问题-回溯法求解1.算法描述在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。n皇后是由八皇后问题演变而来的。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

n皇后问题-回溯法求解

1.算法描述

在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

img1

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

2.算法分析

随着计算机的普及和发展,以前人们无法解决的问题,计算机可以简单计算出来。而且思路十分清晰,那就是暴力求解,遍历所有情况,然后计算出解的个数。

2.1 回溯法

按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法

2.2 回溯法思路

用数组模拟棋盘,从第一行开始,依次选择位置, 如果当前位置满足条件,则向下选位置, 如果不满足条件,那么当前位置后移一位。

img2

img3

最后一个不满足,回溯到上一行, 选下个位置,继续试探。

其实并不需要一个n*n的数组,我们只需要一个n长度的数组来存位置。

表示方式: arr[i] = k; 表示: 第i行的第k个位置放一个皇后。这样一个arr[n]的数组就可以表示一个可行解, 由于回溯,我们就可以求所有解。

2.3 n皇后回溯求解

因为八皇后不能在同行,同列, 同斜线。

  1. 每一行放一个皇后,就解决了不在同行的问题。
  2. 在第i行的时候,遍历n列,试探位置。和之前所有行放的位置进行比较。
  3. 比较列:当前列col 不等于 之前 所有列。 即col != arr[i].
  4. 比较斜线, 因为不再同一斜率为1或者-1的斜线。(row – i) / (col – arr[i]) != 1 或 -1
    可以取巧用绝对值函数: abs(row-i) != abs(col-arr[i])

我们可以提取比较方法:

public boolean comp(int row, int col){ 
      //当前行和列
    for (int i = 0; i < row; i++)  //比较之前row -1 列。
    { 
   
      if(col == arr[i] || abs(row-i) != abs(col-arr[i]))  //如果在同一列,同一斜线
          return false;
    }
    return true;
}

比较函数写完后, 就只剩下回溯过程, 我们采用递归的方式回溯,比较好理解。

//当前层 row
for (int i = 0; i < n; i++){ 
   
  if(comp(row, i)){ 
   
    arr[row] = i;
    //同样方式遍历下一层。 row + 1
  }
}

2.4 时间复杂度

最坏的情况: 每一行有n种情况,有n行, 所以时间复杂度为O(n^n)。
但是由于回溯法会提前判断并抛弃一些情况,使得时间复杂度并没有想象中那么高。但是说是O(n!)也不太对,具体怎么算,暂时也不太清楚。(之前想当然了,此处有修正,多谢评论中的提醒。)

2.5 空间复杂度

因为只用了arr[n]的数组,也就是O(n).

3.代码实现

public class NQueen { 
   
    private int n;
    private long count;
    private int[] arr;
// private int nn;

    public NQueen(int n){ 
   
        this.n = n;
  // nn = (1 << n) - 1;
        count = 0;
        arr = new int[n];
    }


    /** * row col i arr[i] * @param row * @param col * @return */
    public boolean Check(int row, int col){ 
   
        for(int i = 0; i < row; i++){ 
   
            if(col == arr[i] || Math.abs(row - i) == Math.abs(col - arr[i])) //在同一列或者在同一斜线。一定不在同一行
                return false;
        }
        return true;
    }

    public void FindNQueen(int k) { 
   
        if (k == n) { 
      //求出一种解, count+1
            count++;
            return;
        }

        for (int i = 0; i < n; i++) { 
   
            if (Check(k, i)) { 
      //检查是否满足条件
                arr[k] = i;      //记录
                FindNQueen(k + 1);   //递归查找
            }
        }

    }

    public static void main(String args[]){ 
   
        NQueen nQueen = new NQueen(13);
        nQueen.FindNQueen(0);
        System.out.println(nQueen.count);
    }

}

4. 拓展,位运算+回溯法实现

虽然计算机算的很快,但是上诉方法实在是太慢了, java就更慢了。如何网上就有大佬给出了位运算求解。精妙的不行。

4.1 算法思路

我们不再用数组来存储位置,而是用一个整数k,k一开始等于0. 不是普通的0.我们也不比较了,直接用两个整数l和r 记录在斜线在当前行不能走的位置。如果是n皇后, 那么用一个整数
nn = 1 << n 表示结束。

举个栗子吧: 8皇后问题。

初始化 那么我们就变成二进制的角度来看这些初始化的数据吧。

k = 00000000, l = 00000000, r = 00000000; nn = 11111111; (<- 8个0 8个1)

  1. k: 每个位置i的0表示没有皇后,1表示在第i个位置放了一个皇后。
  2. l: 0表示之前所有的列中放的皇后斜率为-1的线上没有涉及这个位置, 1 表示涉及到了,不能放皇后
  3. r: 同l, 所有斜率为1的线涉及的位置。

l和r的实现:

比如k = 00110001. 我要在第4个为位置放一个皇后, 假设l和r都没有涉及这个位置。

那么这个位置x= 00001000.

  1. k = (k & x) = 00111001.
  2. l = (l & x) << 1.
  3. r = (r & x) >> 1.

假设l = 00110001, r = 00100010.下一行,l表示斜率为-1不能放的位置, 那么第i+1行 l 中所有为1的数字都需要向左移动一位,r需要向右移动一位。 l & x 也就是加上当前选中的位置一起移动。

4.2代码实现

   //k 当前已走了多少个位置。 l 左斜线不能走的位置, r 右斜线不能走的位置。
   public void FindNQueen(int k, int l, int r){ 
   
       if(k == nn){ 
   
           count++;
           return;
       }

       int z = nn & (~(k | l | r));  //能走的位置, 和nn取并可以去掉前面多余的1
       while(z != 0){ 
   
           int index = z & (~z+1);   //最右边的一个1, 即要放皇后的位置。
           z -= index;   //去掉这个位置。

           FindNQueen(k | index, (l|index)<<1, (r|index)>>1);   //查找下一个。
       }


   }

4.3 算法分析

  1. 时间复杂度,应该同上分析一致,去掉但是少了检查的for循环,应该是O(n!)
  2. 空间复杂度,只用了几个变量, O(1).

5. 展望

其实还有其他方式和更快的方式求解,比如位运算+多线程, 还有号称时间复杂度为O(1),利用数学公式的构造法求解。扶我起来,我要继续学。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/187371.html原文链接:https://javaforall.net

(0)
上一篇 2022年9月30日 下午7:36
下一篇 2022年9月30日 下午7:46


相关推荐

  • python:set() 函数[通俗易懂]

    python:set() 函数[通俗易懂]描述Python内置函数创建一个无序不重复元素集可进行关系测试,删除重复数据集合对象还支持union(联合),intersection(交),difference(差)和sysmmetr

    2022年7月6日
    25
  • 阿里对标 ChatGPT 项目来了?通义 App 正式更名“千问”,版本号跳至 5.0.0

    阿里对标 ChatGPT 项目来了?通义 App 正式更名“千问”,版本号跳至 5.0.0

    2026年3月13日
    2
  • Java数组超详解

    Java数组超详解一、前言前面我们学习了随机数的介绍和使用,那么这篇我们来学习java中数组的定义和使用,java的数组和c语言的十分类似。二、数组的定义数组定义的形式:格式1:数据类型[]数组名;如int[]arr;说明:定义了一个int类型的数组,数组名是arr格式2:数据类型数组名[];如intarr[];说明:定义了一个int类型的数组名是arr的数组…

    2022年7月14日
    19
  • Nacos配置中心,拉取配置报NACOS SocketTimeoutException httpGet] currentServerAddr:http://localhost:8848,error超时

    Nacos配置中心,拉取配置报NACOS SocketTimeoutException httpGet] currentServerAddr:http://localhost:8848,error超时Nacos 配置中心 项目拉取配置报错原因 NACOSSocketT currentServe http localhost 8848 err connecttimed 原因 bootstrap yml bootstrap properties 用来在程序引导时执行 应用于更加早期配置信息读取 如可以使用来配置 application yml 中使用到参数等 application yml application properti

    2026年3月26日
    1
  • Velocity模板引擎

    Velocity模板引擎velocity 简介 velocity 介绍 Velocity 是一个基于 Java 的模板引擎 可以通过特定的语法获取在 java 对象的数据 填充到模板中 从而实现界面和 java 代码的分离应用场景 Web 应用程序 作为为应用程序的视图 展示数据 源代码生成 velocity 可用于基于模板生成 Java 源代码自动电子邮件 网站注册 认证等的电子邮件模板网页静态化 基于 velocity 模板 生成静态网页 velocity 结构 Velocity 主要分为 app context runtime

    2026年3月26日
    2
  • C++实践(四):C++实现AES-CMAC算法

    C++实践(四):C++实现AES-CMAC算法AES CMACAES CMAC 使用了高级加密标准作为组分 为了产生一个消息认证码 CMAC 需要一个密钥 消息 message 及消息的长度 length 作为输入 输出是消息认证码 AES CMAC 的核心是 CBC MAC 对于待加密消息 M 应用 CBC MAC 算法 在 CMAC 操作中有两种情况 如果输入消息长度等于 Block 的整数倍 最后的 BlockM n 需要先于 K1 异或再进行处理 如果输

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号