我所知道的算法之递归

作者前言

大家好,我是阿濠,今篇内容跟大家分享的是算法之递归,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.

一、递归的概念

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

二、递归的调用机制

列举两个小案例,认识递归调用机制

1.打印问题、2.阶乘问题

//1.打印问题
public class RecursionTest {
    public static void main(String[] args) {
        //通过打印问题,回顾递归调用机制
        test(4);
    }
    public static void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }
}

递归在我们的程序内部如何调用的?程序从main方法入口进入

当我们进入main方法调用test(4)时,开辟独立空间

图片.png

执行test(4)方法时,会有变量n = 4 并进行if判断(4>2),执行test(4-1)方法

图片.png

此时执行test(3)方法时,n = 3 也进行if判断(3>2),并执行test(3-1)方法

图片.png

此时执行test(2)方法时,n = 2 也进行if判断(2>2),但是2>2 不成立,执行输出指令

图片.png

此时test(2)执行完毕后控制台进行输出的结果是 n =2 当test(2)执行完后就退到test(3).. n=3、test(4)... n=4

图片.png

图片.png

public class RecursionTest {
    public static void main(String[] args) {
        int res=factorial(3);
        System.out.println("结果="+res);
    }
    //阶乘问题
    public static int factorial(int n) {
        if(n==1){
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}

按照刚刚示意图的理解,就先是factorial(3 - 1 ) * 3factorial(2 - 1 ) * 2、而factorial(1)直接return 1,所以输出结果就是1 * 2 * 3=6

递归机制小结:

1.当程序执行到一个方法时,就会开辟独立的空间(栈)
2.每个空间的数据(局部变量),是独立的

递归可以解决什么问题呢?

1.各种数学问题:
如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题等等

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

3.将用栈解决的问题-->第归代码比较简洁

递归需要遵守的重要规则

1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

2)方法的局部变量是独立的不会相互影响,比如之前举例的 n 变量

3)如果方法中是使用引用变量,即会共享引用类型的数据,即迷宫问题时方法传递是数组,数组是引用类型那么就会进行调用同一个数据:数组

4)递归必须向退出递归的条件逼近,否则就是无限递归,死龟了抛出异常:StackOverflowError

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

递归的实际问题案例

递归-迷宫问题

图片.png

思路分析:

1.创建二维数组模拟迷宫
2.使用1代表墙,上下全是1,即是列在变,行没变,所以范围是0-6在变
3.使用1代表墙,左右全是1,即是行在变,列没变,所以范围是0-7在变
4.使用1代表挡板,即小球无法走到挡板的位置,按图所示第四行的1、2列
5.约定:0 代表没有走过、1 表示墙、2表示路可以走、3已走过但不通
6.按图所示,起点为止从(1,1)开始(第二行第二列),小球在(6,5)的位置
7.若小球为(i,j),向左走是(i,j-1),向右走是(i,j+1),向上走是(i-1,j),向下是(i+1,j)

创建迷宫
//先创建一个二维数组,模拟迷宫地图
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();
}

运行结果:
1 1 1 1 1 1 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 0 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 1 1 1 1
找小球策略:下右上左
//使用递归回溯来给小球找路
//说明
//1. map表示地图
//2. i,j 表示从地图的哪个位置开始出发(1,1)
//3. 如果小球能到map[6][5]位置,则说明通路找到. .
//4. 约定:当map[i][j] 为0表示该点没有走过当为1表示墙; 2表示通路可以走; 3表示该点已经走过,但是走不通
//5. 在走迷宫时,需要确定-个策略(方法):下->右->上->左 ,如果该点走不通,再回溯
/**
 * @param map 表示地图
 * @param i 从哪个位置开始找
 * @param j 从哪个位置开始找
 * @return 如果找到通路,就返回true, 否则返回false
 */
public static boolean setWay(int[][] map, int i, int j) {
    //小球的位置是(6,5) 对应数组则是map[6][5]
    if(map[6][5] == 2) {
        //到map[6][5]位置,说明球已找到
        return true;
    } else {
        //判断到地图map[i][j]是0,则代表这个点还没有走过
        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, 1 - 1, j)) { //向上
                return true;
            } else if (setWay(map, i, j - 1)) { //向左走
                return true;
            }else {
                //说明该点是走不通,已经走过,是死路
                map[i][j] =3;
                return false;
            }
        } else{
            //如果map[i][j] != 0 ,可能是1, 2, 3
            //当为1表示墙; 2表示通路可以走; 3表示该点已经走过
            return false;
        }
    }
}

步骤分析图:

图片.png

图片.png

图片.png

图片.png

图片.png

图片.png

.....

递归-八皇后问题

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

八皇后问题算法思路分析.

1.第一个皇后先放第一行第一列
2.第二个皇后放在第二行第一列,然后判断是否0K,如果不OK继续放在第二列、第三列...依次把所有列都放完,找到合适的位置
3.继续第三个皇后还是第一列、第二列...`直到第八个皇后都放在一个不冲突的位置,算是找到了一个正确解`
4)当得到一个正确解时,栈回退到上一个栈开始回溯,即第一个皇后放到第一列的所有正确解全部得到.
5)然后回头继续把第一个皇后第二列,后面继续循环执行1,2,3的步骤

步骤示意图分析

图片.png

按照思路先将.第一个皇后先放第一行第一列 (小圆圈代表皇后)

图片.png

第二个皇后放在第二行第一列,然后判断是否0K,显然条件是:两个皇后都不能处于同一行、同一列或同一斜线上

图片.png

图片.png

于是第二个皇后再向右移动一个位置,满足条件不是相互攻击

图片.png

于是继续第三个皇后还是第一列开始,显然条件是:两个皇后都不能处于同一行、同一列或同一斜线上...第...皇后

图片.png

图片.png

图片.png

图片.png

图片.png

图片.png

这时得到一个正确的解就会进行把第一个皇后第二列,后面继续循环执行1,2,3的步骤,但是效率并不高,因为8x8的会执行一万多次,后面会有算法进行优化

提示说明:

理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法用一个一维数组即可解决问题:arr[8]= {0,4, 7,5,2, 6, 1, 3}

arr下标表示第几行,即第几个皇后,arr[i] =val, val表示第i+1个皇后,放在第i+1行的第i+1列

即我们使用下标表示第几行是第几个皇后,按图所示那么对应的值若是一致则代表在同一列:array[n]==array[i]

图片.png

即我们使用下标表示第几行是第几个皇后,那么第n个皇后与第n-1皇后的列差 == 第n个皇后与第n-1行差,那么则是同一斜线上:Math.abs(n - i) == Math.abs(array[n] - array[i]),比如说第二个皇后与第一个皇后的行差=1、列差=1,相等证明按图所示它们是斜线

图片.png

实际操作代码如下:

public class Queue8 {

    //定义一个max表示共有多少个皇后
    int max = 8;
    //定义数组array,保存皇后放置位置的结果,比如arr = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    
    //放置第n个皇后
    public void check(int n){
        //从0开始放置 若n=8 则代表最后一列的皇后了
        if( n == max ){
            printQueue();
            return;
        }
        //依次放入皇后
        for(int i=0; i<max; i++){
            //把当前皇后n,放入第一列位置
            array[n]=i;
            //放入第n个皇后检查是否冲突
            if(judge(n)){
                //若不冲突,则执行n+1个皇后
                check(n+1);
            }
            //若冲突 则继续执行arr[n] = i;即将第n个皇后 放置在本行的后移一个位置
        }

    }

    //查看当我们放置第n个皇后,就去检测该皇后是否和前面已经摆放的皇后冲突
    /**
     * @param n 表示第n个皇后
     * @return
     */
    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皇后是否在同一斜线
            //行差=列差 证明是斜线
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
                return false;
            }
        }
        return true;
    }

    //写一个方法,可以将皇后摆放的位置输出
    private void printQueue(){

        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);
}

运行结果如下:
04752613
05726314
06357142
06471352
13572064
14602753
14630752
15063724
15720364
16257403
16470352
17502463
20647135
24170635
24175360
24603175
24730615
25147063
25160374
25164073
25307461
25317460
25703641
25704613
25713064
26174035
26175304
27360514
30471625
30475261
31475026
31625704
31625740
31640752
31746025
31750246
35041726
35716024
35720641
36074152
36271405
36415027
36420571
37025164
37046152
37420615
40357162
40731625
40752613
41357206
41362750
41506372
41703625
42057136
42061753
42736051
46027531
46031752
46137025
46152037
46152073
46302751
47302516
47306152
50417263
51602473
51603742
52064713
52073164
52074136
52460317
52470316
52613704
52617403
52630714
53047162
53174602
53602417
53607142
57130642
60275314
61307425
61520374
62057413
62714053
63147025
63175024
64205713
71306425
71420635
72051463
73025164

总共是92种思路解法,发现就是按照思路图的思路进行递归判断,可进入小游戏网站实践一下:http://www.4399.com/flash/426...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值