zoukankan      html  css  js  c++  java
  • 算法很美 笔记 2.递归与算法分析

    2.递归与算法分析

    递归

    • 递归设计经验
      找重复(子问题)
      找重复中的变化量→参数
      找参数变化趋势→设计出口

    • 练习策略
      循环改递归
      经典递归
      大量练习,总结规律,掌握套路
      找到感觉,挑战高难度

    1.求n的阶乘

      /**
       * f1(n):求n的阶乘-->f1(n-1)求n-1的阶乘
       * 找重复:n*(n-1)的阶乘,求n-1的阶乘是原问题的重复(规模更小)——子问题
       * 找变化:变化的量应该作为参数
       * 找边界:出口*/
      static int f1(int n) {
        if (n == 1)
          return 1;
        return n * f1(n - 1);
      }
    

    2.打印i到j

      /**
       * 打印i到j
       * 找重复:
       * 找变化:变化的量应该作为参数
       * 找边界:出口*/
      static void f2(int i, int j) {
        if (i > j)
          return;
        System.out.println(i);
        f2(i + 1, j);
      }
    

    3.对数组元素求和

      /**
       * 对arr的所有元素求和
       * 找重复:
       * 找变化:变化的量应该作为参数
       * 找边界:出口
       * @param arr
       */
      static int f3(int[] arr, int begin) {
        if (begin == arr.length - 1) {
          return arr[begin];
        }
        return arr[begin] + f3(arr, begin + 1);
      }
    

    4.翻转字符串

    //翻转字符串
      static String reverse(String src, int end) {
        if (end == 0) {
          return "" + src.charAt(0);
        }
        return src.charAt(end) + reverse(src, end - 1);
      }
    

    分解为:直接量+小规模子问题
    分解为:多个小规模子问题(斐波那契)

    5.斐波那契第n项

    //斐波那契第n项
    static int fib(int n){
        if(n==1||n==2){
            return 1;
        }
        return fib(n-1)+fib(n-2);
    }
    

    斐波那契数列问题
    等价于两个子问题:求前一项、求前二项
    两项求和

    6.辗转相除求最大公因数

    //辗转相除求最大公因数
    static int gcd(int m,int n){
        if(n==0){
            return m;
        }
        return gcd(n,m%n);
    }
    

    7.递归形式插入排序

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

    static void insertSort(int[] arr, int k) {
        if (k == 0) {
            return;
        }
        //对前k-1个元素排序
        insertSort(arr, k - 1);
        //把位置k的元素插入到前面的部分
        int x = arr[k];
        int index = k - 1;
        while (index > -1 && x < arr[index]) {
            arr[index + 1] = arr[index];
            index--;
        }
        arr[index + 1] = x;
    }
    

    8.汉诺塔

    1-N从A移动到B,C作为辅助
    等价于:
    1、1~N-1从A移动到C,B为辅助
    2、把N从A移动到B
    3、1-N-1从C移动到B,A为辅助
    在这里插入图片描述

    /**
    * 将N个盘子从source移动到target的路径的打印
    *
    * N      初始的N个从小到达的盘子,N是最大编号
    * source 原始柱子
    * target 辅助的柱子
    * help   目标柱子
    */
    static void printHanoiTower(int N, String source, String target, String help) {
        if (N == 1) {
            System.out.println("move " + N + " from " + source + " to " + target);
        } else {
            printHanoiTower(N - 1, source, help, target); // 先把前N-1个盘子挪到辅助空间上去
            System.out.println("move " + N + " from " + source + " to " + target);  // N可以顺利到达target
            printHanoiTower(N - 1, help, target, source); // 让N-1从辅助空间回到源空间上去
        }
    }
    
    printHanoiTower(3, "A", "B", "C");
    //从1-N从A移动到B,C为辅助
    

    9.二分查找递归解法

    全范围内二分查找
    等价于三个子问题:
    左边找(递归)
    中间比
    右边找(递归)
    注意:左查找和右查找只选其一

    static int binarySearch(int[] arr,int low,int high,int key){
        if(low>high)
            return -1;
        int mid=low+((high-low)>>1);
        int midVal=arr[mid];
        if(midVal<key){
            return binarySearch(arr,mid+1,high,key);
        }
        else if (midVal>key){
            return binarySearch(arr,low,mid-1,key);
        }else{
            return  mid;
        }
    }
    
    • 找重复
      1、找到一种划分方法
      2、找到递推公式或者等价转换
      都是父问题转化为求解子问题
    • 找变化的量
      变化的量通常要作为参数
    • 找到出口
      根据参数变化的趋势,对边界进行控制,适时终止递归

    算法复杂度

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • n!的弱.上界是n^n,因此增长速度非常快,这意味着单位时间内可求解的问题很小,换言之,超慢

    • 2^n这样的指数函数增长非常快,这种算法可以认为超慢

    • O(n2)和O(n3)增长很快,算法很慢,至少优化到nlgn,O(n2)的有冒泡排序,直接插入排序,选择排序

    • nlgn可以认为是及格的算法吧,一般分治法可以缩小层数为lgn,而每层的复杂度一般为O(n),例如归并排序算法、快速排序算法

    • O (n)叫做线性算法,这种算法比较优秀,或者问题本身比较简单,比如求连续求和最大子数组的线性解

    • O(sqrt(n))当然比O(n)更快,不是没有,但这种很少

    • lgn就是很优秀的算法了,比如二分查找法,但是这种算法往往对输入数据的格式是有要求的,二分查找要求输入数据有序

    • 还有一种是常量,无论规模怎么扩大,都花固定时间,这是为数极少的效率最高的算法了,多数是数据很规则

      递归算法复杂度

    递归关系 结果 举例
    T(n)=T(n/2)+O(1) T(n)=O(logn) 二分查找,辗转相除最大公因数
    T(n)=T(n-1)+O(1) T(n)=O(n) 线性查找
    T(n)=2T(n/2)+O(1) T(n)=O(n)
    T(n)=2T(n/2)+O(n) T(n)=O(nlogn) 归并、快排
    T(n)=2T(n/2)+O(nlogn) T(n)=O(n(logn)^2)
    T(n)=T(n-1)+O(n) T(n)=O(n^2) 选择排序、插入排序
    T(n)=2T(n-1)+O(1) T(n)=O(2^n) 汉诺塔
    T(n)=T(n-1)+T(n-2)+O(1) T(n)=O(2^n) 递归的斐波那契

    排序算法的稳定性

    • 稳定:如果a原本在b前面,而a=b ,排序之后a仍然在b的前面。
    • 不稳定:如果a原本在b的前面,而a=b ,排序之后a可能会出现在b的后面。

    算法稳定性

    排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
    插入排序 O(n^2) O(n^2) O(n) O(1) 稳定
    希尔排序 O(n^1.3) O(n^2) O(n) O(1) 不稳定
    选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
    堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定
    冒泡排序 O(n^2) O(n^2) O(n) O(1) 稳定
    快速排序 O(nlog2n) O(n^2) O(nlog2n) O(nlog2n) 不稳定
    归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定
    计数排序 O(n+k) O(n+k) O(n+k) O(n+k) 稳定
    桶排序 O(n+k) O(n^2) O(n) O(n+k) 稳定
    基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定

    题1:小白上楼梯(递归设计)

    小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶,2阶或者3阶,实现一个方法,计算小白有多少种走完楼梯的方式。

    提示:设n阶台阶的走法数为f(n)。如果只有1个台阶,走法有1种(一步上1个台阶),即f(1)=1;如果有2个台阶,走法有2种(一种是上1阶,再上1阶,另一种是一步上2阶),即f(2)=2;如果有3个台阶,走法有4种(一种每次1阶,共一种;另一种是2+1,共两种;第三种是3,共1种),即f(3)=4;

    当有n个台阶(n>3)时,我们缩小问题规模,可以这样想:最后一步有三种情况,走1步(之前上了n-1个台阶,走法为f(n-1)种),走2步(之前上了n-2个台阶,走法为f(n-2)种),走3步,(之前上了n-1个台阶,走法为f(n-3)种,f(n)=f(n-1)+f(n-2)+f(n-3),n>3

    import java.util.Scanner;
    
    public class _小白上楼梯 {
        public static void main(String[] args) {
            Scanner sc=new Scanner(System.in);
            while (true) {
                int N=sc.nextInt();
                int re=f(N);
                System.out.println(re);
            }
        }
        private static int f(int n) {
            if(n==1){
                return 1;
            }
            if(n==2){
                return 2;
            }
            if(n==3){
                return 4;
            }
            return f(n-1)+f(n-2)+f(n-3);
        }
    }
    

    题2 :旋转数组的最小数字(改造二分法)

    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入-一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一 个旋转,该数组的最小值为1.

    public class _旋转数组最小值 {
        static int f(int arr[]){
            int be=0;
            int end=arr.length-1;
            //没有旋转直接返回第一个
            if(arr[be]<arr[end]){
                return arr[be];
            }
            while (be+1<end){
                int mid=be+((end-be)>>1);
                if(arr[mid]>=arr[be]){//左边有序,最小值在右边(无序)
                    be=mid;
                }else{
                    end=mid;
                }
            }
            return arr[end];//最后剩两个元素,右边的位最小值
        }
    
        public static void main(String[] args) {
            System.out.println(f(new int[]{4,5,6,2,3}));
        }
    }
    

    题3 :在有空字符串的有序字符串数组中查找

    有个排序后的字符串数组,其中散布着一些空字符串,编写-一个方法,找出给定字符串(肯定不是空字符串)的索引。

    1. begin end

    2. while

      1. 取中值
      2. 对于出现空串的处理
      3. 比较改变begin或end
    3. return -1

    public class _在有空字符串的有序字符串数组中查找 {
        static int index(String[] arr,String p){
            int begin=0;
            int end=arr.length-1;
            while(begin<end){
                //取中值
                int mid=begin+((end-begin)>>1);
                //对于出现空串的处理
                while(arr[mid].equals("")){
                    mid++;
                    if(mid>end){//防止死循环
                        return -1;
                    }
                }
                //比较改变begin或end
                if(arr[mid].compareTo(p)>0){
                    end=mid-1;
                }else if(arr[mid].compareTo(p)<0){
                    begin=mid+1;
                }else{
                    return mid;
                }
            }
            return -1;
        }
    
        public static void main(String[] args) {
            String[] arr = {"a", "", "ac", "", "ad", "b", "", "ba"};
            int res = index(arr, "abc");
            System.out.println(res);
        }
    }
    
    

    题4 :最长连续递增子序列(部分有序)

    (1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8)。

    输入: [1,3,5,4,7]
    输出: 3
    解释: 最长连续递增序列是 [1,3,5], 长度为3。
    尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
    
    输入: [2,2,2,2,2]
    输出: 1
    解释: 最长连续递增序列是 [2], 长度为1。
    
    public class _最长连续递增子序列 {
        static int findLengthOfLCIS(int[] nums) {
            if(nums.length == 0)
                return 0;
            int max = 0;
            int count = 1;
            for(int i=0;i<nums.length - 1;i++){
                if(nums[i] < nums[i+1]){
                    count++;
                }else{
                    max = Math.max(count,max);
                    count = 1; 
                }
            }
            max = Math.max(count,max);
            return max;
        }
        public static void main(String[] args) {
            System.out.println(findLengthOfLCIS(new int[]{1,3,5,4,7}));
        }
    }
    
    

    题5:设计一个高效的求a的n次幂的算法

    static int pow(int a, int n) {
        if (n == 0) return 1;
        int res = a;
        int ex = 1;
        //能翻
        while ((ex << 1) <= n) {
          //翻
          res = res * res;
          //指数
          ex <<= 1;
        }
        //不能翻
        //差n-ex次方没有去乘到结果里面
        return res * pow(a, n - ex);
      }
    
  • 相关阅读:
    jquery ajax和php实现返回值 json类型
    jQuery一句话实现多选框全选/取消
    九宫格拼图 支持44 55等
    php通过$_SERVER['HTTP_USER_AGENT']获取浏览器useAgent
    微信刮刮卡代码
    从QQ网站中提取的纯JS省市区三级联动
    Codeigniter+PHPExcel导出数据到Excel文件
    flv视频播放器停止时带图片
    html5+js实现刮刮卡效果
    解决Tensorflow 使用时cpu编译不支持警告:that this TensorFlow binary was not compiled to use: AVX AVX2
  • 原文地址:https://www.cnblogs.com/cxynb/p/12269452.html
Copyright © 2011-2022 走看看