尚硅谷图解Java数据结构和算法.pdf

1、概念

递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。并且递归用到了虚拟机栈
四、递归 - 图1
image.png

2、能解决的问题

数学问题

  • 八皇后问题
  • 汉诺塔
  • 求阶乘
  • 迷宫问题
  • 球和篮子

各种排序算法
image.png

3、规则

  • 方法的变量是独立的,不会相互影响的
  • 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
  • 递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError
  • 当一个方法执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

image.png

4、迷宫问题

思路

  • 用一个二维矩阵代表地图
    • 1代表边界
    • 0代表未做过该地点
    • 2代表走过且能走得通
    • 3代表走过但走不通
  • 设置起点和终点以及每个地点的行走策略
    • 行走策略指在该点所走的方向的顺序,如 右->下->左->上(调用寻找路径的方法,使用递归)
  • 每次行走时假设该点能够走通,然后按照策略去判断,如果所有策略判断后都走不通,则该点走不通

图解
image.png
代码

  1. package com.luyi.DataStructures.recursion;
  2. import com.sun.org.apache.xml.internal.resolver.helpers.PublicId;
  3. public class MiGong {
  4. public static void main(String[] args) {
  5. // 创建一个二维数组 模拟迷宫
  6. int[][] map = new int[8][7];
  7. // 使用1表示墙
  8. // 上下置位1
  9. for (int i = 0; i < 7; i++ ) {
  10. map[0][i] = 1;
  11. map[7][i] = 1;
  12. }
  13. //左右置位1
  14. for (int i = 0; i < 8; i++ ) {
  15. map[i][0] = 1;
  16. map[i][6] = 1;
  17. }
  18. //
  19. // 设置挡板 1 表示
  20. map[3][1] = 1;
  21. map[3][2] = 1;
  22. //map[1][2] = 1;
  23. //map[2][2] = 1;
  24. // 输出地图
  25. System.out.println("输出地图");
  26. for (int i = 0; i < 8; i++) {
  27. for (int j = 0; j < 7; j++) {
  28. System.out.print(map[i][j] + " ");
  29. }
  30. System.out.println();
  31. }
  32. // 使用递归回溯 找路 map是引用类型 可以动态改变值 等于公共变量
  33. if(setWay(map, 1, 1)){
  34. // 输出新的地图
  35. System.out.println("小球的路径");
  36. for (int i = 0; i < 8; i++) {
  37. for (int j = 0; j < 7; j++) {
  38. System.out.print(map[i][j] + " ");
  39. }
  40. System.out.println();
  41. }
  42. }else {
  43. System.out.println("无法找到");
  44. }
  45. }
  46. // 使用递归回溯给小球找路
  47. // 说明 map是地图
  48. // i,j表示从哪个位置开始出发(i,j)
  49. // 如果小球到map[6][5] = 则说明通路找到
  50. // 约定 当map[i][j] 为0时 表示该点没有走过 1为墙 2为通路(小球行径路径) 3为该位置已经走过 但是走不通
  51. // 再走迷宫时 需要确定一个策略(行径方向 当走不通时往策略的下一个方向) 下->右->上->左 ,如果该点都走不通 在回溯
  52. /**
  53. *
  54. * @param map 地图
  55. * @param i 从哪个位置开始找
  56. * @param j
  57. * @return 如果找到通路 返回true 否则为false
  58. */
  59. public static boolean setWay(int[][] map, int i, int j) {
  60. if (map[6][5] == 2){
  61. // 说明通路已经找到
  62. return true;
  63. }else {
  64. //如果当前的点还没有走过
  65. if (map[i][j] == 0){
  66. // 按照上述策略 下->右->上->左尝试
  67. map[i][j] = 2; // 假的当前点可以走
  68. if (setWay(map, i + 1, j )){ // 向下走
  69. return true;
  70. }else if (setWay(map,i , j + 1)){ // 向右走
  71. return true;
  72. }else if (setWay(map, i - 1, j)){ // 向上走
  73. return true;
  74. }else if (setWay(map, i,j - 1)){ // 向左走
  75. return true;
  76. }else {
  77. // 都没走通 这个点是个思死路
  78. map[i][j] = 3;
  79. return false;
  80. }
  81. }else { // 肯能为 1是墙 2走过 3不能走
  82. return false;
  83. }
  84. }
  85. }
  86. }

5、八皇后问题

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

  • 将第一个皇后放在第一行第一列
  • 将第二个皇后放在第二行第一列,判断是否会和其他皇后相互攻击,若会相互攻击,则将其放到第三列、第四列…直到不会相互攻击为止
  • 将第三个皇后放在第三行第一列,判断是否会和其他皇后相互攻击,若会相互攻击,则将其放到第三列、第四列…知道不会相互攻击为止,并以此类推在摆放的过程中,有可能会改动前面所放的皇后的位置
  • 当得到一个正确的解时,就会回溯到上一行,由此来找出第一个皇后在第一行第一列的所有解
  • 再将第一个皇后放到第一行第二列,并重复以上四个步骤
  • 注意
    • 棋盘本身应该是用二维数组表示,但是因为皇后所在的行数是固定的,所以可以简化为用一个一维数组来表示。其中的值代表皇后所在的列
    • 数组下标代表皇后所在行数,所以判断是否在同一行列斜线上时,只需要判断是否在同一列和同一斜线上即可
      • 是否同列判断:值是否相同
      • 是否同一斜线:行号-行号是否等于列号-列号,且列号相减要取绝对值

image.png
image.png
代码

  1. package com.atguigu.recursion;
  2. public class Queue8 {
  3. //定义一个max表示共有多少个皇后
  4. int max = 8;
  5. //定义数组array, 保存皇后放置位置的结果,比如 arr = {0 , 4, 7, 5, 2, 6, 1, 3}
  6. int[] array = new int[max];
  7. static int count = 0;
  8. static int judgeCount = 0;
  9. public static void main(String[] args) {
  10. //测试一把 , 8皇后是否正确
  11. Queue8 queue8 = new Queue8();
  12. queue8.check(0);
  13. System.out.printf("一共有%d解法", count);
  14. System.out.printf("一共判断冲突的次数%d次", judgeCount); // 1.5w
  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个皇后就既然放好
  20. print();
  21. return;
  22. }
  23. //依次放入皇后,并判断是否冲突
  24. for(int i = 0; i < max; i++) {
  25. //先把当前这个皇后 n , 放到该行的第1列
  26. array[n] = i;
  27. //判断当放置第n个皇后到i列时,是否冲突
  28. if(judge(n)) { // 不冲突
  29. //接着放n+1个皇后,即开始递归
  30. check(n+1); //
  31. }
  32. //如果冲突,就继续执行 array[n] = i; 即将第n个皇后,放置在本行得 后移的一个位置
  33. }
  34. }
  35. //查看当我们放置第n个皇后, 就去检测该皇后是否和前面已经摆放的皇后冲突
  36. /**
  37. *
  38. * @param n 表示第n个皇后
  39. * @return
  40. */
  41. private boolean judge(int n) {
  42. judgeCount++;
  43. for(int i = 0; i < n; i++) {
  44. // 说明
  45. //1. array[i] == array[n] 表示判断 第n个皇后是否和前面的n-1个皇后在同一列
  46. //2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判断第n个皇后是否和第i皇后是否在同一斜线
  47. // n = 1 放置第 2列 1 n = 1 array[1] = 1
  48. // Math.abs(1-0) == 1 Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1
  49. //3. 判断是否在同一行, 没有必要,n 每次都在递增
  50. if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]) ) {
  51. return false;
  52. }
  53. }
  54. return true;
  55. }
  56. //写一个方法,可以将皇后摆放的位置输出
  57. private void print() {
  58. count++;
  59. for (int i = 0; i < array.length; i++) {
  60. System.out.print(array[i] + " ");
  61. }
  62. System.out.println();
  63. }
  64. }

一共有92种放法,就不一一展示了