java递归求八皇后问题解法

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

java递归求⼋皇后问题解法
⼋皇后问题
⼋皇后问题,是⼀个古⽼⽽著名的问题,是回溯算法的典型案例。

该问题是国际西洋棋棋⼿马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放⼋个皇后,使其不能互相攻击,即任意两个皇后都不能处于同⼀⾏、同⼀列或同⼀斜线上,问有多少种摆法。

⾼斯认为有76种⽅案。

1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有⼈⽤图论的⽅法解出92种结果。

⽹上有很多⼋皇后的⼩游戏,不清楚规则的可以体验⼀把。

递归理解
由于我们使⽤经典的递归回溯算法,所以要先理解递归的调⽤过程,在使⽤递归前我们先看下普通⽅法的调⽤过程在JVM中如何体现。

⾸先我们来看下jvm中五个重要的空间,如下图所⽰
这⾥我们主要关注栈区,当调⽤某个⽅法时会在栈区为每个线程分配独⽴的栈空间,⽽每个⽅法都会以栈帧的形式压⼊栈中,即每个⽅法从进⼊到退出都对应着⼀个栈帧的压栈和出栈。

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

如下图所⽰
理解了jvm程序栈的结构后,下⾯我们以图解的⽅式先讲解⼀下普通⽅法(理解普通⽅法调⽤过程后,再讲解递归的调⽤过程)的调⽤过程。

假设程序main⽅法⾸先调⽤了method1,在method1中调⽤了method2,在method2中调⽤method3。

代码如下
1public static void main(String []args){
2 method1();
3 }
5private static void method1(){
6 System.out.println("method1调⽤开始");
7 method2();
8 System.out.println("method1调⽤结束");
9 }
10
11private static void method2(){
12 System.out.println("method2调⽤开始");
13 method3();
14 System.out.println("method2调⽤结束");
15 }
16private 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的阶乘的递归⽅法为例讲解调⽤过程,代码如下
1public static void main(String []args){
2 System.out.println(fn(5));
3 }
4
5private static int fn(int n){
6if(n == 1){
7return 1;
8 }
9return 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*/
4public class BaHuangHou {
5private final static int max = 8;
6private static int array[] = new int[max];
7private static int count = 0;
8public 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*/
20private static void check(int n){
21if(n == max){
22 print(array);
23return;
24 }
25for(int i=0; i<max; i++){
26 array[n] = i;
27if(judge(n)){
28 check(n+1);
29 }
30 }
31 }
32/**
33 * 判断第n个皇后是否与之前的冲突
34 * @param n
35 * @return
36*/
37private static boolean judge(int n){
38for(int i=0; i<n; i++){
39if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
40return false;
41 }
42 }
43return true;
44 }
45
46/**
47 * 打印数组值
48 * @param array
49*/
50public static void print(int array[]){
51for (int i = 0; i <max; 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⽅法:
1private static void check(int n){
2if(n == max){
3 print(array);
4return;
5 }
6for(int i=0; i<max; i++){
7 array[n] = i;
8if(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的⽅法,依此类推,知道所有的⼋皇后解法全部求
出。

总结
好了,到这⾥不知道⼤家是否理解了使⽤递归回溯法求⼋皇后解法的问题?如有疑问的地⽅,可以在留⾔区评论提问。

相关文档
最新文档