八皇后问题递归求解

算法思路分析

  1. 第一个皇后先放在第一行第一列
  2. 第二个皇后放在第二行第一列,判断与之前的皇后位置是否冲突,如果冲突,则依次再将其放在第二列,第三列...直至找到一个合适的位置为止
  3. 继续放第三个皇后,同第二步一样,依次将其放在第三行的第一列,第二列......一直到第八个皇后也能放在一个不冲突的位置,则算找到一个正确的解,但如果在第八行找不到合适的位置,则会回溯至第七行,将第七行皇后的位置继续后移,找到第七行中与前面的皇后不冲突的位置将其放置,然后再放第八行。如果第七行不行则回溯至第六行......直至回溯至第二行。
  4. 当得到一个正确解的时候,在栈退回到上一个栈时,就会开始回溯,即将第一个皇后放在第一行的第一列的所有正确解全部得到。
  5. 将第一个皇后放在第二列,上述操作循环进行,得到所有的正确解。
 
说明:理论上应该创建一个二维数组表示棋盘,但是实际上可以通过算法使用一个一维数组就能解决问题arr[i]=val,arr的索引i表示第第i+1行,第i+1个元素放在棋盘的val+1列
 
下面直接上代码,具体内容在代码中解析:
 

 首先,定义的EightQueens类拥有如下属性:

1     //定义一个max表示共有多少皇后
2     int max = 8;
3 
4     //定义数组array,保存正解的结果
5     int[] array = new int[max];
6 
7     //计数
8     static int count = 0;

 

 

  定义一个输出方法,能够把运算结果输出,输出格式为array = {p1, p2, p3, p4, p5, p6, p7, p8},p1-p8分别代表八个皇后在棋盘中八行的列数-1

1     //方法:将皇后摆放的位置输出
2     private void print(){
3         count++;
4         for (int j : array) {
5             System.out.print(j + "\t");
6         }
7         System.out.println();
8     }

 

  这里同样是很重要的方法,也是整个算法中会被调用次数最多的方法,会被调用超过15000+次。该方法主要通过一个循环内嵌if表达式来判定该行的皇后位置是否与之前行冲突

 1     //方法:判断放置第n个皇后时,检测该皇后的位置是否与前面的皇后位置冲突
 2     private boolean isValid(int n){
 3         for (int i = 0; i < n; i++) {
 4             //判定条件:
 5             // array[i] == array[n]: 两皇后不能位于同一列
 6             // Math.abs(n - i) == Math.abs(array[n] - array[i]) 两皇后不能位于同一斜线(行差不能等于列差,若相等则必定处于同一斜线上)
 7             if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
 8                 return false;
 9             }
10         }
11         return true;
12     }

  该if语句主要判定的内容是该皇后当前所在列是否有其他皇后,并且该皇后所在的对角线是否有其他皇后。

  该方法为主方法

 1     //方法:放置第n个皇后
 2     //****注意:setQueen()方法在每一次递归时,都会进入到该层中的for循环,因此会产生回溯
 3     private void setQueen(int n){
 4         //若n==8,则会再次进入循环放入第九个皇后,因此需要在循环内返回
 5         if (n == max) {
 6             print();
 7             return;
 8         }
 9         //依次放入皇后,判断是否冲突
10         for (int i = 0; i < max; i++) {
11             //当前皇后从第一列(i=0)开始依次放置
12             array[n] = i;
13             //判断第n个皇后放置在i列时候是否冲突
14             if (isValid(n)) {   //不冲突
15                 //放置第n+1个皇后,即开始递归
16                 setQueen(n+1);
17             }
18             //① 若位置冲突,则会isValid()方法会会返回false,方法回溯至for循环,i会再加一,即放置在当前的后一个位置,继续进入判断。
19         }
20     }

  其中递归部分比较难理解是如何实现回溯的,我们以n = 0输入,表示第一个皇后开始落位,因为是循环结构,n的数值会不断递增,因此在首层将会判断此皇后是否超过指定数值,若超过将在if语句中判定为true,从而进入if语句,打印并结束递归。

  如果没有超过指定值,那么向下进行,先通过for循环将i值赋给保存结果的数组,如果该方法能够得到验证并通过下面的isValid方法进入递归,那么这个i值就会被保存。如果不能,那么这个i值在if判断时不会进入语句中,从而退出循环返回至for循环i++,直到有一个合适的位置能让其进入if语句中。

  这里需要注意的是,如果这一层递归的for循环中没有任何的值能够满足规则,那么会在①位置向上一层回溯,在上一层i的位置继续加一计算。意思就是说,这一层的i虽然能满足判断要求,但是只是针对本层而言,基于这个i的下一层i没有办法满足要求,因而这个i不能作为正确结果输出,所以需要向后继续寻找合适的位置。

  如果全部8层都找到了正确的位置,将会直接进入方法头部的if语句打印并输出,但是这个if是最后一层皇后调用的,并不代表全部的方法结束了。这里的return只是让这一层递归结束开始归回,他会返回上一层继续寻找下一个值,直至8行8列全部扫描完毕,由第八列第一行的皇后调用return最后结束方法。

  以下用一个简略的图示表达了这种思想

  由第一行第一列的皇后进入方法内部,判断是否达到最大值,是否与前面位置冲突,若这两个要求都满足,则进入下一层递归

 

 

   由于在第二层时与上一层的皇后在同一行,因而这一层的皇后必须更改位置,向右移一行,还是冲突,向右移两行,此时的位置才完全合适,有条件进入下一层

 

   进入这层循环,和上层相似,同样是在判断冲突条件上失败无法进入下一层。在这里为了演示回溯,在这里假设这一层在循环一遍后没有合适的位置,需要向上层返回

 

 

 

  回到第二层,由于下一层反馈需要更改当前的位置,所以第二层皇后不得不向后再移动一位

 

  第三层皇后得以顺利插入

 

 

 

 

posted @ 2021-03-23 23:26  Carln  阅读(172)  评论(0编辑  收藏  举报