5.递归

1、概念

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

2、能解决的问题

数学问题

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

各种排序算法

3、规则

  • 方法的变量是独立的,不会相互影响的

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

  • 递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError

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

    者返回时,该方法也就执行完毕

4、迷宫问题

思路

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

图解

初始地图

行走路径

策略:右下左上

代码

public class Demo2 {
   public static void main(String[] args) {
      //得到地图
      int length = 7;
      int width = 6;
      int[][] map = getMap(length, width);

      //设置一些障碍
      map[1][2] = 1;
      map[2][2] = 1;

      //打印地图
      System.out.println("地图如下");
      for(int i=0; i<length; i++) {
         for(int j=0; j<width; j++) {
            System.out.print(map[i][j]+" ");
         }
         System.out.println();
      }

      //走迷宫
      getWay(map, 1, 1);

      //行走路径
      System.out.println("行走路径");
      for(int i=0; i<length; i++) {
         for(int j=0; j<width; j++) {
            System.out.print(map[i][j]+" ");
         }
         System.out.println();
      }

   }

   /**
    * 创建地图
    * @param length 地图的长
    * @param width 地图的宽
    * @return 创建好的地图
    */
   public static int[][] getMap(int length, int width) {
      int[][] map = new int[length][width];
      //先将第一行和最后一行设置为1(边界)
      for(int i=0; i<width; i++) {
         map[0][i] = 1;
         map[length-1][i] = 1;
      }
      //再将第一列和最后一列设置为1
      for(int i=0; i<length; i++) {
         map[i][0] = 1;
         map[i][width-1] = 1;
      }
      return map;
   }

   /**
    * 开始走迷宫
    * @param map 地图
    * @param i 起点横坐标
    * @param j 七点纵坐标
    * @return 能否走通,true能走通,false反之
    */
   public static boolean getWay(int[][] map, int i, int j) {
      int length= map.length;
      int width = map[0].length;
      //假设右下角为终点
      if(map[length-2][width-2] == 2) {
         //走通了,返回true
         return true;
      } else {
         if(map[i][j] == 0) {
            //假设改路能走通
            map[i][j] = 2;
            //行走策略 右->下->左->上
            if(getWay(map, i, j+1)) {
               return true;
            }else if(getWay(map, i+1, j)) {
               return true;
            }else if(getWay(map, i-1, j)) {
               return true;
            }else if(getWay(map, i, j-1)) {
               return true;
            }
            //右下左上都走不通
            map[i][j] = 3;
         }else {
            //改路已经被标记过了,不用再走了,直接返回false
            return false;
         }
      }
      return false;
   }
}

运行结果

地图如下
1 1 1 1 1 1 
1 0 1 0 0 1 
1 0 1 0 0 1 
1 0 0 0 0 1 
1 0 0 0 0 1 
1 0 0 0 0 1 
1 1 1 1 1 1 
行走路径
1 1 1 1 1 1 
1 2 1 0 0 1 
1 2 1 0 0 1 
1 2 2 2 2 1 
1 0 0 0 2 1 
1 0 0 0 2 1 
1 1 1 1 1 1

5、八皇后问题

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

思路

  • 将第一个皇后放在第一行第一列

  • 将第二个皇后放在第二行第一列,判断是否会和其他皇后相互攻击,若会相互攻击,则将其放到第三列、第四列…知道不会相互攻击为止

  • 将第三个皇后放在第三行第一列,判断是否会和其他皇后相互攻击,若会相互攻击,则将其放到第三列、第四列…知道不会相互攻击为止,并以此类推在摆放的过程中,有可能会改动前面所放的皇后的位置

  • 当得到一个正确的解时,就会回溯到上一行,由此来找出第一个皇后在第一行第一列的所有解

  • 再将第一个皇后放到第一行第二列,并重复以上四个步骤

  • 注意

    • 棋盘本身应该是用二维数组表示,但是因为皇后所在的行数是固定的,所以可以简化为用一个一维数组来表示。其中的值代表皇后所在的列
    • 数组下标代表皇后所在行数,所以判断是否在同一行列斜线上时,只需要判断是否在同一列和同一斜线上即可
      • 是否同列判断:值是否相同
      • 是否同一斜线:行号-行号是否等于列号-列号,且列号相减要取绝对值

代码

public class Demo3 {
   /**
    * 创建皇后所放位置的数组,数组的下标代表行号,数组中的值代表所在的列号
    */
   static int sum = 0;
   int  max = 8;
   int[] arr = new int[max];
   public static void main(String[] args) {
      Demo3 demo = new Demo3();
      //放入第一个皇后,开始求后面的皇后
      demo.check(0);
      System.out.println("一共有"+sum+"种放法");
   }

   /**
    * 打印数组元素
    */
   public void print() {
      for(int i = 0; i<arr.length; i++) {
         System.out.print(arr[i] + " ");
      }
      sum++;
      System.out.println();
   }

   /**
    * 判断该位置的皇后与前面几个是否冲突
    * @param position 需要判断的皇后的位置
    * @return true代表冲突,false代表不冲突
    */
   public boolean judge(int position) {
      for(int i = 0; i<position; i++) {
         //如果两个皇后在同一列或者同一斜线,就冲突
         //因为数组下标代表行数,所以不会存在皇后在同一行
         //所在行数-所在行数 如果等于 所在列数-所在列数,则两个皇后在同一斜线上
         if(arr[i] == arr[position] || (position-i) == Math.abs(arr[position]-arr[i])) {
            return true;
         }
      }
      return false;
   }

   /**
    * 检查该皇后应放的位置
    * @param queen 要检查的皇后
    */
   public void check(int queen) {
      if(queen == max) {
         //所有的皇后都放好了,打印并返回
         print();
         return;
      }
      //把皇后放在每一列上,看哪些不会和之前的冲突
      for(int i = 0; i<max; i++) {
         //把第queen+1个皇后放在第i列
         arr[queen] = i;
         if(!judge(queen)) {
            //不冲突,就去放下一个皇后
            check(queen+1);
         }
      }
   }
}

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

posted @ 2022-03-06 14:53  随遇而安==  阅读(24)  评论(0编辑  收藏  举报