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

算法思考,初步思路:

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

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

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

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

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

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

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

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

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

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

import java.util.Date;

/**
* 在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,
* 即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
* 下面使用递归方法解决
* @author newflydd@189.cn
*
*/
public class EightQueen {
private static final short N=8; //使用常量来定义,方便之后解N皇后问题
private static int count=0; //结果计数器 public static void main(String[] args) {
Date begin =new Date();
//初始化棋盘,全部置0
short chess[][]=new short[N][N];
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
chess[i][j]=0;
}
} putQueenAtRow(chess,0);
Date end =new Date();
System.out.println("解决 " +N+ " 皇后问题,用时:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,计算结果:"+count);
} private static void putQueenAtRow(short[][] chess, int row) {
/**
* 递归终止判断:如果row==N,则说明已经成功摆放了8个皇后
* 输出结果,终止递归
*/
if(row==N){
count++;
System.out.println("第 "+ count +" 种解:");
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
System.out.print(chess[i][j]+" ");
}
System.out.println();
}
return;
} short[][] chessTemp=chess.clone(); /**
* 向这一行的每一个位置尝试排放皇后
* 然后检测状态,如果安全则继续执行递归函数摆放下一行皇后
*/
for(int i=0;i<N;i++){
//摆放这一行的皇后,之前要清掉所有这一行摆放的记录,防止污染棋盘
for(int j=0;j<N;j++)
chessTemp[row][j]=0;
chessTemp[row][i]=1; if( isSafety( chessTemp,row,i ) ){
putQueenAtRow(chessTemp,row+1);
}
}
} private static boolean isSafety(short[][] chess,int row,int col) {
//判断中上、左上、右上是否安全
int step=1;
while(row-step>=0){
if(chess[row-step][col]==1) //中上
return false;
if(col-step>=0 && chess[row-step][col-step]==1) //左上
return false;
if(col+step<N && chess[row-step][col+step]==1) //右上
return false; step++;
}
return true;
}
}

输出结果:

需要打印棋盘时,耗时34毫秒,再看一看不需要打印棋盘时的性能:

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

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

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

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

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

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

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

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

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

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

import java.util.Date;

public class EightQueen2 {
private static final short K=15; //使用常量来定义,方便之后解N皇后问题
private static int count=0; //结果计数器
private static short N=0; public static void main(String[] args) {
for(N=9;N<=K;N++){
Date begin =new Date();
/**
* 初始化棋盘,使用一维数组存放棋盘信息
* chess[n]=X:表示第n行X列有一个皇后
*/
short chess[]=new short[N];
for(int i=0;i<N;i++){
chess[i]=0;
} putQueenAtRow(chess,(short)0);
Date end =new Date();
System.out.println("解决 " +N+ "皇后问题,用时:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,计算结果:"+count);
}
} private static void putQueenAtRow(short[] chess, short row) {
/**
* 递归终止判断:如果row==N,则说明已经成功摆放了8个皇后
* 输出结果,终止递归
*/
if(row==N){
count++;
// System.out.println("第 "+ count +" 种解:");
// for(int i=0;i<N;i++){
// for(int j=0;j<N;j++){
// System.out.print(chess[i][j]+" ");
// }
// System.out.println();
// }
return;
} short[] chessTemp=chess.clone(); /**
* 向这一行的每一个位置尝试排放皇后
* 然后检测状态,如果安全则继续执行递归函数摆放下一行皇后
*/
for(short i=0;i<N;i++){
//摆放这一行的皇后
chessTemp[row]=i; if( isSafety( chessTemp,row,i ) ){
putQueenAtRow(chessTemp,(short) (row+1));
}
}
} private static boolean isSafety(short[] chess,short row,short col) {
//判断中上、左上、右上是否安全
short step=1;
for(short i=(short) (row-1);i>=0;i--){
if(chess[i]==col) //中上
return false;
if(chess[i]==col-step) //左上
return false;
if(chess[i]==col+step) //右上
return false; step++;
} return true;
}
}

运算结果:

可以看到所有结果的耗时缩短了一倍有余,这无疑是一次算法的进步

辣么,还有改进空间吗?

答案必然是肯定的,对于算法,我们越是精益求精,我们的能力就越强大,我们越是浅尝辄止,我们的进步就越慢。

下一篇博客我们来继续改进这个问题的算法,摒弃编译器自带的递归回溯,自己手写回溯过程,相信效率会进一步提高,最终在可控范围内将16皇后问题解出来。

8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案的更多相关文章

  1. LeetCode通关:连刷十四题,回溯算法完全攻略

    刷题路线:https://github.com/youngyangyang04/leetcode-master 大家好,我是被算法题虐到泪流满面的老三,只能靠发发文章给自己打气! 这一节,我们来看看回 ...

  2. A*寻路算法探究

    A*寻路算法探究 A*算法常用在游戏的寻路,是一种静态网路中求解最短路径的搜索方法,也是解决很多搜索问题的算法.相对于Dijkstra,BFS这些算法在复杂的搜索更有效率.本文在U3D中进行代码的测试 ...

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

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

  4. 回溯算法之n皇后问题

    今天在看深度优先算法的时候,联想到DFS本质不就是一个递归回溯算法问题,只不过它是应用在图论上的.OK,写下这篇博文也是为了回顾一下回溯算法设计吧. 学习回溯算法问题,最为经典的问题我想应该就是八皇后 ...

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

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

  6. C语言回溯算法解决N皇后问题

    回溯算法的模型是 x++, not satisfy ? x-- : continue. 代码中x作列号,y[x]保存第x列上皇后放置的位置. #include<stdio.h> #incl ...

  7. 回溯算法 LEETCODE别人的小结 一八皇后问题

    回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试中寻找问题的解,当发现已不满足求解条件时,就回溯返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目的.但是当探索到某 ...

  8. 回溯算法——解决n皇后问题

    所谓回溯(backtracking)是通过系统地搜索求解问题的方法.这种方法适用于类似于八皇后这样的问题:求得问题的一个解比较困难,但是检查一个棋局是否构成解很容易. 不多说,放上n皇后的回溯问题代码 ...

  9. 8皇后问题SQL求解(回溯算法)

    问题 八皇后问题是一个古老而著名的问题,是回溯算法的典型例题.该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一 ...

随机推荐

  1. DSP中常用的C语言关键字

    const Ø使用:const 数据类型 变量名: Ø作用:优化存储器的分配,表示变量的内容是常数,不会改变. Ø举例:const char tab[1024]={显示数据}; volatile(易变 ...

  2. 使用fwrite()函数和fprintf()函数输出数据到文件时的区别

    使用书上的一个课后题为例 有5个学生,每个学生有3门课的成绩,从键盘输入学生数据(包括学号,姓名,3们课程成绩),计算出每个学生的平均成绩,将原有数据和计算出的平均分数存放在磁盘文件“stud”中. ...

  3. Base64编解码Android和ios的例子,补充JNI中的例子

    1.在Android中java层提供了工具类:android.util.Base64; 里面都是静态方法,方便直接使用: 使用方法如下: // Base64 编码: byte [] encode =  ...

  4. hadoop安装问题

    1. 运行start-dfs.sh启动HDFS守护进程,start-yarn.sh面向YARN的资源器和节点管理器,资源管理器web地址是http://localhost:8080/.输入stop.d ...

  5. javascript和swf在网页中交互的一些总结

    Javascript和swf在网页中交互一般可有以下几种情况: 1.swf和这些调用的javascript在同域 2.swf和这些调用的javascript在不同域,比如加载远程的swf然后call别 ...

  6. sqlite3中的数据类型

    大多数的数据库引擎(到现在据我们所知的除了sqlite的每个sql数据库引擎)都使用静态的.刚性的类型,使用静态类型,数据的类型就由它的容器决定,这个容器是这个指被存放的特定列. Sqlite使用一个 ...

  7. 【mysql5.6】SQL基础

    我买了本深入浅出MySQL, 记录一下笔记. 一.数据定义语言(DDL) 1.创建数据库  create database name; 2.显示所有的数据库  show databases; 3.选择 ...

  8. 高质量图形库:pixellib

    点这里 pixellib 是高质量 2D 图形库: 高质量抗锯齿,矢量图形绘制 多种图像格式: RGB, BGR, ARGB, ABGR, RGBA, BGRA 8 / 15 / 16 / 24 / ...

  9. hadoop,spark,linux上常用命令

    记下常用命令,慢慢补充 1.hadoop 查看hdfs上的目录: hadoop fs -ls /给hdfs上目录授予权限:   hadoop fs -chmod 777 /tmp/hive 在hdfs ...

  10. MySQL: InnoDB 还是 MyISAM? (转载)

    MyISAM存储引擎 原文作者:http://www.cnblogs.com/villion/archive/2009/07/09/1893762.html MyISAM是 默认存储引擎.它基于更老的 ...