8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案

川长思鸟来 2022-05-22 08:43 235阅读 0赞

原文地址:https://www.cnblogs.com/newflydd/p/5091646.html

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。———以上节选自百度百科。

算法思考,初步思路:

构建二维int或者short型数组,内存中模拟棋盘

chess[r][c]=0表示:r行c列没有皇后,chess[r][c]=1表示:r行c列位置有一个皇后

从第一行第一列开始逐行摆放皇后

依题意每行只能有一个皇后,遂逐行摆放,每行一个皇后即可

摆放后立即调用一个验证函数(传递整个棋盘的数据),验证合理性,安全则摆放下一个,不安全则尝试摆放这一行的下一个位置,直至摆到棋盘边界

当这一行所有位置都无法保证皇后安全时,需要回退到上一行,清除上一行的摆放记录,并且在上一行尝试摆放下一位置的皇后(回溯算法的核心)

当摆放到最后一行,并且调用验证函数确定安全后,累积数自增1,表示有一个解成功算出

验证函数中,需要扫描当前摆放皇后的左上,中上,右上方向是否有其他皇后,有的话存在危险,没有则表示安全,并不需要考虑当前位置棋盘下方的安全性,因为下面的皇后还没有摆放

回溯算法的天然实现是使用编译器的递归函数,但是其性能令人遗憾

下面我们使用上面的思路初步实现8皇后的问题解法,并且将所有解法打印出来,供我们验证正确性

  1. import java.util.Date;
  2. /** * 在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击, * 即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 * 下面使用递归方法解决 * @author newflydd@189.cn * */
  3. public class EightQueen {
  4. private static final short N=8; //使用常量来定义,方便之后解N皇后问题
  5. private static int count=0; //结果计数器
  6. public static void main(String[] args) {
  7. Date begin =new Date();
  8. //初始化棋盘,全部置0
  9. short chess[][]=new short[N][N];
  10. for(int i=0;i<N;i++){
  11. for(int j=0;j<N;j++){
  12. chess[i][j]=0;
  13. }
  14. }
  15. putQueenAtRow(chess,0);
  16. Date end =new Date();
  17. System.out.println("解决 " +N+ " 皇后问题,用时:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,计算结果:"+count);
  18. }
  19. private static void putQueenAtRow(short[][] chess, int row) {
  20. /** * 递归终止判断:如果row==N,则说明已经成功摆放了8个皇后 * 输出结果,终止递归 */
  21. if(row==N){
  22. count++;
  23. System.out.println("第 "+ count +" 种解:");
  24. for(int i=0;i<N;i++){
  25. for(int j=0;j<N;j++){
  26. System.out.print(chess[i][j]+" ");
  27. }
  28. System.out.println();
  29. }
  30. return;
  31. }
  32. short[][] chessTemp=chess.clone();
  33. /** * 向这一行的每一个位置尝试排放皇后 * 然后检测状态,如果安全则继续执行递归函数摆放下一行皇后 */
  34. for(int i=0;i<N;i++){
  35. //摆放这一行的皇后,之前要清掉所有这一行摆放的记录,防止污染棋盘
  36. for(int j=0;j<N;j++)
  37. chessTemp[row][j]=0;
  38. chessTemp[row][i]=1;
  39. if( isSafety( chessTemp,row,i ) ){
  40. putQueenAtRow(chessTemp,row+1);
  41. }
  42. }
  43. }
  44. private static boolean isSafety(short[][] chess,int row,int col) {
  45. //判断中上、左上、右上是否安全
  46. int step=1;
  47. while(row-step>=0){
  48. if(chess[row-step][col]==1) //中上
  49. return false;
  50. if(col-step>=0 && chess[row-step][col-step]==1) //左上
  51. return false;
  52. if(col+step<N && chess[row-step][col+step]==1) //右上
  53. return false;
  54. step++;
  55. }
  56. return true;
  57. }
  58. }

输出结果:
这里写图片描述

需要打印棋盘时,耗时34毫秒,再看一看不需要打印棋盘时的性能:
这里写图片描述

耗时2毫秒,性能感觉还可以。

你以为到这儿就结束了吗?高潮还没开始,下面我们来看看这种算法解决9、10、11…15皇后问题的性能

稍微变动一下代码,循环打印出各个解的结果,如下图所示:
这里写图片描述

当我开始尝试解决16皇后问题时,发现时间复杂度已经超出我的心里预期,最终没让它继续执行下去。

上网一查N皇后的国际记录,已经有科研单位给出了25皇后的计算结果,耗时暂未可知

我们的程序跑16皇后已经无能为力,跑15皇后已经捉襟见肘(87秒)

中国的一些算法高手能在100秒内跑16皇后,可见上面的算法效率只能说一般,辣么,该如何改进呢?

我们发现二维棋盘数据在内存中浪费严重,全是0和1的组成,同时每次递归时使用JAVA的clone函数克隆一个新的棋盘,消耗进一步扩大,这里面一定有改进的方案。

我们考虑将二维数组使用一维数组代替,将一维数组的下标数据利用起来,模仿棋盘结构,如chess[R]=C时,表示棋盘上R行C列有一个皇后

这样程序的空间效率会得到迅速提高,同时二维数据改变成一维数据后的遍历过程也会大为缩减,时间效率有所提高,下面贴出代码:

  1. import java.util.Date;
  2. public class EightQueen2 {
  3. private static final short K=15; //使用常量来定义,方便之后解N皇后问题
  4. private static int count=0; //结果计数器
  5. private static short N=0;
  6. public static void main(String[] args) {
  7. for(N=9;N<=K;N++){
  8. Date begin =new Date();
  9. /** * 初始化棋盘,使用一维数组存放棋盘信息 * chess[n]=X:表示第n行X列有一个皇后 */
  10. short chess[]=new short[N];
  11. for(int i=0;i<N;i++){
  12. chess[i]=0;
  13. }
  14. putQueenAtRow(chess,(short)0);
  15. Date end =new Date();
  16. System.out.println("解决 " +N+ "皇后问题,用时:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,计算结果:"+count);
  17. }
  18. }
  19. private static void putQueenAtRow(short[] chess, short row) {
  20. /** * 递归终止判断:如果row==N,则说明已经成功摆放了8个皇后 * 输出结果,终止递归 */
  21. if(row==N){
  22. count++;
  23. // System.out.println("第 "+ count +" 种解:");
  24. // for(int i=0;i<N;i++){
  25. // for(int j=0;j<N;j++){
  26. // System.out.print(chess[i][j]+" ");
  27. // }
  28. // System.out.println();
  29. // }
  30. return;
  31. }
  32. short[] chessTemp=chess.clone();
  33. /** * 向这一行的每一个位置尝试排放皇后 * 然后检测状态,如果安全则继续执行递归函数摆放下一行皇后 */
  34. for(short i=0;i<N;i++){
  35. //摆放这一行的皇后
  36. chessTemp[row]=i;
  37. if( isSafety( chessTemp,row,i ) ){
  38. putQueenAtRow(chessTemp,(short) (row+1));
  39. }
  40. }
  41. }
  42. private static boolean isSafety(short[] chess,short row,short col) {
  43. //判断中上、左上、右上是否安全
  44. short step=1;
  45. for(short i=(short) (row-1);i>=0;i--){
  46. if(chess[i]==col) //中上
  47. return false;
  48. if(chess[i]==col-step) //左上
  49. return false;
  50. if(chess[i]==col+step) //右上
  51. return false;
  52. step++;
  53. }
  54. return true;
  55. }
  56. }

运算结果:
这里写图片描述
可以看到所有结果的耗时缩短了一倍有余,这无疑是一次算法的进步

发表评论

表情:
评论列表 (有 0 条评论,235人围观)

还没有评论,来说两句吧...

相关阅读

    相关 解决八皇后回溯算法

    问题简述:八皇后问题是一个古来而著名的问题,该问题是19世纪著名的数学家高斯同学提出来的。在8\8的国际象棋上摆放八个皇后,使其不能互相的攻击,也就是说,任意的两个皇后不能放在

    相关 N皇后经典回溯算法

    N皇后题目: 给你一个N\N的棋盘,放置N个皇后,让皇后之间不攻击;攻击规则:皇后可攻击同行、同列、对角线上的所有皇后。 题目分析:如果没有对角线限制,那么这道题就是一个