递归思想与典型问题浅析
递归需要遵守的重要规则
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. }