热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java递归求八皇后问题解法

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

八皇后问题

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

网上有很多八皇后的小游戏,不清楚规则的可以点击这里体验一把。

 

递归理解

由于我们使用经典的递归回溯算法,所以要先理解递归的调用过程,在使用递归前我们先看下普通方法的调用过程在JVM中如何体现。首先我们来看下jvm中五个重要的空间,如下图所示

技术分享图片

这里我们主要关注栈区,当调用某个方法时会在栈区为每个线程分配独立的栈空间,而每个方法都会以栈帧的形式压入栈中,即每个方法从进入到退出都对应着一个栈帧的压栈和出栈。如下图所示

技术分享图片

 

在每个栈帧中都会有方法独立的局部变量表,操作数栈,动态连接,返回地址等信息。如下图所示

技术分享图片

 

理解了jvm程序栈的结构后,下面我们以图解的方式先讲解一下普通方法(理解普通方法调用过程后,再讲解递归的调用过程)的调用过程。

假设程序main方法首先调用了method1,在method1中调用了method2,在method2中调用method3。代码如下

1 public static void main(String []args){
2 method1();
3 }
4
5 private static void method1(){
6 System.out.println("method1调用开始");
7 method2();
8 System.out.println("method1调用结束");
9 }
10
11 private static void method2(){
12 System.out.println("method2调用开始");
13 method3();
14 System.out.println("method2调用结束");
15 }
16 private static void method3(){
17 System.out.println("method3调用开始");
18 System.out.println("method3调用结束");
19 }

  当执行main方法时,会执行以下步骤

  1)首先将main方法压入栈中,在main方法中调用method1,方法mehod1会压入栈中,并执行打印“method1调用开始”

  2)执行到第7行时,将method2压入栈中,执行method2方法的代码打印出“method2调用开始”

  3)执行到第13行时调用method3方法,将method3方法压入栈中,执行method3方法打印“method3调用开始”,方法压入栈中的过程图解,如下图所示

 

 技术分享图片 

  当执行到图4中的method3方法打印出“method3调用开始”后会执行以下步骤

  1)method3执行打印“method3调用结束”后method3方法体已全部执行完毕,method3方法会出栈,并根据栈帧中程序计数器记录的调用者调用本方法的所在行返回。即返回到method2的13行

  2)执行method2第14行,打印出“method2调用结束”。method2方法体执行完毕,method2方法出栈,返回到method1的第7行

  3)执行method1第8行,打印出method1调用结束。method1方法出栈,返回到main方法中第2行,main方法执行完毕,main方法出栈,整个程序运行结束

  对应图解如下

技术分享图片

 

  根据上面的流程可知程序的运行结果为:

  method1调用开始

  method2调用开始

  method3调用开始

  method3调用结束

  method2调用结束

  method1调用结束

 

 理解了普通方法的调用过程后,下面我们来讲解递归方法的调用过程,我们都知道递归调用就是方法调用自己,当然我们也可以套用上面普通方法的流程,主观认为它是调用别的方法。

 下面以一个求n的阶乘的递归方法为例讲解调用过程,代码如下

1 public static void main(String []args){
2 System.out.println(fn(5));
3 }
4
5 private static int fn(int n){
6 if(n == 1){
7 return 1;
8 }
9 return fn(n-1)*n;
10 }

 下面还以图解的方式讲解递归的执行过程,为了好区分每次递归的过程,我们以传入的参数标示fn方法,如n=5时,我们假定调用fn5方法。调用过程如下图所示

技术分享图片

 

  方法的调用扔以压栈的方式进行,调用fn(5)时,fn5压栈,而求fn(5)需要先调用fn(4),从而fn4压栈,依此类推,直到fn(1)方法压栈,此时if(n==1)条件成立,fn(1)方法返回。如下图 

技术分享图片技术分享图片技术分享图片技术分享图片

        图10                    图11               图12                  图13

 技术分享图片

        图14

  执行到图14后,递归的执行过程结束,并将结果5*4*3*2*1的结果返回给main方法并输出,结果为120。

  以上就是递归的执行过程分析,其实跟普通方法的调用过程一样,只不过递归调用的方法是自己而已。

  好了,终于到了本文的重点了(铺垫做的太多),递归回溯法求八皇后解法问题

八皇后问题解法


  问题分析

  1)用代码求解八皇后问题的前提,我们要先构造出来一个8*8的二维数组,但由于八皇后问题的条件限制----任意两个皇后不能同行,所以我们可使用一个8位一维数组表示棋盘,一维数组的第n个元素即代表第n-1(从第0行开始)行,第n个元素的值即代表第n行的列值,如:0 4 7 5 2 6 1 3 ,其中0表示第0行第0列,4表示第2行第5列,7表示第3行第8列,以此类推。

  2)我们在求解的过程中,每添加一个皇后,行数加1,所以不会出现任意两个皇后处在同一行的情况,所以我们只需判断任意两个皇后不在同一列,也不在同一斜线上即可。

  3)从第0行第0列开始放第一个皇后,依此循环8个皇后,并在下一行判断,只要不跟前面所有皇后在同一列或同一斜线上即可放置皇后。

  代码实现


1 /**
2 * 递归法解决八皇后问题
3 */
4 public class BaHuangHou {
5 private final static int max = 8;
6 private static int array[] = new int[max];
7 private static int count = 0;
8 public static void main(String []args){
9 //定义一个一位数组表示八皇后的棋盘(第n个代表第n行,值代表第n行的第m列)
10
11 check(0);
12 System.out.printf("总共有%d种解法\n",count);
13 }
14
15 /**
16 * 放置第n个皇后
17 * @param n
18 * @return
19 */
20 private static void check(int n){
21 if(n == max){
22 print(array);
23 return;
24 }
25 for(int i=0; i){
26 array[n] = i;
27 if(judge(n)){
28 check(n+1);
29 }
30 }
31 }
32 /**
33 * 判断第n个皇后是否与之前的冲突
34 * @param n
35 * @return
36 */
37 private static boolean judge(int n){
38 for(int i=0; i){
39 if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
40 return false;
41 }
42 }
43 return true;
44 }
45
46 /**
47 * 打印数组值
48 * @param array
49 */
50 public static void print(int array[]){
51 for (int i = 0; i ) {
52 System.out.print(array[i]+" ");
53 }
54 count ++ ;
55 System.out.println();
56
57 }
58 }


  代码分析

  首先我们定义了一个8个元素的一维数组 array ,用来表示一个8*8的棋盘。

  1)先来看下判断皇后是否与前面冲突(即在同一列或同一斜线)的judge方法:

    if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]))

    第一个条件array[i] == array[n],因一维数组的值即代表所在行的所在列值,所以如果值相同,则代表在同一列。

    第二个条件Math.abs(n-i) == Math.abs(array[n] - array[i]),n-i表示两个皇后相差几行,array[n]-array[i]表示相差几列,如果相差行等于相差,则这两个皇后能构成一个正方形,即在同一斜线上

  2)在来看执行判断过程的check方法:

1 private static void check(int n){
2 if(n == max){
3 print(array);
4 return;
5 }
6 for(int i=0; i){
7 array[n] = i;
8 if(judge(n)){
9 check(n+1);
10 }
11 }
12 }

  第2行的if()条件判断,用于表示一次求解过程的结束。当n==max即n=8时,即表示前面已经放置了8个皇后(n从0开始)。

  第6行的for循环,表示从第0行的第0列开始放第一个皇后,一直到第0行的第7列遍历出所有第0行的皇后摆放方法。同理,执行到n=1时,表示放置第二个皇后,即第2行的摆放方法,只要第二行不跟第一行冲突,就在第三行放置第3个皇后,以此类推直到放置第7行的第八个皇后。如果在某行遍历完所在行的所有列,均与前面的皇后冲突,说明前面的摆放不能求解出一个八皇后解法,此时该行的循环执行结束,该行所在的方法出栈,回溯到前面一行的方法执行。前面一行继续执行for循环的i++,当i++后即该行皇后向后一个位置移动,如果不跟前面的所有皇后冲突,则再进入下一行的下一个皇后从第0列开始摆放,依此类推。

  当得到一个正确解法后,n=8所在方法出栈(参考前面讲解的递归方法入栈出栈),执行n=7(第8个皇后)所在方法的for循环,继续执行i++,查看最后一行的皇后后面列是否还有正确解法,如果有则输出,如果没有则该行所在方法出栈,进而执行n=6(第7个皇后)所在方法的for循环,继续执行i++。依此类推

  用文字描述稍微有点抽象,不过如果理解了我们前面讲解的递归方法的执行过程,理解起来还是比较容易的。这里使用了for循环求解八皇后的所有解法,所以相对会难以理解。

 图解的方式理解八皇后解法

  在main方法中调用check(0)后,n=0的check方法入栈,并执行for循环的i=0,array[0]=0,即第一个皇后摆放在第0行第0列,此时程序栈和棋盘情况如下图所示  

  技术分享图片

 

  由于这时是第一个皇后,所以肯定没有冲突,但要记住n=0时的check方法的for循环只进行到i=0,便调用check(1),调用下一个皇后的摆放判断,此时程序栈和棋盘情况如下图所示

 

  技术分享图片

 

  当n=1的check方法入栈后,执行for循环方法,由于i=0和i=1均会与第一个皇后冲突,所以这两个位置不能摆放,我们用虚线圆表示不能摆放,用实线圆形表示可以摆放。此时n=1的check方法的for循环执行到i=2。第二个皇后摆放后,会调用check(2),则n=2的check方法入栈,此时程序栈和棋盘情况如下图所示

  技术分享图片

 

  第n=2的check方法入栈后,执行for循环方法,在i<4之前的所有位置均会与前面两个皇后冲突,所以只能放在i=4的位置。此时n=2的check方法的for循环执行到i=4。调用check(3),则n=3的check方法入栈,此时程序栈和期盼情况如下图所示

  技术分享图片

 

  n=3的所在行摆放皇后之后,调用check(4)的方法,此时n=2的check方法的for循环执行到i=4。

  依此类推,直到执行到n=5时,for循环执行完所有遍历,发现均与前面的皇后冲突,如下图所示

  技术分享图片

 

  当n=5的for循环执行完后,check(5)方法出栈,回溯到check(4)的方法继续执行for循环,前面我们知道check(4)的for循环i执行到i=3,所以从i=3继续执行i++,如下图所示

  技术分享图片

 

  可以看出n=4的check方法执行到i=7时,才能满足不与前面的皇后冲突,这时会继续调用check(5)方法,即n=5的check方法再次入栈,如下图所示

  技术分享图片

 

  可以看出n=5时,所在行的所有列均无法摆放皇后,所示n=5的check方法再次出栈,而n=4的check方法的for循环也执行到i=7,所以check(4)方法也会出栈,进而执行n=3的for循环,而我们之前记录可以看到n=3的for循环执行到i=1,所以继续执行i++,并依此判断是否与前面的皇后冲突。

  从上面的过程我们可以看到,当栈顶方法所在行的所有列均不能摆放皇后时,会回溯到前面的行执行。

  下面我们在用一个摆放成功的案例来讲解回溯过程,例如,0 4 7 5 2 6 1 3  即(0,0) (1,4) (2,7) (3,5) (4,2) (5,6) (7,1) (8,3)的摆法,此时程序栈和棋盘如下图所示

  技术分享图片

 

  可以看到,n=7时第八个皇后摆放成功,会调用check(8),进而满足if(n==8)条件,所以check(8)方法出栈,继续执行n=7的check方法,而此时n=7的for循环i=3,继续执行i++,看n=7的所在行的后面的列是否还有能摆放成功的。如果没有则n=7的check方法执行完毕,回溯到n=6的方法,依此类推,知道所有的八皇后解法全部求出。

 

总结

  好了,到这里不知道大家是否理解了使用递归回溯法求八皇后解法的问题?如有疑问的地方,可以在留言区评论提问。

 


推荐阅读
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 本文详细解析了JavaScript中相称性推断的知识点,包括严厉相称和宽松相称的区别,以及范例转换的规则。针对不同类型的范例值,如差别范例值、统一类的原始范例值和统一类的复合范例值,都给出了具体的比较方法。对于宽松相称的情况,也解释了原始范例值和对象之间的比较规则。通过本文的学习,读者可以更好地理解JavaScript中相称性推断的概念和应用。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
author-avatar
闺蜜好我会明白
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有