递归思想与典型问题浅析

递归需要遵守的重要规则

1) 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2) 方法的局部变量是独立的,不会相互影响, 比如n变量
3) 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.
4) 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
5) 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

 

迷宫问题

说明:
1) 小球得到的路径,和程序员设置的找路策略有关即:找路的上下左右的顺序相关
2) 再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化
3) 测试回溯现象
4) 思考: 如何求出最短路径?

  思路:计算每种走的策略(初始方向、顺逆时针等)的数组中2的数量,最少的即为最短路径

其中一种策略的代码实现:

1. public class migong {  
2.     public static void main(String[] args) {  
3.         //定义一个二维数组表示所走的迷宫地图  
4.         int[][] map = new int[8][7];//默认初始值为0  
5.         //初始化迷宫:将迷宫外围建造围墙  
6.         for (int[] row : map) {  
7.             row[0] = 1;//每一行的第一列置为 1  
8.             row[6] = 1;//每一行的第最后一列置为 1  
9.         }  
10.         for (int i = 0; i < map[0].length; i++) {  
11.             map[0][i] = 1;//第一行的每一列置为 1  
12.             map[map.length - 1][i] = 1;//最后一行的每一列置为 1  
13.         }  
14.         //设置中间的挡板  
15.         map[3][1] = 1;  
16.         map[3][2] = 1;  
17.         map[2][2] = 1;  
18.   
19.         //遍历初始迷宫  
20.         for (int[] row : map) {  
21.             for (int data : row) {  
22.                 System.out.print(data + "  ");  
23.             }  
24.             System.out.println();  
25.         }  
26.         System.out.println("************************");  
27.           
28.         //通过递归来找路  
29.         setWay(map, 1, 1, 1, 5);  
30.           
31.         //遍历所走的地图  
32.         for (int[] row : map) {  
33.             for (int data : row) {  
34.                 System.out.print(data + "  ");  
35.             }  
36.             System.out.println();  
37.         }  
38.     }  
39.   
40.     /** 
41.      * @param map 迷宫图 
42.      * @param i   入口的行坐标 
43.      * @param j   入口的列坐标 
44.      * @param a   出口的行坐标 
45.      * @param b   出口的列坐标 
46.      * @return 是否找到通路,true:找到  false:没找到 
47.      * 说明: 
48.      * 1. map 表示地图 
49.      * 2. i , j  表示从地图的哪个位置开始出发 (1,1) 
50.      * 3. 如果小球能到 map[a][b]位置,则说明通路找到. 
51.      * 4. 约定: 当 map[i][j]为 0:表示该点没有走过, 
52.      *    当为1:表示墙,当为2:表示通路可以走,当为3:表示该点已经走过,但是走不通 
53.      * 5.在走迷宫时,需要确定一个策略(方法):下->右->上->左 , 如果该点走不通,再回溯 
54.      */  
55.     private static boolean setWay(int[][] map, int i, int j, int a, int b) {  
56.         if (map[a][b] == 2) {//说明走到了出口  
57.             return true;  
58.         } else {  
59.             if (map[i][j] == 0) {//如果当前这个点还没有走过  
60.                 //按照策略 下->右->上->左  走  
61.                 map[i][j] = 2; // 假定该点是可以走通.  
62.   
63.                 if (setWay(map, i + 1, j, a, b)) {//向下走  
64.                     return true;  
65.                 } else if (setWay(map, i, j + 1, a, b)) {//向右走  
66.                     return true;  
67.                 } else if (setWay(map, i - 1, j, a, b)) {//向上走  
68.                     return true;  
69.                 } else if (setWay(map, i, j - 1, a, b)) {//向左走  
70.                     return true;  
71.                 } else {//说明为死路,将此格置为3,并回溯  
72.                     map[i][j] = 3;//将2改为3,说明此格为死路  
73.                     return false;  
74.                 }  
75.             } else {//说明此时值为 1 / 2 / 3  
76.                 return false;  
77.             }  
78.         }  
79.     }  
80. }   

 

回溯算法解决:八皇后问题

八皇后问题介绍

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、 同一列或同一斜线上,问有多少种摆法(答案是92种)。

八皇后问题算法思路分析

1) 第一个皇后先放第一行第一列
2) 第二个皇后放在第二行第一列、然后判断是否 OK, 如果不 OK,继续放在第二列、第三列、依次把所有列都 放完,找到一个合适
3) 继续第三个皇后,还是第一列、第二列……直到第 8 个皇后也能放在一个不冲突的位置,算是找到了一个正确 解
4) 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解, 全部得到.
5) 然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4 的步骤

  • 说明:

理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题.
例如:arr[8] ={0 , 4, 7, 5, 2, 6, 1, 3} //对应 arr 下标表示第几行,即第几个皇后,arr[i] = val , val 表示第 i+1 个皇后,放在第 i+1行的第 val+1 列。
(每个棋子在放的时候,约定放的第n个棋子就仅仅在第n行找位置满足要求)

如何解决八皇后问题?

所谓递归回溯,本质上是一种枚举法。这种方法从棋盘的第一行开始尝试摆放第一个皇后,摆放成功后,递归一层,再遵循规则在棋盘第二行来摆放第二个皇后。如果当前位置无法摆放,则向右移动一格再次尝试,如果摆放成功,则继续递归一层,摆放第三个皇后......
如果某一层看遍了所有格子,都无法成功摆放,则回溯到上一个皇后,让上一个皇后右移一格,再进行递归。如果八个皇后都摆放完毕且符合规则,那么就得到了其中一种正确的解法。说起来有些抽象,我们来看一看递归回溯的详细过程。
1.第一层递归,尝试在第一行摆放第一个皇后

2.第二层递归,尝试在第二行摆放第二个皇后(前两格被第一个皇后封锁,只能落在第三格):

3.第三层递归,尝试在第三行摆放第三个皇后(前四格被第一第二个皇后封锁,只能落在第五格):

4.第四层递归,尝试在第四行摆放第四个皇后(第一格被第二个皇后封锁,只能落在第二格):

5.第五层递归,尝试在第五行摆放第五个皇后(前三格被前面的皇后封锁,只能落在第四格)

 

6.由于所有格子都“绿了”,第六行已经没办法摆放皇后,于是进行回溯重新摆放第五个皇后到第八格。:

7.第六行仍然没有办法摆放皇后,第五行也已经尝试遍了,于是回溯到第四行,重新摆放第四个皇后到第七格

8.继续摆放第五个皇后,以此类推......

 代码实现

1. public class queen8 {  
2.     public static final int MAX = 8;//定义一个 MAX 表示共有多少个皇后  
3.     static int[] arr = new int[MAX];//定义数组 array, 保存皇后放置位置的结果  
4.     static int count;//用于记录满足条件的数组个数(满足条件的摆法数)  
5.   
6.     public static void main(String[] args) {  
7.         long start = System.currentTimeMillis();  
8.         queen8 queen8 = new queen8();  
9.         queen8.check(0);  
10.         System.out.printf("一共有%d种方案", count);  
11.         long stop = System.currentTimeMillis();  
12.         System.out.println(stop-start);  
13.   
14.     }  
15.   
16.     //编写一个方法,放置第 n 个皇后  
17.     //特别注意: check 是每一次递归时,进入到 check 中都有for(int i = 0; i < max; i++),因此会有回溯  
18.     private void check(int n) {  
19.         if (n == MAX) { //n = 8 , 说明8个皇后就已经满足摆放条件(摆放好了0到7)  
20.             print();  
21.             return;  
22.         }  
23.         for (int i = 0; i < MAX; i++) {  
24.             arr[n] = i;//把这个皇后放在第n行的第i列  
25.             //判断当放置到第n行的第i列时是否有冲突  
26.             if (judge(n)) { // 不冲突则接着放 n+1个皇后,即开始递归  
27.                 check(n + 1);  
28.              
29.             //如果冲突,就继续执行 for循环:array[n] = i;  即:将第 n 个皇后,放置在本行的后移的一个位置  
30.         }  
31.   
32.     }  
33.   
34.     /** 
35.      * 查看当我们放置第 n 个皇后, 就去检测该皇后是否和前面( 1到n-1 )已经摆放的皇后冲突 
36.      * 
37.      * @param n 
38.      * @return true:没有冲突 false:有冲突 
39.      */  
40.     private boolean judge(int n) {  
41.         for (int i = 0; i < n; i++) {  
42.             //1. array[i] == array[n]表示判断第n个皇后是否和前面的 n-1 个皇后在同一列  
43.             //2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判断第 n 个皇后是否和第 i 皇后是否在同一斜线  
44.             //   (其实就是判断数学中两点构成的线的斜率是否为1:abs(x1-x2)==abs(y1-y2))  
45.             if (arr[n] == arr[i] || Math.abs((n - i)) == Math.abs(arr[n] - arr[i])) {  
46.                 return false;  
47.             }  
48.         }  
49.         return true;  
50.     }  
51.   
52.     //负责打印输出数组  
53.     public static void print() {  
54.         count++;  
55.         for (int data : arr) {  
56.             System.out.print(data + "  ");  
57.         }  
58.         System.out.println();  
59.     }  
60. }  

 

posted @ 2020-11-02 14:18  白刃天使  阅读(211)  评论(0编辑  收藏  举报