算法篇 - 递归回溯

490 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

算法无疑困扰了大多数人,我们在学习时也会感觉很吃力。学完之后也很容易忘记,我们就需要做好归纳,算法篇我将持续更新我在学习算法时遇到的各种算法,争取让该篇的内容丰富起来,成为一个连载篇。本文我们将介绍一下递归及用递归实现回溯,解决一些程序中遇到的一些问题。

递归

通俗的讲递归就是在程序中调用自身,然后留一个可以返回的出口。

一般用于解决在不满足条件时,使用相同的套路解决问题,直至满足条件返回正确的结果。


递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 ——摘自《百度百科》

回溯

回溯算法通过不断尝试,当无法继续下去时,回溯到上一个节点开始新的尝试,直至解决问题或失败。

回溯法是一种选优搜索法,按照一定策略去搜索,以达到目标。当某一点走不通时,会回退一步重新选择,这种走不通的再往回走的算法就是回溯算法,回头后在重新找的合适点的地方为回溯点。


回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就回溯返回,尝试别的路径。 ——摘自《百度百科》

递归与回溯

递归与回溯并不冲突,反而我们可以通过递归去实现回溯算法,从而可以用这种较笨的办法去解决迷宫,八皇后等问题。

举例:

迷宫问题

顺利逃生
地图:
1	1	1	1	1	1	1	
1	2	2	2	2	2	1	
1	0	0	0	0	2	1	
1	0	1	1	0	2	1	
1	1	1	0	0	2	1	
1	0	0	0	0	2	1	
1	0	0	0	0	2	1	
1	1	1	1	1	1	1	
package com.zhj.bz.datastructure.recursion;

import java.util.Arrays;

/**
 * 递归:迷宫问题
 * @author zhj
 */
public class MiGong {
    public static void main(String[] args) {
        // 先创建一个二维数组,模拟迷宫
        // 地图
        int row = 8;
        int col = 7;
        int[][] map = new int[row][col];
        // 使用1表示墙
        // 设墙
        for (int i = 0; i < col; i++) {
            map[0][i] = 1;
            map[row-1][i] = 1;
        }
        for (int i = 0; i < row; i++) {
            map[i][0] = 1;
            map[i][col-1] = 1;
        }
        // 设挡板
        map[4][1] = 1;
        map[4][2] = 1;
        map[3][2] = 1;
        map[3][3] = 1;
        // 走
        if (setWay2(map, 1, 1, row, col)) {
            System.out.println("顺利逃生");
        } else {
            System.out.println("走投无路");
        }
        System.out.println("地图:");
        for (int[] ints : map) {
            for (int n : ints) {
                System.out.print(n + "\t");
            }
            System.out.println();
        }
    }

    /**
     * map[row-2][col-2] 为出口
     * 1 不能走,2可以走,3表示该位置已经走过,但走不通
     * 策略 下,右,上,左,如果走不通再回溯
     * @param map 地图
     * @param i 出发点坐标
     * @param j 出发点坐标
     * @param row 地图大小
     * @param col 地图大小
     * @return
     */
    public static boolean setWay(int[][] map, int i, int j,int row, int col) {
        if (map[row-2][col-2] == 2) {
            return true;
        } else {
            if (map[i][j] == 0) {
                map[i][j] = 2;
                if (setWay(map,i+1,j, row, col)) { // 下
                    return true;
                } else if (setWay(map, i,j+1, row, col)){ // 右
                    return true;
                } else if (setWay(map,i-1,j-1, row, col)){ // 上
                    return true;
                } else if (setWay(map,i,j-1, row, col)){ // 左
                    return true;
                } else {
                    map[i][j] = 3;
                    return false;
                }
            } else {
                return false;
            }
        }
    }

    /**
     * map[row-2][col-2] 为出口
     * 1 不能走,2可以走,3表示该位置已经走过,但走不通
     * 策略 右,下,左,上,如果走不通再回溯
     * @param map 地图
     * @param i 出发点坐标
     * @param j 出发点坐标
     * @param row 地图大小
     * @param col 地图大小
     * @return
     */
    public static boolean setWay2(int[][] map, int i, int j,int row, int col) {
        if (map[row-2][col-2] == 2) {
            return true;
        } else {
            if (map[i][j] == 0) {
                map[i][j] = 2;
                if (setWay2(map, i, j+1, row, col)) { // 右
                    return true;
                } else if (setWay2(map, i+1, j, row, col)){ // 下
                    return true;
                } else if (setWay2(map, i, j-1, row, col)){ // 左
                    return true;
                } else if (setWay2(map, i-1, j-1, row, col)){ // 上
                    return true;
                } else {
                    map[i][j] = 3;
                    return false;
                }
            } else {
                return false;
            }
        }
    }

}

八皇后问题


package com.zhj.bz.datastructure.recursion;

/**
 * 八皇后问题 8*8共计92中解法
 * @author zhj
 */
public class Queen8 {

    private static final int MAX = 8;
    private static int[] array = new int[MAX];
    private static int index = 0;

    public static void main(String[] args) {
        check(0);
    }

    /**
     * 放第n个皇后
     * @param n
     */
    public static void check(int n) {
        if (n == MAX) {
            index++;
            System.out.println("第 "+ index +" 次" + "八个皇后已就位");
            show();
            return;
        }
        for (int i = 0; i < MAX; i++) {
            // 先把当前这个皇后放到该行的第一列
            array[n] = i;
            // 判断是否有问题
            if (judge(n)) {
                // 接着放第n+1个皇后
                check(n+1);
            }
            // 冲突继续循环向下放,即将第n个皇后后移一个位置
        }
    }

    /**
     * 查看当我们放置第n皇后,就去检测该皇后是否和前面的皇后冲突
     * @param n 第n皇后
     * @return
     */
    public static boolean judge(int n) {
        for (int i = 0; i < n; i++) {
            // array[i] == array[n] 判断是否在同一列
            // Math.abs(n-i) == Math.abs(array[n]-array[i]) 判断是否在同一斜线
            // n每次都在递增
            if (array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n]-array[i])) {
                return false;
            }
        }
        return true;
    }

    public static void show() {
        for (int i = 0; i < array.length; i++) {
            System.out.println("第" + (i+1) + "个皇后, 放在第" + array[i] + "个位置。");
        }
    }

}