回溯算法(递归)

递归:递归就是在函数里调用函数本身。

基线条件和递归条件
1、基线条件:就是指结束调用的条件,避免形成无限循环。
2、递归条件:就是指函数调用自己的条件。
3、递归必须向基线条件逼近,否则就会出现无线递归。

一、打印问题

给出一个整数,使用递归打印它。

public class Recursion {
    public static void main(String[] args) {
        Recursion.test(4);
    }

    public static void test(int a) {
        if (a > 2) {
            test(a-1);
        }
        System.out.println(a);
    }
}


解读:
第一次调用test,创建一个栈,传入4,判断4是否会大于2,4大于2进行第二次调用,4-1
第二次调用test,创建一个栈,传入3,判断3是否会小于2,3大于2进行第三次调用,3-1
第三从调用test,创建一个栈,传入2,判断2是否会大于2,2等于2进行返回
第一次返回,销毁栈,打印2,
第二次返回,销毁栈,打印3
第三次返回,销毁栈,打印4
结束程序

2
3
4

二、累加问题

给出一个整数,使用递归求出它累加的和。

public class Accumulation {
    public static void main(String[] args) {
        int a = Accumulation.test(3);
        System.out.println(a);
    }

    public static int test(int x) {
        if (x == 1) {
            return 1;
        } else {
            return x+test(x-1);
        }
    }
}


解读:
1.在main中第一次调用test,创建第一层栈,传入3,判断是否等于1,3大于1,符合递归条件,进行第二次调用test,3-1
2.第二次调用test,创建第二层栈,传入2,判断是否等于1,2大于1,符合递归条件,进行第三次调用test,2-1
3.第三次调用test,创建第三层栈,传入1,判断是否等于1,1等于1,符合基线条件,进行返回
4.第三层栈,返回1
5.第二层栈,返回3,2+1
6.第一层栈,返回6,3+2+1
程序结束

6

三、阶层问题
给出一个整数,使用递归求出它的阶乘。

public class Factorial {
    public static void main(String[] args) {
        int a = Factorial.test(5);
        System.out.println(a);
    }

    public static int test(int x){
        if (x == 1){ // 基线条件
            return 1;
        }else{ // 递归条件
            return test(x-1) * x;
        }
    }
}


递归解读:

1. 在main中第一次调用test,创建第一层栈,传入5,判断是否等于1,5大于1,符合递归条件,进行第二次调用test,5-1
2. 第二次调用test,创建第二层栈,传入4,判断是否等于1,4大于1,符合递归条件,进行第三次调用test,4-1
3. 第三次调用test,创建第三层栈,传入3,判断是否等于1,3大于1,符合递归条件,进行第四次调用test,3-1
4. 第四次调用test,创建第四层栈,传入2,判断是否等于1,2大于1,符合递归条件,进行第五次调用test,2-1
5. 第五次调用test,创建第五层栈,传入1,判断是否等于1,1等于1,符合基线条件,进行返回
6. 第五层栈,销毁栈,返回1,return 1;
7. 第四层栈,销毁栈,返回2,return test(2-1) * 2;
8. 第三层栈,销毁栈,返回6,return test(3-1) * 3;
9. 第二层栈,销毁栈,返回24,return test(4-1) * 4;
10. 第一层栈,销毁栈,返回120,return test(24-1) * 5;
11. 程序结束

120

四、斐波那契
用递归求出斐波那契数1,1,2,3,4,5,8,13...给一个整数x,求出它的值是多少。

public class Fibonacci {
    public static void main(String[] args) {
        int x = 4;
        int a = Fibonacci.test(x);
        if (a != -1){
            System.out.println("当x="+x+",对应的斐波那契是"+a);
        }
    }

    //思路分析:斐波那契是前两个数之和,且必须是整数。
    public static int test(int x) {
        if (x >= 1) { //斐波那契必须是整数
            if (x == 1 || x == 2) { //
                return 1;
            } else {
                return test(x - 1) + test(x - 2);
            }
        } else {
            System.out.println("要求输入的x必须是大于等于1的整数");
            return -1;
        }

    }
}


解读:
第一次调用test方法,向方法传递整数x(x=4)
判断x是否大于等于1(因为斐波那契数不能小于1),小于1,输出提示信息,并返回-1
大于等于1,则在判断x是否是1或2,是1或2则返回1;(因为1和2的斐波那契数是1)
否则进行第二次第一个调用test,创建栈,传入3 + 第二次第二个调用test,创建栈,传入2
第二次第一个test,进行x是否大于等于1的判断,3>=1,在判断是否会等于1或2,3不等于1或2,
进行第三次第一个调用test,创建栈,传入2,+ 第三次第二个调用test,创建栈,传入1
第三次第一个test,进行x是否大于等于1的判断,2>=1,在判断是否会等于1或2,2等于1或2,进行返回
第三次第二个test,进行x是否大于等于1的判断,1>=1,在判断是否会等于1或2,1等于1或2,进行返回
第二次第二个test,进行x是否大于等于1的判断,2>=1,在判断是否会等于1或2,2等于1或2,进行返回

第三次第一个test,返回,1
第三次第二个test,返回1
第二次第一个test,返回2
第二次第二个test,返回1
第一次返回调用处,返回3

3

 


五、猴子吃桃问题
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后的每天猴子都吃其中的一半,然后再多吃一个。
当第10天时,想再吃时(还没有吃),发现只有一个桃子了。问最初有多少个桃子。
 

public class MonkeyPeach {
    public static void main(String[] args) {
        int day = 1;
        int a = MonkeyPeach.test(day);
        if (a != -1) {
            System.out.println("第" + day + "天有" + a + "个桃");
        }
    }

    public static int test(int day) {
        if (day == 10) { // 第十天只有1个桃
            return 1;
        } else if (day >= 1 && day <= 9) { //天数必须在1-10天之内
            return (test(day + 1) + 1) * 2;
        } else {
            System.out.println("day必须在1-10之间");
            return -1;
        }
    }
}


思路分析:逆推
1. day10 有 1个桃子
2. day9  有 (day10+1)*2=4 个桃子  4/2-1=1
3. day8  有 (day9+1)*2=10 个桃子  10/2-1=4
规律就是 前一天的桃子 = (后一天的桃子 +1)*2

输入天数可以获取当天桃子的个数,输入6
第一次调用test(6),创建栈,出传入6,判断是否等于10,6不等于10,在判断是否大于等于1且小于等于9,6大于等于1且小于等于9,进行第二次调用test
第二次调用test(6+1),创建栈,传入7,判断是否等于10,7不等于10,在判断是否大于等于1且小于等于9,7大于等于1且小于等于9,进行第三次调用test
第三次调用test(7+1),创建栈,传入8,判断是否等于10,8不等于10,在判断是否大于等于1且小于等于9,8大于等于1且小于等于9,进行第四次调用test
第四次调用test(8+1),创建栈,传入9,判断是否等于10,9不等于10,在判断是否大于等于1且小于等于9,9大于等于1且小于等于9,进行第五次调用test
第五次调用test(9+1),创建栈,传入10,判断是否等于10,10等于10,进行返回,返回1
第五次调用,return 1,返回1,销毁栈
第四次调用,return (1 + 1) * 2,返回4,销毁栈
第五次调用,return (4 + 1) * 2,返回10,销毁栈
第五次调用,return (10 + 1) * 2,返回22,销毁栈
第五次调用,return (22 + 1) * 2,返回46,销毁栈
程序结束

46

六、汉诺塔问题
把圆盘从a塔移动到c塔,且小于圆盘要在上面。

public class HanoiTower {
    public static void main(String[] args) {
        Test test = new Test();
        test.move(5,'A', 'B', 'C');
    }
}

class Test{
    // num 表示要移动的个数,a,b,c 分别表示A塔 B塔 C塔
    public static void move(int num, char a, char b, char c){
        // 如果只有一个圆盘
        if (num == 1){
            System.out.println(a + "->" + c);
        }else{
            // 如果有多个圆盘,可以看成两个,最下面的和上面的所有圆盘(num-1)
            //(1)先移动上面所有的圆盘到b,借助c
            move(num-1, a, c, b);
            //(2)把最下面的这个圆盘,移动到c
            System.out.println(a + "->" + c);
            //(3)再把b塔的所有圆盘,移动到c,借助a
            move(num-1, b, a, c);
        }
    }
}

汉诺塔解读:
main创建第一层栈,第一次调用move,创建第二层栈,传入 3,A,B,C,判断3是否等于1,3不等于1,
则进行第二次调用move,创建第三层栈,传入 2,A,C,B,判断2是否等于1,2不等于1
则进行第三次调用move,创建第四层栈,传入 1,A,B,C,判断1是否等于1,1等于1,输出 A->C,销毁栈,返回第三层
第三层栈,输出 A->B,
第四次调用move,创建第四层栈,传入1,C,A,B,判断1是否等于1,1等于1,输出 C->B,销毁栈,返回第三层
销毁栈,返回到第二层
第二层栈,输出 A->C,
第二层栈,进行第五次调用move,创建第三层栈,传入2,B,A,C,判断2是否等于1,2不等于1
则进行第六次调用move,创建第四层栈,传入 1,B,C,A,判断1是否等于1,1等于1,输出 B->A,销毁栈,返回第三层
第三层栈,输出B->C
第三层,进行第七次调用move,创建第四层栈,传入1,C,B,A,判断1是否等于1,1等于1,输出 A->C,销毁栈,返回第三层
销毁栈,返回第二层
销毁栈,返回第一层调用处
结束程序

A->C
A->B
C->B
A->C
B->A
B->A
C->A

 ▲表示输出

 

七、老鼠出迷宫问题

public class Maze {
    public static void main(String[] args) {
        //创建迷宫,用二维数组表示 int[][] map = new int[8][7]
        //先规定 map数组的元素值:0表示可以走,1表示障碍物
        int[][] map = new int[8][7];

        // 设置障碍物
        //将最上面的一行和最下面的一行,全部设置为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;


        // 输出地图
        System.out.println("====== 迷宫地图 ======");
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }


        // 使用findWay给老鼠找路
        test t = new test();
        t.findWay(map,1,1);
        System.out.println("====== 找路情况如下 ======");
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
}

// 使用递归回溯解决老鼠出迷宫
// 1. findWay方法就是专门来找迷宫路径的
// 2. 如果找到返回true,没有找到返回false
// 3. map 就是二维数组,即表示迷宫
// 4. x,y就是老鼠的坐标,初始化为 (1, 1)
// 5. 因为是递归找路,所以先规定 map数组的各个值的含义
//    0表示可以走,1表示障碍物,2表示可以走,3表示走过,但走不通是死路
// 6. 当map[6][5]=2 就说明找到通路,就可以结束,否则就继续找。
// 7. 先确定老鼠找路策略 下-> 右-> 上-> 左
class test {
    public boolean findWay(int[][] map, int x, int y) {
        if (map[6][5] == 2) { //说明已经找到
            return true;
        } else {
            if (map[x][y] == 0) { //当前这个位置0,说明表示可以走
                // 我们可以假定走通
                map[x][y] = 2;
                // 使用找路策略,来确定该位置是否真的可以走通
                // 下-> 右-> 上-> 左
                if (findWay(map, x + 1, y)) { //下
                    return true;
                } else if (findWay(map, x, y + 1)) { //右
                    return true;
                } else if (findWay(map, x - 1, y)) { //上
                    return true;
                } else if (findWay(map, x, y - 1)) { //左
                    return true;
                } else {
                    map[x][y] = 3;
                    return false;
                }
            } else { //map[x][y] = 1, 2, 3
                return false;
            }
        }
    }
}


====== 迷宫地图 ======
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 1 1 1 1 1 1 
1 2 0 0 0 0 1 
1 2 2 2 0 0 1 
1 1 1 2 0 0 1 
1 0 0 2 0 0 1 
1 0 0 2 0 0 1 
1 0 0 2 2 2 1 
1 1 1 1 1 1 1 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值