八皇后的来源

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解。

八皇后问题最早是由国际象棋棋手马克斯·贝瑟尔(Max Bezzel)于1848年提出。第一个解在1850年由弗朗兹·诺克(Franz Nauck)给出。并且将其推广为更一般的n皇后摆放问题。诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。

问题分析

我们先不看8皇后,我们先来看一下4皇后,其实原理都是一样的,比如在下面的4*4的格子里,如果我们在其中一个格子里输入了皇后,那么在这一行这一列和这左右两边的对角线上都不能有皇后。

所以有一种方式就是我们一个个去试

第一行

比如我们在第一行第一列输入了一个皇后

第二行

第二行我们就不能在第一列和第二列输入皇后了,因为有冲突了。但我们可以在第3列输入皇后

第三行

第三行我们发现在任何位置输入都会有冲突。这说明我们之前选择的是错误的,再回到上一步,我们发现第二步不光能选择第3列,而且还能选择第4列,既然选择第3列不合适,那我们就选择第4列吧

第二行(重新选择)

第二行我们选择第4列

第三行(重新选择)

第3行我们只有选择第2列不会有冲突

第四行

我们发现第4行又没有可选择的了。第一次重试失败

第二次重试

到这里我们只有重新回到第一步了,这说明我们之前第一行选择第一列是无解的,所以我们第一行不应该选择第一列,我们再来选择第二列来试试

第一行

这一行我们选择第2列

第二行

第二行我们前3个都不能选,只能选第4列

第三行

第三行我们只能选第1列

第四行

第四行我们只能选第3列

最后我们终于找到了一组解。除了这组解还有没有其他解呢,肯定还是有的,因为4皇后是有两组解的,这里我们就不在一个个试了。

我们来看一下他查找的过程,就是先从第1行的第1列开始往下找,然后再从第1行的第2列……,一直到第1行的第n列。代码该怎么写呢,看到这里估计大家都已经想到了,这不就是一棵N叉树的前序遍历吗,我们来看下,是不是下面这样的。

我们先来看一下二叉树的前序遍历怎么写,不明白的可以参考下373,数据结构-6,树

public static void preOrder(TreeNode tree) {    if (tree == null)        return;    System.out.printf(tree.val + "");    preOrder(tree.left);    preOrder(tree.right);}

二叉树是有两个子节点,那么N叉树当然是有N个子节点了,所以N叉树的前序遍历是这样的

public static void preOrder(TreeNode tree) {    if (tree == null)        return;    System.out.printf(tree.val + "");    preOrder("第1个子节点");    preOrder("第2个子节点");    ……    preOrder("第n个子节点");}

如果N是一个很大的值,这样写要写死人了,我们一般使用循环的方式

public static void preOrder(TreeNode tree) {    if (tree == null)        return;    System.out.printf(tree.val + "");    for (int i = 0; i <n ; i++) {        preOrder("第i个子节点");    }}

搞懂了上面的分析过程,那么这题的代码轮廓就呼之欲出了

private void solve(char[][] chess, int row) {"终止条件"return;for (int col = 0; col < chess.length; col++) {//判断这个位置是否可以放皇后if (valid(chess, row, col)) {//如果可以放,我们就把原来的数组chess复制一份,char[][] temp = copy(chess);//然后在这个位置放上皇后temp[row][col] = 'Q';//下一行solve(temp, row + 1);}}
}

我们来分析下上面的代码,因为是递归所以必须要有终止条件,那么这题的终止条件就是我们最后一行已经走完了,也就是

if (row == chess.length) {         return;}

第7行就是判断在这个位置我们能不能放皇后,如果不能放我们就判断这一行的下一列能不能放……,如果能放我们就把原来数组chess复制一份,然后把皇后放到这个位置,然后再判断下一行,这和我们上面画图的过程非常类似。注意这里的第9行为什么要复制一份,因为数组是引用传递,这涉及到递归的时候分支污染问题(后面有时间我会专门写一篇关于递归的时候分支污染问题)。当然不复制一份也是可以的,我们下面再讲。当我们把上面的问题都搞懂的时候,代码也就很容易写出来了,我们来看下N皇后的最终代码

 1public void solveNQueens(int n) {2    char[][] chess = new char[n][n];3    for (int i = 0; i < n; i++)4        for (int j = 0; j < n; j++)5            chess[i][j] = '.';6    solve(chess, 0);7}89private void solve(char[][] chess, int row) {
10    if (row == chess.length) {
11        //自己写的一个公共方法,打印二维数组的,
12        // 主要用于测试数据用的
13        Util.printTwoCharArrays(chess);
14        System.out.println();
15        return;
16    }
17    for (int col = 0; col < chess.length; col++) {
18        if (valid(chess, row, col)) {
19            char[][] temp = copy(chess);
20            temp[row][col] = 'Q';
21            solve(temp, row + 1);
22        }
23    }
24}
25
26//把二维数组chess中的数据测下copy一份
27private char[][] copy(char[][] chess) {
28    char[][] temp = new char[chess.length][chess[0].length];
29    for (int i = 0; i < chess.length; i++) {
30        for (int j = 0; j < chess[0].length; j++) {
31            temp[i][j] = chess[i][j];
32        }
33    }
34    return temp;
35}
36
37//row表示第几行,col表示第几列
38private boolean valid(char[][] chess, int row, int col) {
39    //判断当前列有没有皇后,因为他是一行一行往下走的,
40    //我们只需要检查走过的行数即可,通俗一点就是判断当前
41    //坐标位置的上面有没有皇后
42    for (int i = 0; i < row; i++) {
43        if (chess[i][col] == 'Q') {
44            return false;
45        }
46    }
47    //判断当前坐标的右上角有没有皇后
48    for (int i = row - 1, j = col + 1; i >= 0 && j < chess.length; i--, j++) {
49        if (chess[i][j] == 'Q') {
50            return false;
51        }
52    }
53    //判断当前坐标的左上角有没有皇后
54    for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
55        if (chess[i][j] == 'Q') {
56            return false;
57        }
58    }
59    return true;
60}

代码看起来比较多,我们主要看下solve方法即可,其他的方法不看也可以,知道有这个功能就行。solve代码中其核心代码是在17-23行,上面是终止条件的判断,我们就用4皇后来测试一下

solveNQueens(4);

看一下打印的结果

我们看到4皇后的时候有两组解,其中第一组和我们上面图中分析的完全一样。

4皇后解决了,那么8皇后也一样,我们只要在函数调用的时候传入8就可以了。同理,要想计算10皇后,20皇后,100皇后……也都是可以的。

回溯解决

上面代码中每次遇到能放皇后的时候,我们都会把原数组复制一份,这样对新数据的修改就不会影响到原来的,也就是不会造成分支污染。但这样每次尝试的时候都都把原数组复制一份,影响效率,有没有其他的方法不复制呢,是有的。就是每次我们选择把这个位置放置皇后的时候,如果最终不能成功,那么返回的时候我们就还要把这个位置还原。这就是回溯算法,也是试探算法。我们来看下代码

 1private void solve(char[][] chess, int row) {2    if (row == chess.length) {3        //自己写的一个公共方法,打印二维数组的,4        // 主要用于测试数据用的5        Util.printTwoCharArrays(chess);6        System.out.println();7        return;8    }9    for (int col = 0; col < chess.length; col++) {
10        if (valid(chess, row, col)) {
11            chess[row][col] = 'Q';
12            solve(chess, row + 1);
13            chess[row][col] = '.';
14        }
15    }
16}

主要来看下11-13行,其他的都没变,还和上面的一样。这和我们之前讲的391,回溯算法求组合问题很类似。他是先假设[row][col]这个位置可以放皇后,然后往下找,无论找到找不到最后都会回到这个地方,因为这里是递归调用,回到这个地方的时候我们再把它复原,然后走下一个分支。最后我们再来看下使用回溯算法解N皇后的完整代码

 public void solveNQueens(int n) {char[][] chess = new char[n][n];for (int i = 0; i < n; i++)for (int j = 0; j < n; j++)chess[i][j] = '.';solve(chess, 0);}private void solve(char[][] chess, int row) {if (row == chess.length) {//自己写的一个公共方法,打印二维数组的,// 主要用于测试数据用的Util.printTwoCharArrays(chess);System.out.println();return;}for (int col = 0; col < chess.length; col++) {if (valid(chess, row, col)) {chess[row][col] = 'Q';solve(chess, row + 1);chess[row][col] = '.';}}
}//row表示第几行,col表示第几列
private boolean valid(char[][] chess, int row, int col) {//判断当前列有没有皇后,因为他是一行一行往下走的,//我们只需要检查走过的行数即可,通俗一点就是判断当前//坐标位置的上面有没有皇后for (int i = 0; i < row; i++) {if (chess[i][col] == 'Q') {return false;}}//判断当前坐标的右上角有没有皇后for (int i = row - 1, j = col + 1; i >= 0 && j < chess.length; i--, j++) {if (chess[i][j] == 'Q') {return false;}}//判断当前坐标的左上角有没有皇后for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {if (chess[i][j] == 'Q') {return false;}}return true;
}

总结

8皇后问题其实是一道很经典的回溯算法题,我们这里并没有专门针对8皇后来讲,我们这里讲的是N皇后,如果这道题搞懂了,那么8皇后自然也就懂了,因为这里的N可以是任何正整数。

递归一般可能会有多个分支,我们要保证每个分支的修改不会污染其他分支,也就是不要对其他分支造成影响,这一点很重要。由于一些语言中除了基本类型以外,其他的大部分都是引用传递,所以我们在一个分支修改之后可能就会对其他分支产生一些我们不想要的垃圾数据,这个时候我们就有两中解决方式,一种就是我们上面讲到的第一种,复制一份新的数据,这样每个分支都会产生一些新的数据,所以即使修改了也不会对其他分支有影响。还一种方式就是我们每次使用完之后再把它复原,一般的情况下我们都会选择第二种,因为这种代码更简洁一些,也不会重复的复制数据,造成大量的垃圾数据。

转自:https://mp.weixin.qq.com/s?__biz=MzU0ODMyNDk0Mw==&mid=2247487459&idx=1&sn=f45fc8231198edb3de2acc69e17986ea&chksm=fb419cc3cc3615d5d31d55072d5c2f58e4002aa8c1b637e89f1ef577e253943c04efc39c1b1d&scene=21#wechat_redirect

2021-01-11经典的八皇后问题和N皇后问题, 回溯相关推荐

  1. (三十 :2021.01.11)MICCAI 2018 追踪之论文纲要(上)

    讲在前面 这部分是PART I和PART II. 论文目录 PART I Image Quality and Artefacts(图像质量和伪影) 概要 1.Conditional Generativ ...

  2. Java黑皮书课后题第7章:***7.36(游戏:八皇后问题)经典的八皇后难题是要将八个皇后放在棋盘上,任何两个皇后都不能相互攻击(没有两个皇后在同行、同列、同一对角线)。编写程序显示一个解决方案

    7.36(游戏:八皇后问题)经典的八皇后难题是要将八个皇后放在棋盘上,任何两个皇后都不能相互攻击(没有两个皇后在同行.同列.同一对角线).编写程序显示一个解决方案 题目 题目描述 破题 题目 题目描述 ...

  3. 八皇后问题初始思路python_【单人解谜】经典的八皇后问题解析

    哈啰~ 大家好,今天分享一个有趣的皇后问题. 八皇后问题是西洋棋延伸而来的问题, 西洋棋的皇后是威力很大的棋子, 皇后在棋盘上的「攻击范围」为同行.同列.同对角线的地方, 要如何在8*8的方形棋盘上摆 ...

  4. 经典问题——八皇后问题:最适合C语言初学者的解法

    什么是八皇后问题: 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后 ...

  5. 新鲜出炉 | 临床基因组学数据分析实战将于2021年11月12-14开课!!!

    福利公告:为了响应学员的学习需求,经过易生信培训团队的讨论筹备,现安排<临床基因组学数据分析实战>于2021年11月12-14 线上/线下课程 (线上课是通过腾讯会议实时直播线下课,实时互 ...

  6. 2021年11月中国乘用车品牌出口量排行榜:MGZS出口量突破2万辆,Top前五中有3个品牌归属上海股份(附月榜TOP150详单)

        榜单解读: 2021年11月,中国汽车工业协会统计的302个乘用车品牌累计出口170190辆,环比下降14.13%,同比增长71.23%,其中SUV出口量占比67.65%,轿车出口量占比30. ...

  7. mysql经典总结文章_MySQL基础篇(01):经典实用查询案例,总结整理

    MySQL基础篇(01):经典实用查询案例,总结整理 发布时间:2020-02-26 22:25:21 来源:51CTO 阅读:244 作者:知了一笑 本文源码:GitHub·点这里 || GitEE ...

  8. 2021双11上云狂欢节 | 爆款产品底价全面开售

    一年一度的双11狂欢节终于来啦!怎样用最少的钱获得最多的福利?下面这份攻略收藏好,这波活动快冲! 双11主会场:http://click.aliyun.com/m/1000304310/ 01 领取双 ...

  9. 2021年11月国产数据库排行榜:openGauss闯入前三,Kingbase流行度与日俱增,TDengine厚积薄发

    点击蓝字 关注我们 2021年11月的国产数据库流行度排行榜已在墨天轮发布,本月共有163家数据库参与排名.就前15名的总体情况来看,除openGauss反超OceanBase闯入前三,TDengin ...

  10. 2021年11月线上消费信贷市场用户洞察:头部效应明显,绿色金融成新风口

    根据易观千帆数据显示,2021年11月,中国线上消费信贷市场用户活跃人数达到5697万人.尽管消费需求的下降叠加部分消费场景趋严致使近半年来活跃人数走低,但环比看已出现回升迹象,从7月份的-7.7%恢 ...

最新文章

  1. 【Python】zip函数
  2. Hadoop实践之Python(一)
  3. CVPR 2019 | APDrawingGAN:人脸秒变艺术肖像画
  4. 免费Google地图API使用说明(转)
  5. Java私有构造函数不能阻止继承
  6. char数组拷贝wchar数组
  7. mysql待办事项表名_Activiti中彻底解决待办事项列表查询复杂、API不友好的设计方案...
  8. 修改linux服务器时间
  9. Spring-beans-BeanWrapper
  10. 阳新一中2021高考成绩查询,阳新一中2019高考成绩喜报、一本二本上线人数情况...
  11. 使用vue构建组件化开发项目
  12. python中grid是什么意思_python中网格Grid和列表List的认识
  13. sge中的windows选项
  14. 港澳台、大陆身份证正则表达式
  15. 解决 Ubuntu 安装显卡驱动后,屏幕变黄的原因
  16. marshmallow库的简单学习
  17. TCP创建多人聊天室
  18. CocosCreator项目学习系列lt;三gt;BattleCity_90tank坦克大战项目的学习
  19. linux u盘更新程序,嵌入式linux下插u盘自动更新的设计
  20. 网站性能优化实战——从12.67s到1.06s的故事

热门文章

  1. java 面试 接到邀请后你可以做哪些准备
  2. svga插件_如何利用AE导出SVGA(svga)JSON(json)动画的工作分享
  3. Python Scrapy中文教程,Scrapy框架快速入门!
  4. PADS使用技巧——页间连接符的方向转换
  5. Quartus II 安装教程—FPGA入门教程
  6. 如何做好初中物理实验室的建设与管理
  7. SHIF算法原理分析
  8. 海云安:利益博弈,APP安全漏洞背后的攻防交锋
  9. (附源码)python《C语言程序设计》课程案例库研究 毕业设计 030946
  10. Android3d结构光,为什么3D结构光在安卓手机中没有取代指纹识别?