回溯法求解N皇后问题(Java实现)

简介:

回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并以此慢慢地扩大问题规模,迭代地逼近最终问题的解。这种迭代类似于穷举并且是试探性的,因为当目前的可能答案被测试出不可能可以获得最终解时,则撤销当前的这一步求解过程,回溯到上一步寻找其他求解路径。

为了能够撤销当前的求解过程,必须保存上一步以来的求解路径,这一点相当重要。

光说不做没意思,用学过的算法题来运用一下。

N 皇后问题:在一个 N * N 的国际象棋棋盘中,怎样放置 N 个皇后才能使 N 个皇后之间不会互相有威胁而共同存在于棋局中,即在 N * N 个格子的棋盘中没有任何两个皇后是在同一行、同一列、同一斜线上。

求解思路:最容易想到的方法就是有序地从第 1 列的第 1 行开始,尝试放上一个皇后,然后再尝试第 2 列的第几行能够放上一个皇后,如果第 2 列也放置成功,那么就继续放置第 3 列,如果此时第 3 列没有一行可以放置一个皇后,说明目前为止的尝试是无效的(即不可能得到最终解),那么此时就应该回溯到上一步(即第 2 步),将上一步(第 2 步)所放置的皇后的位置再重新取走放在另一个符合要求的地方…如此尝试性地遍历加上回溯,就可以慢慢地逼近最终解了。

需要解决的问题:如何表示一个 N * N 方格棋盘能够更有效?怎样测试当前所走的试探路径是否符合要求?这两个问题都需要考虑到使用怎样的数据结构,使用恰当的数据结构有利于简化编程求解问题的难度。

为此,我们使用以下的数据结构:

int column[col] = row 表示第 col 列的第 row 行放置一个皇后

boolean rowExists[i] = true 表示第 i 行有皇后

boolean a[i] = true 表示右高左低的第 i 条斜线有皇后(按 →  ↓ 顺序1~ 2*N -1 依次编号)

boolean b[i] = true 表示左高右低的第 i 条斜线有皇后(按 →  ↑ 顺序1~ 2*N -1 依次编号)

对应这个数据结构的算法实现如下:

 
  1. /**  
  2.  * 回溯法求解 N 皇后问题  
  3.  * @author haolloyin  
  4.  */ 
  5. public class N_Queens {  
  6.       
  7.     // 皇后的个数  
  8.     private int queensNum = 4;  
  9.  
  10.     // column[i] = j 表示第 i 列的第 j 行放置一个皇后  
  11.     private int[] queens = new int[queensNum + 1];  
  12.  
  13.     // rowExists[i] = true 表示第 i 行有皇后  
  14.     private boolean[] rowExists = new boolean[queensNum + 1];  
  15.  
  16.     // a[i] = true 表示右高左低的第 i 条斜线有皇后  
  17.     private boolean[] a = new boolean[queensNum * 2];  
  18.  
  19.     // b[i] = true 表示左高右低的第 i 条斜线有皇后  
  20.     private boolean[] b = new boolean[queensNum * 2];  
  21.       
  22.     // 初始化变量  
  23.     private void init() {  
  24.         for (int i = 0; i < queensNum + 1; i++) {  
  25.             rowExists[i] = false;  
  26.         }  
  27.           
  28.         for(int i = 0; i < queensNum * 2; i++) {  
  29.             a[i] = b[i] = false;  
  30.         }  
  31.     }  
  32.  
  33.     // 判断该位置是否已经存在一个皇后,存在则返回 true  
  34.     private boolean isExists(int row, int col) {  
  35.         return (rowExists[row] || a[row + col - 1] || b[queensNum + col - row]);  
  36.     }  
  37.  
  38.     // 主方法:测试放置皇后  
  39.     public void testing(int column) {  
  40.  
  41.         // 遍历每一行  
  42.         for (int row = 1; row < queensNum + 1; row++) {  
  43.             // 如果第 row 行第 column 列可以放置皇后  
  44.             if (!isExists(row, column)) {  
  45.                 // 设置第 row 行第 column 列有皇后   
  46.                 queens[column] = row;  
  47.                 // 设置以第 row 行第 column 列为交叉点的斜线不可放置皇后  
  48.                 rowExists[row] = a[row + column - 1] = b[queensNum + column - row] = true;  
  49.                   
  50.                 // 全部尝试过,打印  
  51.                 if(column == queensNum) {  
  52.                     for(int col = 1; col <= queensNum; col++) {  
  53.                         System.out.print("("+col + "," + queens[col] + ")  ");  
  54.                     }  
  55.                     System.out.println();  
  56.                 }else {  
  57.                     // 放置下一列的皇后  
  58.                     testing(column + 1);  
  59.                 }  
  60.                 // 撤销上一步所放置的皇后,即回溯  
  61.                 rowExists[row] = a[row + column - 1] = b[queensNum + column - row] = false;  
  62.             }  
  63.         }  
  64.     }  
  65.       
  66.     // 测试  
  67.     public static void main(String[] args) {  
  68.         N_Queens queen = new N_Queens();  
  69.         queen.init();  
  70.         // 从第 1 列开始求解  
  71.         queen.testing(1);  
  72.     }  


当 N = 8 时,求解结果如下(注:横坐标为 列数, 纵坐标为 行数):

 
 
 
  1. (1,1)  (2,5)  (3,8)  (4,6)  (5,3)  (6,7)  (7,2)  (8,4)    
  2. (1,1)  (2,6)  (3,8)  (4,3)  (5,7)  (6,4)  (7,2)  (8,5)    
  3. (1,1)  (2,7)  (3,4)  (4,6)  (5,8)  (6,2)  (7,5)  (8,3)    
  4. ... ...  
  5. ... ...  
  6. (1,8)  (2,2)  (3,4)  (4,1)  (5,7)  (6,5)  (7,3)  (8,6)    
  7. (1,8)  (2,2)  (3,5)  (4,3)  (5,1)  (6,7)  (7,4)  (8,6)    
  8. (1,8)  (2,3)  (3,1)  (4,6)  (5,2)  (6,5)  (7,7)  (8,4)    
  9. (1,8)  (2,4)  (3,1)  (4,3)  (5,6)  (6,2)  (7,7)  (8,5)   
当 N = 4 时,求解结果如下:
 
 
 
  1. (1,2)  (2,4)  (3,1)  (4,3)    
  2. (1,3)  (2,1)  (3,4)  (4,2)  

有时间的话将输出的结果打印为直观一点的符号形式或界面形式更好。
 
小结:
1、根据问题选择恰当的数据结构非常重要,就像上面 a 、b 标志数组来表示每一条斜线的编号顺序以及方向都相当重要。看书的时候也是费了些时间来理解的,呼…另外,queens [col] = row 数组只是用了一维而不是二维来表示纵横交错的方格棋盘上特定位置是否有皇后也是比较经济而有意思的。
 
2、正确运用、组织所确定的数据结构到算法的实现逻辑中也是很重要的,就像代码中的 isExists(int row, int col) 方法内的 (rowExists[row] || a[row + col - 1] || b[queensNum + col - row]) 就是很明确的理解了尝试放置皇后的位置的 x ,y 坐标与斜线之间的数值关系,才使得算法得以正确执行。当然,对于斜线的编号、顺序也是相当重要的。


本文转自 xxxx66yyyy 51CTO博客,原文链接:http://blog.51cto.com/haolloyin/353105,如需转载请自行联系原作者
相关文章
|
Java
Java 实现汉字按照首字母分组排序
Java 实现汉字按照首字母分组排序
720 0
|
Java 数据安全/隐私保护
JAVA 实现上传图片添加水印(详细版)(上)
JAVA 实现上传图片添加水印(详细版)
1279 0
JAVA 实现上传图片添加水印(详细版)(上)
|
网络协议 Java
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
ip地址的分类: 1、ipv4、ipv6 127.0.0.1:4个字节组成,0-255,42亿;30亿都在北美,亚洲就只有4亿 2011年就用尽了。
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
|
Java
Java实现拼图小游戏(7)——查看完整图片(键盘监听实例2)
由于在移动和图片中我们已经添加了键盘监听,也继承了键盘监听的接口,那么我们只需要在重写方法内输入我们的代码即可
221 0
|
存储 Java
Java实现图书管理系统
本篇文章是对目前Java专栏已有内容的一个总结练习,希望各位小主们在学习完面向对象的知识后,可以阅览本篇文章后,自己也动手实现一个这样的demo来加深总结应用已经学到知识并进行巩固。
424 0
Java实现图书管理系统
|
数据可视化 Java
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
如果要在某一个界面里面添加功能的话,都在一个类中,会显得代码难以阅读,而且修改起来也会很困难,所以我们将游戏主界面、登录界面、以及注册界面都单独编成一个类,每一个类都继承JFrame父类,并且在类中创建方法来来实现页面
546 0
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
|
数据可视化 Java 容器
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
注意由于我们计步功能的步数要在重写方法中用到,所以不能将初始化语句写在方法体内,而是要写在成员位置。在其名字的时候也要做到“见名知意”,所以我们给它起名字为step
330 0
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
|
Java
Java实现拼图小游戏(7)—— 作弊码和判断胜利
当我们好不容易把拼图复原了,但是一点提示也没有,完全看不出来是成功了,那么我们就需要有判断胜利的功能去弹出“成功”类的图片,以便于玩家选择是重新开始还是退出小游戏
313 0
Java实现拼图小游戏(7)—— 作弊码和判断胜利
|
Java
Java实现拼图小游戏(6)—— 移动图片(键盘监听实操练习)
当我们实现向上移动图片的时候,其实就是把空图片的下面一张图片往上移动,然后将空图片的下面那张图片设置为空图片,最后再调整初始位置为现在空图片所在位置即可,注意做完这些以后还要再加载图片,否则显示不出来
387 0
Java实现拼图小游戏(6)—— 移动图片(键盘监听实操练习)
|
存储 Java 数据库
JAVA实现网络多线程编程小游戏开发
实验总结:五子棋是一个很简单的游戏,但是如果认真对待,一个代码一个代码的去研究,会收获到很多知识,会打好学习基础。方便以后开发更高、更难的项目时打下稳固的基础。在自己开发的过程中会有各种意想不到的bug,通过查阅资料及询问老师同学进行解决对本身的一个代码能力会有一个质的增长,同时这也是一个非常快乐的过程。有进步,总归是好事。
JAVA实现网络多线程编程小游戏开发