Java data structure and algorithm (5): recursion

1. Definition

Recursion is a method that calls itself, passing in different variables each time it is called. Recursion helps programmers solve complex problems, and at the same time makes the code concise.

2. Recursive call mechanism

  1. Printing problem
  2. Factorial problem
  3. Illustrate the recursive call mechanism using a graphical method

Insert picture description here
Code

package com.lele.recursion;

/**
 * author: hwl
 * date: 2020/10/4 9:15
 * version: 1.0.0
 * modified by:
 * description:
 */
public class RecursionTest {
    
    

    public static void main(String[] args) {
    
    
//        test(4);

        System.out.println("阶乘结果:" + factorial(3));
    }

    /**
     * 打印问题
     * @param n
     */
    public static void test(int n) {
    
    
        if (n > 2) {
    
    
            test(n - 1);
        }
        System.out.println("n=" + n);
    }

    /**
     * 阶乘问题
     * @param n
     * @return
     */
    public static int factorial(int n) {
    
    
        if (n == 1) {
    
    
            return 1;
        } else {
    
    
            return factorial(n - 1) * n; // 1*2*3
        }
    }
}

3. Problems that recursion can solve

  1. Various mathematical problems: eight queens problem, tower of Hanoi, factorial problem, maze problem, ball and basket problem;
  2. Recursion is also used in various algorithms: fast sorting, merge sort, binary search, divide and conquer algorithm, etc.;
  3. The problem solved by the stack is solved by recursion, and the code is more concise;

4. Rules for recursion

  1. When a method is executed, a new protected independent space (stack space) is created;
  2. The local variables of the method are independent and will not affect each other;
  3. If the method uses a reference type variable (such as an array), the data of the reference type will be shared;
  4. Recursion must approach the condition of exiting recursion, otherwise it will be infinite recursion, and StackOverflowError will appear;
  5. When a method is executed, or when it encounters a return, it will return, and whoever calls it will return the result to whoever. At the same time, when the method is executed or returns, the method is also executed;

5. Maze problem

Insert picture description here
Code

package com.lele.recursion;

/**
 * author: hwl
 * date: 2020/10/6 12:45
 * version: 1.0.0
 * modified by:
 * description: 迷宫问题(最终的路径与程序员设置的找策略路径有关,即:找路的上下左右的顺序有关)
 */
public class MiGong {
    
    

    public static void main(String[] args) {
    
    
        // 先创建一个二维数组,模拟迷宫
        // 地图
        int[][] map = new int[8][7];
        // 使用1表示墙
        // 上下全部置为1
        for (int i = 0; i < 7; i++) {
    
    
            map[0][i] = 1;
            map[7][i] = 1;
        }

        // 左右全部置为1
        for (int i = 0; i < 8; i++) {
    
    
            map[i][0] = 1;
            map[i][6] = 1;
        }

        // 设置挡板,用1表示
        map[3][1] = 1;
        map[3][2] = 1;

        // 输出地图
        System.out.println("地图的情况:");
        for (int i = 0; i < 8; i++) {
    
    
            for (int j = 0; j < 7; j++) {
    
    
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }

        // 使用递归回溯给小球找路   策略(方法) 下=>右=>上=>左
        setWay(map, 1, 1);
        // 策略(方法) 上=>右=>下=>左
//        setWay2(map, 1, 1);


        // 输出新的地图,小球走过并标识过的
        System.out.println("小球走过,并标识过的 地图的情况:");
        for (int i = 0; i < 8; i++) {
    
    
            for (int j = 0; j < 7; j++) {
    
    
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }

    /**
     * 使用递归回溯来给小球找路
     * 说明:
     *   1.如果小球能到 map[6][5] 位置,则说明通路找到
     *   2.当map[i][j]为:0表示该点没有走过;1表示墙;2表示通路可以走;3表示该点已经走过,但是走不通;
     *   3.在走迷宫时,需要确定一个策略(方法) 下=>右=>上=>左  , 如果该点走不通,再回溯;
     * @param map  表示地图
     * @param i  小球起始位置的行索引
     * @param j  小球起始位置的列索引
     * @return   如果找到通路,就返回true,否则返回false
     */
    public static boolean setWay(int[][] map, int i, int j) {
    
    
        if (map[6][5] == 2) {
    
     // 通路已经找到
            return true;
        } else {
    
    
            if (map[i][j] == 0) {
    
      //该点还没有走过
                // 按照策略 下=>右=>上=>左  走
                map[i][j] = 2; // 假定该点是可以走通
                if (setWay(map, i+1, j)) {
    
      //向下走
                    return true;
                } else if (setWay(map, i, j+1)) {
    
      // 向右走
                    return true;
                } else if (setWay(map, i-1, j)) {
    
      //向上走
                    return true;
                } else if (setWay(map, i, j-1)) {
    
      // 向左走
                    return true;
                } else {
    
    
                    // 该点走不通,是死路
                    map[i][j] = 3;
                    return false;
                }
            } else {
    
      // map[i][j] 可能是1、2、3
                return false;
            }
        }
    }

    public static boolean setWay2(int[][] map, int i, int j) {
    
    
        if (map[6][5] == 2) {
    
     // 通路已经找到
            return true;
        } else {
    
    
            if (map[i][j] == 0) {
    
      //该点还没有走过
                // 按照策略 上=>右=>下=>左  走
                map[i][j] = 2; // 假定该点是可以走通
                if (setWay2(map, i-1, j)) {
    
      //向上走
                    return true;
                } else if (setWay2(map, i, j+1)) {
    
      // 向右走
                    return true;
                } else if (setWay2(map, i+1, j)) {
    
      //向下走
                    return true;
                } else if (setWay2(map, i, j-1)) {
    
      // 向左走
                    return true;
                } else {
    
    
                    // 该点走不通,是死路
                    map[i][j] = 3;
                    return false;
                }
            } else {
    
      // map[i][j] 可能是1、2、3
                return false;
            }
        }
    }
}

6. The eight queens problem (backtracking algorithm)

Introduction The
Eight Queens problem is an old and well-known problem and a typical case of backtracking algorithms. The problem was raised by the chess player Max Bethel in 1848: place eight queens on an 8X8 chess board so that they cannot attack each other, that is: no two queens can be in the same row and same line. Column or the same diagonal line, ask how many pendulum methods there are (92).
Insert picture description here
Thinking analysis

  1. The first queen is placed in the first row and first column;
  2. Put the second queen in the first column of the second row, and then judge whether it is OK. If it is not OK, continue to put it in the second and third columns, put all the columns in order, and find a suitable one;
  3. Continue to the third queen, or the first column, the second column... until the eighth queen can be placed in a non-conflicting position, it is considered to have found a correct solution;
  4. When a correct solution is obtained, when the stack rolls back to the previous stack, it will start to backtrack, that is: put the first queen and all correct solutions in the first column are all obtained;
  5. Then go back and continue to put the first queen into the second column, and then continue to cycle through the steps 1, 2, 3, and 4.

Explanation
Theoretically, a two-dimensional array should be created to represent the chessboard, but in fact, a one-dimensional array can be used to solve the problem through an algorithm.

arr[8] = {
    
    0,4,7,5,2,6,1,3} 
// arr下标 表示 第几行,即第几个皇后
// arr[i] = val, val表示第i+1个皇后,放在第i+1行的第val+1列

Code

package com.lele.recursion;

/**
 * author: hwl
 * date: 2020/10/6 16:56
 * version: 1.0.0
 * modified by:
 * description: 八皇后问题
 */
public class Queue8 {
    
    

    // 定义一个 max 表示共有多少个皇后
    int max = 8;
    //定义数组 array,保存皇后放置位置的结果,比如 arr = {0,4,7,5,2,6,1,3}
    int[] array = new int[max];
    static int count = 0;
    static int judgeCount = 0;

    public static void main(String[] args) {
    
    
        // 测试 8皇后是否正确
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("一共有%d种解法", count);
        System.out.printf("一共判断冲突的次数有%d次", judgeCount);// 1.5w
    }

    /**
     * 放置第 n 个皇后
     * 注意:check 是每一次递归时,进入到chenck中都有 for(int i = 0; i < max; i++),因此会有回溯
     * @param n
     */
    private void check(int n) {
    
    
        if (n == max) {
    
      // 8个皇后已经放好
            print();
            return;
        }

        // 依次放入皇后,并判断是否冲突
        for (int i = 0; i < max; i++) {
    
    
            // 先把当前这个皇后 n,放到该行的第1列
            array[n] = i;
            // 判断当放置第n个皇后到i列时,是否冲突
            if (judge(n)) {
    
    
                // 接着放 n+1 个皇后,即开始递归
                check(n+1);
            }
            // 如果冲突,就继续执行 array[n] = i;即将第n个皇后,放置本行的后移一个位置
        }
    }

    /**
     * 查看当我们放置第n个皇后,就去检测该皇后是否和前面已经摆放的皇后冲突
     * @param n  表示第n个皇后
     * @return
     */
    private boolean judge(int n) {
    
    
        judgeCount++;
        for (int i = 0; i < n; i++) {
    
    
            /**
             * 说明:
             *   1. array[i] == array[n] 表示 判断第n个皇后是否和前面的n-1个皇后在同一列
             *   2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示 判断第n个皇后是否和第i皇后在同一斜线
             *   3. 判断是否在同一行:没有必要,n每次都在递增
             */
            if (array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])) {
    
    
                return false;
            }
        }
        return true;
    }

    /**
     * 输出皇后的摆放位置
     */
    private void print() {
    
    
        count++;
        for (int i = 0; i < array.length; i++) {
    
    
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

Guess you like

Origin blog.csdn.net/houwanle/article/details/108916254
Recommended