算法基础课 2.1 递归训练

递归
栈结构 层层调用 层层返回
设计递归有三个步骤

  1. 找重复
  2. 找变化
  3. 找边界

public static void main(String[] args) {
        f(10);
    }
    //注意死循环
    static void f(int i) {
        if (i ==0) {
            return;
        }
        //调用自身
        f(i-1);
    }

n的阶乘

public static void main(String[] args) {
        System.out.println(f(4));
    }
/*  
 *  f(n):求n的阶乘 -- > f(n-1)求n-1的阶乘
 *  1.  找重复 n*(n-1)的阶乘,求n-1的阶乘是原问题的重复(规模更小)--子问题
    2.  找变化  变化的量应该作为参数
    3.  找边界 出口
    */
    static int f(int n) {
        if(n==1) {
            return 1;
        }
        return n * f(n-1);
    }

打印 i 到 j (委托思想 偷懒思想 我只做一部分(尽可能小的一部分) 剩下部分 我交给其他人做 )

    public static void main(String[] args) {
        f(10,20);
    }
    static void f(int i,int j) {
        if(i>j){
            return;
        }
        System.out.println(i);
        f(i+1,j);
    }

对arr的所有元素求和 (Array 数组)

public static void main(String[] args) {
        int res = f(new int[] {1,2,3,4,5},0);
        System.out.println(res);
    }
    static int f(int[] arr,int beign) {//这个变化当中去找参数 加参数是设计递归的一个难点 很多人切完蛋糕的一小块 不知道该怎么写 这时候你要想想是不是缺参数
                                     //begin是那个变化量 不停地往前推进 arr是一个常量
        if(beign == arr.length-1) { //当begin 等于数组最后一个的下标 就不用再求和 就直接返回那个值即可
            return arr[beign];
        }
        return arr[beign] + f(arr,beign+1);
    }
>>> 15

翻转字符串

public static void main(String[] args) {
        System.out.println(f("abcd",3)); // a b c d 反转 从后面切一刀 d + (a b c)以后同理  当然也可以从前面切一刀 但是好像没有变化的值了 
    }
    
    static String f(String src,int end) {
        if(end == 0) {
            return ""+src.charAt(end);//""+是为了转为String类型
        }
        return src.charAt(end)+f(src,end-1);
    }
>>>dcba

重复中找变化 ,变化中找重复


斐波那契数列
首先斐波那契数列 是这样的一个数列 1 1 2 3 5 8 13
每一项都是前两项的和 求第N项斐波那契数 这个N是第n项 本身无法参与运算 不像我们阶乘那个
这个也符合我们拆解成更小规模的子任务 ,但是这个地方要拆解成两个子任务
一个小弟去求n-1 一个小弟去求n-2 我只负责把他们合起来
这是递归的一种变体

递归包括:
分解为:直接量+小规模子问题
也可以分解为:多个(两个及两个以上)小规模子问题

这个问题 需要拆 和 凑
划一刀 解决一部分 再划一刀 再解决一部分 去拆去凑

public static void main(String[] args) {
        System.out.println(f(5)); 
    }
    static int f(int n) {
        if(n==1||n ==2) {
            return 1;
        }
        return f(n-1)+f(n-2);
    }
>>>5

画出递归示意图 你会发现很有意思的事情
之前 那些 比如求10的阶乘 他们是单路径 没有分支 一条路径走下来
但是我们做斐波那契数列时 开叉了
这个叫做递归解答树


这个是 每个点都要开叉
你会发现 有大量重复的求解 以后我们去优化
调用过程 比如 求解f(6)时 f5 和f4是同时进行的吗 显然不是
是先左子树 再右子树 返回来 再求兄弟(也是先求兄弟的左子树 再求兄弟的右子树)



最大公约数
辗转相除法
m % n等于0时 n是最大公约数,m%n 余数是k 则接下来 n%k 再进行判断
如果要写成公式则
f(m,n) = f(n,m%n)

public static void main(String[] args) {
        System.out.println(f(33,10));
    }
    static int f(int m,int n) {
        if(n==0) {
            return m;
        }
        return f(n,m%n);
    }


总而言之 你能找到问题的演变 推进的方式 本质上来讲都可以写成递推公式
只要在递推范围中再越缩越小 也就是在问题的规模在 越减越小 就是成功的递推设计


插入排序改递归

对数组的0~倒数第一个 排序
等价于:
对数组的0~倒数第二个元素,这部分排序
然后把最后一个元素插入到这个有序的部分中

public static void main(String[] args) {
        int[] arr = new int[]{3,2,4,1};
        f(arr,3);
        for(int i =0;i<=arr.length-1;i++) {
            System.out.println(arr[i]+" ");
        }
        
    }
    
    static void f(int[]arr,int k) {
        if(k ==0) {
            return;
        }
        //对前k-1部分进行排序
        f(arr,k-1);
        //对位置k的元素插入到前面的部分
        int x = arr[k];//假设是3241 保存数组序列号为k(这里是3)的值是1
        int index = k-1;//保存下一个要比较的序列号k-1(这里是2)
        while(index>-1&&x-1 因为1要和序列号为0的进行比较 比较后满足条件 arr[1]=arr[0] 后减1 index就变为-1了 
            arr[index+1] = arr[index];
            index--;
        }
        arr[index+1] = x;//注意这个坑 index+1 因为最后符合条件前多减了一次 ,index =-1此时是要把1插入到数组中 所以 index+1 为0
    }
>>>1 2 3 4 

总结

  • 找重复

    • 方法1找到一种划分方法
    • 方法2找到递推公式或者等价转换
      都是父问题转化为求解子问题
  • 找变化的量
    变化的量通常要作为参数
    如果找变化的量困难 要不是参数不够 要不就没找到隐含的参数

  • 找出口‘

所有的循环语句一定能改成递归 有的简单 有的难


汉诺塔
1-N从A移到B,C作为辅助 B为最终的目标柱子
等价于:
1、1~N-1从A移动到C B作为辅助
2、把N从A移动到B
3、1-N-1从C移动到B,A作为辅助
其中 每部 都直接是结果,不表示过程
难点就是N从A到B后 就可以把B作为空柱子 不用再管N 问题也就等价
最难想的就是转换柱子的角色 柱子的角色在变 把柱子作为参数就可以了
除了规模在变化以外 从哪到哪 以谁为辅助(柱子角色) 都在变化


图不全
    public static void main(String[] args) {
        f(3,"A","B","C");
    }
    /**
     * 将N个盘子从source移动到target的路径打印
     * @param N 初始的N个从小到大的盘中,N是最大的编号
     * @param from  原始柱子
     * @param to    目标柱子
     * @param help  辅助柱子
     */
    static void f(int N,String from,String to,String help) {
        if(N==1){
            System.out.println("move "+N+" from "+from+" to "+to);
            return;
        }
        f(N-1,from,help,to);//先把前N-1个盘子挪到辅助空间去
        System.out.println("move "+N+" from "+from+" to "+to);//N可以顺利到达目标柱子
        f(N-1,help,to,from);//让N-1个盘子回到目的空间去
      //注意调用方法,除了数值越变越小,这三个柱子的角色 也在互相变化  谁为参考点 谁为目标 它在变化
    }
>>>move 1 from A to B
>>>move 2 from A to C
>>>move 1 from B to C
>>>move 3 from A to B
>>>move 1 from C to A
>>>move 2 from C to B
>>>move 1 from A to B


你可能感兴趣的:(算法基础课 2.1 递归训练)