Algoritmos y estructuras de datos de Java: recursividad y retroceso

1. ¿Qué es la recursividad?

En pocas palabras: la recursión es un método que se llama a sí mismo, pasando diferentes variables cada vez que se llama. La recursión ayuda a los programadores a resolver problemas complejos mientras hace que el código sea conciso.

Mire un escenario de aplicación práctica, problema de laberinto (retroceso), recursividad (Recursión)

Enumeraré dos casos pequeños para ayudarlo a comprender la recursividad. Aquí revisaré el mecanismo de llamada recursiva para usted.

  • problema de impresión
  • problema factorial
public static void test(int n) {
    if (n > 2) {
	    test(n - 1);
    }
    System.out.println("n=" + n);
}

public static int factorial(int n) {
    if (n == 1) {
        return 1;
    } else {
        return factorial(n - 1) * n;
    }
}

¿Para qué tipo de problemas se utiliza la recursividad?

  1. Varios problemas matemáticos como: Problema de las 8 reinas, Torre de Hanoi, Problema factorial, Problema del laberinto, Problema de la pelota y la canasta (Concurso de programación de Google).
  2. La recursividad también se utiliza en varios algoritmos, como clasificación rápida, clasificación por fusión, búsqueda binaria, algoritmo de división y conquista, etc.
  3. El problema resuelto por la pila --> el primer código es más conciso.

Reglas importantes a seguir para la recursividad

  • Cuando se ejecuta un método, se crea un nuevo espacio independiente protegido (espacio de pila).
  • Las variables locales del método son independientes y no se afectan entre sí, como la variable n.
  • Si el método usa una variable de tipo de referencia (como una matriz), los datos de ese tipo de referencia se comparten.
  • La recursividad debe acercarse a la condición de salir de la recursividad, de lo contrario, es una recursividad infinita, se produce StackOverflowError y la tortuga está muerta :).
  • Cuando un método termina de ejecutarse, o encuentra un retorno, regresará, y quien lo llame devolverá el resultado a quien lo llame.

2. Caso de código 1 - Problema de laberinto

Nota: El camino obtenido por la pelota está relacionado con la estrategia de búsqueda de camino establecida por el programador, es decir: el orden de arriba, abajo, izquierda y derecha del camino está relacionado con el orden del camino. ) para ver si la ruta ha cambiado. Prueba de fenómenos de retroceso.

package com.szh.recursion;

/**
 * 走迷宫问题
 */
public class MiGong {

    //使用递归回溯来给小球找路, 说明:
    //1. map 表示地图
    //2. i,j 表示从地图的哪个位置开始出发 (1,1)
    //3. 如果小球能到 map[6][5] 位置,则说明通路找到.
    //4. 约定:当 map[i][j] 为 0 表示该点没有走过; 当为 1 表示墙; 2 表示通路可以走;
    //5. 在走迷宫时,需要确定一个策略(方法) 下->右->上->左 , 如果该点走不通,再回溯
    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 { //左
                    return true;
                }
            } else { //map[i][j] != 0, 即只能为1、2。 1表示墙(无法走),2表示已经走过了,所以此时直接返回false
                return false;
            }
        }
    }

    //修改找路的策略,改成 上->右->下->左
    public static boolean setWay2(int[][] map, int i, int j) {
        if(map[6][5] == 2) { // 通路已经找到ok
            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 { //左
                    return true;
                }
            } else {
                return false;
            }
        }
    }

    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;
        }
        //多添加两块墙体
        map[3][1] = 1;
        map[3][2] = 1;
//      map[1][2] = 1;
//		map[2][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();
        }
    }
}


3. Caso de código 2 - El problema de las ocho reinas

El problema de las ocho reinas es un problema antiguo y bien conocido, un caso típico de algoritmo de retroceso. El problema lo planteó el ajedrecista Max Bethel en 1848: Coloca ocho reinas en una cuadrícula de ajedrez de 8×8 de manera que no puedan atacarse entre sí, es decir, no pueden estar dos reinas en la misma fila, en la misma columna o en la misma columna. mismo corte, pregunte cuántos péndulos hay.

La primera reina se coloca primero en la primera fila y la primera columna.

La segunda reina se coloca en la segunda fila y la primera columna, y luego juzgue si está bien. Si no está bien, continúe colocándola en la segunda columna, la tercera columna, y coloque todas las columnas por turno para encontrar uno adecuado.

Continúe con la tercera reina, o la primera columna, la segunda columna... Hasta que la octava reina también se pueda colocar en una posición no conflictiva, se ha encontrado una solución correcta.

Cuando se obtenga una solución correcta, cuando la pila vuelva a la pila anterior, comenzará el backtracking, es decir, se colocará la primera reina en la primera columna y se obtendrán todas las soluciones correctas.

Luego regrese a la primera reina y coloque la segunda columna, y luego continúe recorriendo los pasos 1, 2, 3 y 4.

package com.szh.recursion;

/**
 * 八皇后问题
 */
public class Queue8 {

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

    //编写一个方法,放置第n个皇后
    //特别注意: check 是 每一次递归时,进入到check中都有  for(int i = 0; i < max; i++),因此会有回溯
    private void check(int n) {
        if (n == max) { //n = 8 , 表示这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个皇后,放置在本行第i列向后的那一列
        }
    }

    //查看当我们放置第n个皇后, 就去检测该皇后是否和前面已经摆放的n-1个皇后冲突
    private boolean judge(int n) {
        //每摆放一个皇后,就循环去和之前摆好的皇后位置相比较,看是否冲突
        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])) {
                judgeCount++;
                return false;
            }
        }
        return true;
    }

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

    public static void main(String[] args) {
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("一共有%d解法\n", count);
        System.out.printf("一共判断冲突的次数%d次", judgeCount);
    }
}

De hecho, el proceso de retroceso se puede ver al Depurar el código aquí, por lo que no diré más.

Supongo que te gusta

Origin blog.csdn.net/weixin_43823808/article/details/123527998
Recomendado
Clasificación