【Java数据结构和算法】010-递归:迷宫问题、八皇后问题(回溯算法)

柔情只为你懂 2023-10-05 13:43 71阅读 0赞

目录

0、警醒自己

一、递归应用场景和调用机制

1、简介

2、两个案例

打印问题:

打印结果:

执行分析图:

阶乘问题:

运行结果:

阶乘逻辑解析:

二、递归能解决的问题和规则

1、额递能解决什么问题

2、递归调用规则

三、迷宫问题

1、问题描述

2、代码实现

3、运行结果

4、问题回顾

5、代码实现

6、运行结果

7、思考:最短路径问题

四、八皇后问题

1、问题描述

2、思路分析

说明:

3、代码实现

4、运行结果


0、警醒自己

1、学习不用心,骗人又骗己;

2、学习不刻苦,纸上画老虎;

3、学习不惜时,终得人耻笑;

4、学习不复习,不如不学习;

5、学习不休息,毁眼伤身体;

7、狗才等着别人喂,狼都是自己寻找食物;

一、递归应用场景和调用机制

1、简介

简单的说:递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。

2、两个案例

打印问题:

  1. package com.zb.ds;
  2. //递归
  3. public class Recursion {
  4. public static void main(String[] args) {
  5. test(4);
  6. }
  7. public static void test(int n) {
  8. if (n > 2) {
  9. test(n - 1);
  10. }
  11. System.out.println("n=" + n);
  12. }
  13. }

打印结果:

  1. n=2
  2. n=3
  3. n=4

执行分析图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5Njg5MzQz_size_16_color_FFFFFF_t_70

阶乘问题:

  1. package com.zb.ds;
  2. //递归
  3. public class Recursion {
  4. public static void main(String[] args) {
  5. test(4);
  6. System.out.println(test1(5));
  7. }
  8. //打印问题
  9. public static void test(int n) {
  10. if (n > 2) {
  11. test(n - 1);
  12. }
  13. System.out.println("n=" + n);
  14. }
  15. //阶乘问题
  16. public static int test1(int n){
  17. if(n==1){
  18. return 1;
  19. }else {
  20. return test1(n-1) * n;
  21. }
  22. }
  23. }

运行结果:

  1. n=2
  2. n=3
  3. n=4
  4. 120

阶乘逻辑解析:

test1(4)*5 —— test1(3)*4*5 —— test1(2)*3*4*5 —— test1(1)*2*3*4*5 —— 1*2*3*4*5

二、递归能解决的问题和规则

1、额递能解决什么问题

各种数学问题如:8皇后问题、汉诺塔、阶乘问题、迷宫问题、球和篮子的问题(google编程大赛);

各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等;

将用栈解决的问题—>递归代码比较简洁;

2、递归调用规则

①当程序执行到一个方法时,就会开辟一个独立的空间(栈);

②每个空间的数据(局部变量)是相互独立的;

③如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据;

④递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了。。。

⑤当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕;

三、迷宫问题

1、问题描述

①小球得到的路径,和程序员设置的找路策略有关即:找路的上下左右的顺序相关;

②再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化;

③测试回溯现象;

④思考: 如何求出最短路径?

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5Njg5MzQz_size_16_color_FFFFFF_t_70 1

2、代码实现

  1. package com.zb.ds;
  2. //递归:迷宫问题
  3. public class MiGong {
  4. public static void main(String[] args) {
  5. //创建一个二维数组,模拟迷宫
  6. //地图
  7. int[][] map = new int[8][7];
  8. //使用1表示墙
  9. //上下全部置为1
  10. for (int i = 0; i < 7; i++) {
  11. map[0][i] = 1;
  12. map[7][i] = 1;
  13. }
  14. //左右全部置为1
  15. for (int i = 0; i < 8; i++) {
  16. map[i][0] = 1;
  17. map[i][6] = 1;
  18. }
  19. //挡板
  20. map[3][1] = 1;
  21. map[3][2] = 1;
  22. //输出地图
  23. for (int i = 0; i < 8; i++) {
  24. for (int j = 0; j < 7; j++) {
  25. System.out.print(map[i][j] +" ");
  26. }
  27. System.out.println();
  28. }
  29. //找路
  30. if(findWay(map, 1, 1)){
  31. System.out.println("找到路了!");
  32. }else {
  33. System.out.println("没找到路!");
  34. }
  35. //输出地图
  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. }
  43. /**
  44. * 使用递归回溯,给小球找路
  45. * 说明:
  46. * 1、map是地图;
  47. * 2、i、j表示从哪个位置出发(1,1)
  48. * 3、如果小球能到map的(6,5),说明找到路
  49. * 4、约定:当地图map[i][j]为0时,表示未测试过(未走过),当为1时为墙,2为通路,3表示该位置已经走过但无法走通;
  50. * 5、确定一个走迷宫探路的策略:先走下边、下边走不通走右边,以此类推顺序为“下——右——上——左”,如果走不通再回溯
  51. * @param map 地图
  52. * @param i 从哪个位置开始找
  53. * @param j 从哪个位置开始找
  54. * @return 找到路返回true,否则返回false
  55. */
  56. public static boolean findWay(int[][] map,int i,int j){
  57. if(map[6][5]==2){
  58. return true;
  59. }else {
  60. if(map[i][j]==0){
  61. //没走过,按策略走:下——右——上——左
  62. map[i][j] = 2;//假定此点可以走通
  63. if(findWay(map,i+1,j)){
  64. //向下走
  65. return true;
  66. }else if(findWay(map,i,j+1)){
  67. //向右走
  68. return true;
  69. }else if(findWay(map,i-1,j)){
  70. //向上走
  71. return true;
  72. }else if(findWay(map,i,j-1)){
  73. //向左走
  74. return true;
  75. }else {
  76. map[i][j] = 3;
  77. return false;
  78. }
  79. }else{//如果map[i][j]!=0,可能是1,2,3
  80. return false;
  81. }
  82. }
  83. }
  84. }

3、运行结果

(找到路了,2是路线,这个算法真是太强大了)

  1. 1 1 1 1 1 1 1
  2. 1 0 0 0 0 0 1
  3. 1 0 0 0 0 0 1
  4. 1 1 1 0 0 0 1
  5. 1 0 0 0 0 0 1
  6. 1 0 0 0 0 0 1
  7. 1 0 0 0 0 0 1
  8. 1 1 1 1 1 1 1
  9. 找到路了!
  10. 1 1 1 1 1 1 1
  11. 1 2 0 0 0 0 1
  12. 1 2 2 2 0 0 1
  13. 1 1 1 2 0 0 1
  14. 1 0 0 2 0 0 1
  15. 1 0 0 2 0 0 1
  16. 1 0 0 2 2 2 1
  17. 1 1 1 1 1 1 1

4、问题回顾

我们可以知道,小球得到的路径和程序员设置的找路策略有很大关系,我们来讲原来的(下右上左)改成(上右下左),进行测试,看结果如何;

5、代码实现

(说明:只需要将i的+1和-1呼唤即可)

  1. package com.zb.ds;
  2. //递归:迷宫问题
  3. public class MiGong {
  4. public static void main(String[] args) {
  5. //创建一个二维数组,模拟迷宫
  6. //地图
  7. int[][] map = new int[8][7];
  8. //使用1表示墙
  9. //上下全部置为1
  10. for (int i = 0; i < 7; i++) {
  11. map[0][i] = 1;
  12. map[7][i] = 1;
  13. }
  14. //左右全部置为1
  15. for (int i = 0; i < 8; i++) {
  16. map[i][0] = 1;
  17. map[i][6] = 1;
  18. }
  19. //挡板
  20. map[3][1] = 1;
  21. map[3][2] = 1;
  22. //输出地图
  23. for (int i = 0; i < 8; i++) {
  24. for (int j = 0; j < 7; j++) {
  25. System.out.print(map[i][j] +" ");
  26. }
  27. System.out.println();
  28. }
  29. //找路
  30. if(findWay(map, 1, 1)){
  31. System.out.println("找到路了!");
  32. }else {
  33. System.out.println("没找到路!");
  34. }
  35. //输出地图
  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. }
  43. /**
  44. * 使用递归回溯,给小球找路
  45. * 说明:
  46. * 1、map是地图;
  47. * 2、i、j表示从哪个位置出发(1,1)
  48. * 3、如果小球能到map的(6,5),说明找到路
  49. * 4、约定:当地图map[i][j]为0时,表示未测试过(未走过),当为1时为墙,2为通路,3表示该位置已经走过但无法走通;
  50. * 5、确定一个走迷宫探路的策略:先走下边、下边走不通走右边,以此类推顺序为“下——右——上——左”,如果走不通再回溯
  51. * @param map 地图
  52. * @param i 从哪个位置开始找
  53. * @param j 从哪个位置开始找
  54. * @return 找到路返回true,否则返回false
  55. */
  56. public static boolean findWay(int[][] map,int i,int j){
  57. if(map[6][5]==2){
  58. return true;
  59. }else {
  60. if(map[i][j]==0){
  61. //没走过,按策略走:下——右——上——左
  62. //更改策略为:上——右——下——左
  63. //说明:只需要将+1和-1呼唤即可
  64. map[i][j] = 2;//假定此点可以走通
  65. if(findWay(map,i-1,j)){
  66. //向下走
  67. return true;
  68. }else if(findWay(map,i,j+1)){
  69. //向右走
  70. return true;
  71. }else if(findWay(map,i+1,j)){
  72. //向上走
  73. return true;
  74. }else if(findWay(map,i,j-1)){
  75. //向左走
  76. return true;
  77. }else {
  78. map[i][j] = 3;
  79. return false;
  80. }
  81. }else{//如果map[i][j]!=0,可能是1,2,3
  82. return false;
  83. }
  84. }
  85. }
  86. }

6、运行结果

  1. 1 1 1 1 1 1 1
  2. 1 0 0 0 0 0 1
  3. 1 0 0 0 0 0 1
  4. 1 1 1 0 0 0 1
  5. 1 0 0 0 0 0 1
  6. 1 0 0 0 0 0 1
  7. 1 0 0 0 0 0 1
  8. 1 1 1 1 1 1 1
  9. 找到路了!
  10. 1 1 1 1 1 1 1
  11. 1 2 2 2 2 2 1
  12. 1 0 0 0 0 2 1
  13. 1 1 1 0 0 2 1
  14. 1 0 0 0 0 2 1
  15. 1 0 0 0 0 2 1
  16. 1 0 0 0 0 2 1
  17. 1 1 1 1 1 1 1

7、思考:最短路径问题

(搁置,我自己的想法还不够成熟,暂时不做实现!)

四、八皇后问题

1、问题描述

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

20201120090548586.png

2、思路分析

①第一个皇后先放第一行第一列;

②第二个皇后放在第二行第一列、然后判断是否OK(是否冲突), 如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适;

③继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解;

④当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到;

⑤然后回头继续第一个皇后放第二列,后面继续循环执行①②③④的步骤;

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5Njg5MzQz_size_16_color_FFFFFF_t_70 2

说明:

理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题. arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3} //对应arr 下标 表示第几行,即第几个皇后,arr[i] = val , val 表示第i+1个皇后,放在第i+1行的第val+1列;

3、代码实现

  1. package com.zb.ds;
  2. //递归:八皇后问题(回溯算法)
  3. public class EightQueen {
  4. //定义一个max表示有多少个皇后
  5. int max = 8;
  6. int count = 0;
  7. //定义一个数组array,保存皇后放置位置的结果,比如:arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
  8. int[] array = new int[max];
  9. public static void main(String[] args) {
  10. //测试八皇后
  11. new EightQueen().check(0);
  12. }
  13. //编写一个方法,放置第n个皇后
  14. //每一次递归都有一套for循环,所有也有了从后往前回溯,成立的话摆下一个皇后,不成立的话将当前皇后摆到下一列继续判断,
  15. // 假如当前行都不成立,回到上一行进行如此循环的判断,直到全部走通,简直amazing
  16. private void check(int n){
  17. if (n==max){//n==8,已经放了8个皇后
  18. print();
  19. return;
  20. }
  21. //依次放入皇后,并判断是否冲突
  22. for (int i = 0; i < max; i++) {
  23. //先把当前皇后n,放到改行的第1列
  24. array[n] = i;
  25. //判断当放置第n个皇后到i列时,是否冲突
  26. if(judge(n)){//不冲突
  27. //接着放n+1和皇后,开始递归
  28. check(n+1);
  29. }
  30. //如果冲突就继续执行循环,放到下一列
  31. }
  32. }
  33. //当我们放置第n个皇后去检测是否与前面摆放的皇后有冲突
  34. //n表示第n个皇后,i表示n前面的某个皇后,array[n]表示第n个皇后的位置,array[i]表示n前面的某个皇后的位置
  35. private boolean judge(int n){
  36. for (int i = 0; i < n; i++) {
  37. if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
  38. //在同一列或者在同一斜线上
  39. //在同一斜线上:Math.abs(n-i)表示相差的列数,Math.abs(array[n] - array[i])表示相差的行数,如果二者相等,说明在一条斜线上
  40. return false;
  41. }
  42. }
  43. return true;
  44. }
  45. //写一个方法,可以讲皇后摆放的位置打印出来
  46. private void print(){
  47. count++;
  48. System.out.print("第" + count + "种解法:");
  49. for (int value : array) {
  50. System.out.print(value + " ");
  51. }
  52. System.out.println();
  53. }
  54. }

4、运行结果

  1. 1种解法:0 4 7 5 2 6 1 3
  2. 2种解法:0 5 7 2 6 3 1 4
  3. 3种解法:0 6 3 5 7 1 4 2
  4. 4种解法:0 6 4 7 1 3 5 2
  5. 5种解法:1 3 5 7 2 0 6 4
  6. 6种解法:1 4 6 0 2 7 5 3
  7. 7种解法:1 4 6 3 0 7 5 2
  8. 8种解法:1 5 0 6 3 7 2 4
  9. 9种解法:1 5 7 2 0 3 6 4
  10. 10种解法:1 6 2 5 7 4 0 3
  11. 11种解法:1 6 4 7 0 3 5 2
  12. 12种解法:1 7 5 0 2 4 6 3
  13. 13种解法:2 0 6 4 7 1 3 5
  14. 14种解法:2 4 1 7 0 6 3 5
  15. 15种解法:2 4 1 7 5 3 6 0
  16. 16种解法:2 4 6 0 3 1 7 5
  17. 17种解法:2 4 7 3 0 6 1 5
  18. 18种解法:2 5 1 4 7 0 6 3
  19. 19种解法:2 5 1 6 0 3 7 4
  20. 20种解法:2 5 1 6 4 0 7 3
  21. 21种解法:2 5 3 0 7 4 6 1
  22. 22种解法:2 5 3 1 7 4 6 0
  23. 23种解法:2 5 7 0 3 6 4 1
  24. 24种解法:2 5 7 0 4 6 1 3
  25. 25种解法:2 5 7 1 3 0 6 4
  26. 26种解法:2 6 1 7 4 0 3 5
  27. 27种解法:2 6 1 7 5 3 0 4
  28. 28种解法:2 7 3 6 0 5 1 4
  29. 29种解法:3 0 4 7 1 6 2 5
  30. 30种解法:3 0 4 7 5 2 6 1
  31. 31种解法:3 1 4 7 5 0 2 6
  32. 32种解法:3 1 6 2 5 7 0 4
  33. 33种解法:3 1 6 2 5 7 4 0
  34. 34种解法:3 1 6 4 0 7 5 2
  35. 35种解法:3 1 7 4 6 0 2 5
  36. 36种解法:3 1 7 5 0 2 4 6
  37. 37种解法:3 5 0 4 1 7 2 6
  38. 38种解法:3 5 7 1 6 0 2 4
  39. 39种解法:3 5 7 2 0 6 4 1
  40. 40种解法:3 6 0 7 4 1 5 2
  41. 41种解法:3 6 2 7 1 4 0 5
  42. 42种解法:3 6 4 1 5 0 2 7
  43. 43种解法:3 6 4 2 0 5 7 1
  44. 44种解法:3 7 0 2 5 1 6 4
  45. 45种解法:3 7 0 4 6 1 5 2
  46. 46种解法:3 7 4 2 0 6 1 5
  47. 47种解法:4 0 3 5 7 1 6 2
  48. 48种解法:4 0 7 3 1 6 2 5
  49. 49种解法:4 0 7 5 2 6 1 3
  50. 50种解法:4 1 3 5 7 2 0 6
  51. 51种解法:4 1 3 6 2 7 5 0
  52. 52种解法:4 1 5 0 6 3 7 2
  53. 53种解法:4 1 7 0 3 6 2 5
  54. 54种解法:4 2 0 5 7 1 3 6
  55. 55种解法:4 2 0 6 1 7 5 3
  56. 56种解法:4 2 7 3 6 0 5 1
  57. 57种解法:4 6 0 2 7 5 3 1
  58. 58种解法:4 6 0 3 1 7 5 2
  59. 59种解法:4 6 1 3 7 0 2 5
  60. 60种解法:4 6 1 5 2 0 3 7
  61. 61种解法:4 6 1 5 2 0 7 3
  62. 62种解法:4 6 3 0 2 7 5 1
  63. 63种解法:4 7 3 0 2 5 1 6
  64. 64种解法:4 7 3 0 6 1 5 2
  65. 65种解法:5 0 4 1 7 2 6 3
  66. 66种解法:5 1 6 0 2 4 7 3
  67. 67种解法:5 1 6 0 3 7 4 2
  68. 68种解法:5 2 0 6 4 7 1 3
  69. 69种解法:5 2 0 7 3 1 6 4
  70. 70种解法:5 2 0 7 4 1 3 6
  71. 71种解法:5 2 4 6 0 3 1 7
  72. 72种解法:5 2 4 7 0 3 1 6
  73. 73种解法:5 2 6 1 3 7 0 4
  74. 74种解法:5 2 6 1 7 4 0 3
  75. 75种解法:5 2 6 3 0 7 1 4
  76. 76种解法:5 3 0 4 7 1 6 2
  77. 77种解法:5 3 1 7 4 6 0 2
  78. 78种解法:5 3 6 0 2 4 1 7
  79. 79种解法:5 3 6 0 7 1 4 2
  80. 80种解法:5 7 1 3 0 6 4 2
  81. 81种解法:6 0 2 7 5 3 1 4
  82. 82种解法:6 1 3 0 7 4 2 5
  83. 83种解法:6 1 5 2 0 3 7 4
  84. 84种解法:6 2 0 5 7 4 1 3
  85. 85种解法:6 2 7 1 4 0 5 3
  86. 86种解法:6 3 1 4 7 0 2 5
  87. 87种解法:6 3 1 7 5 0 2 4
  88. 88种解法:6 4 2 0 5 7 1 3
  89. 89种解法:7 1 3 0 6 4 2 5
  90. 90种解法:7 1 4 2 0 6 3 5
  91. 91种解法:7 2 0 5 1 4 6 3
  92. 92种解法:7 3 0 2 5 1 6 4

发表评论

表情:
评论列表 (有 0 条评论,71人围观)

还没有评论,来说两句吧...

相关阅读

    相关 解决皇后回溯算法

    问题简述:八皇后问题是一个古来而著名的问题,该问题是19世纪著名的数学家高斯同学提出来的。在8\8的国际象棋上摆放八个皇后,使其不能互相的攻击,也就是说,任意的两个皇后不能放在