算法很美(二)---递归

递归

  • 找重复 : 找出子问题,并把原问不断分解成子问题
  • 找变化 : 找出原问题和子问题之间有哪些变化
  • 找边界 : 找出递归退出的点,不能无限循环

第一部分:递归基础

  • 递归求一个数的阶层
public class Hello {//求某个数的阶层
    public static int ff1(int n){
        if(n==0)
            return 1;
        return n * ff1(n-1);
    }
    public static void main(String[] args) {
        System.out.println(ff1(3));
    }
}
  • 打印i到j的值
public class Hello {//打印i到j的值
    public static void ff1(int i,int j){
        if(i==j)
            return;
        System.out.println(i);
        ff1(i+1,j);
    }
    public static void main(String[] args) {
        ff1(3,9);
    }
}
  • 数组求和
public class Hello {//数组求和
    public static int ff1(int arr[], int i){
        if (i==arr.length-1)
            return arr[arr.length-1];
        return arr[i] + ff1(arr,i+1);
    }
    public static void main(String[] args) {
        int arr[] = {1,2,3,4,5,6,7,8,9,10}; //自定义了一个数组
        int i = 0;

        System.out.println(ff1(arr, i));
    }
}
  • 翻转字符串
public class Hello {//翻转字符串
    public static String ff1(String str, int j){
        if (j==0){
            return str.charAt(j)+"";
        }
        return str.charAt(j)+ff1(str,j-1);
    }
    public static void main(String[] args) {
        String str1 = "abcdefg";
        String str2 = "i love you";
        System.out.println(ff1(str1,str1.length()-1));
        System.out.println(ff1(str2,str2.length()-1));
    }
}
  • 斐波那契数列
  • O(2^n) ,这种类似的还有3,8(八皇后问题)的n次方阶
  • 为什么是这个时间复杂度呢?
  • 因为每次递归,都分成了两个全新的部分,而全新的部分还都要进行计算,1、2、4、8…
public class Hello {//递归完成斐波拉契数列
    public static int ff1(int j){
        if(j==2||j==1)
            return 1;
        return ff1(j-1)+ff1(j-2);
    }
    public static void main(String[] args) {
        System.out.println(ff1(5));
    }
}
  • 最大公约数
  • log(n)阶 (由数学公式推导出来,大概每两次,n削减一半)
  • 为什么是这个时间复杂度?
m n
n m%n (根据数学推导 m%n < m/2)
m%n n%(m%n) (根据数学推导 n%(m%n) < n/2)
也就是说每隔两个数就衰减一半,
	
次数 规模
2次 n/2
4次 n/2^2
6次 n/2^3
...
k次 n/2^(k/2)

再取对数...
故与log(n)阶类似。
public class Hello {//最大公约数
    public static int ff1(int m, int n){
        if (m%n == 0)
            return n;
        else
            return ff1(n, m%n); //辗转相除法
    } 
    public static void main(String[] args) {
        System.out.println(ff1(4,3)); //控制m比n大就好了
    }
}
  • 插入排序改递归
  • o(n^2) : 想象一下,插入排序与冒泡大体类似,都是需要用一个元素,与剩下的元素逐一比较,而每次都用一个元素,这就相当于两个for循环了。
public class Hello {//插入排序改递归
    public static void ff1(int arr[], int k){
        if (k==0){
            return;
        }
        ff1(arr,k-1); //将前k-1个元素进行排序
        int x = arr[k];
        int index = k-1;
        while (x<arr[index]){
            arr[index+1] = arr[index]; // 比k大的元素应该后移
            index--;
            if(index==-1)
                break;
        }
        arr[index+1] = x;
    }
    public static void main(String[] args) {
        int arr [] = {3,6,2,8,4,9};
        ff1(arr, arr.length-1);
        for(int i = 0;i<arr.length;i++){
            System.out.print(arr[i]);
        }
    }
}
  • 汉诺塔问题
  • O(2^n)
  • 发现每次调用函数,都是上次调用的两倍,与斐波那契数列类似。
public class Hello {//汉诺塔问题
    public static void hanno(int N, String A, String B, String C){
        if (N==1){
            System.out.println(N+"by"+A+"--->"+B);
        }else{
            hanno(N-1, A, C, B);
            System.out.println(N+"by"+A+"--->"+B);
            hanno(N-1,C, B, A);
        }
    }

    public static void main(String[] args) {
        hanno(6,"A", "B", "C");
    }
}
  • 二分查找
  • log(n)阶
  • 为什么是这个时间复杂度?
  • 每次都找一半,理解为2^n(你查找的次数) = x(总次数),这样再取对数,即为log(n)阶
public class Hello {//二分查找递归解法
    public static int ff1(int arr[], int l, int r, int key) {
         if (l > r)
            return -1;
         else if (key > arr[(l+r)/2])
            return ff1(arr, (l + r) / 2 + 1, r, key);
         else if(key < arr[(l+r)/2])
            return ff1(arr, l, (l + r) / 2, key);
         else
            return (l+r)/2;
    }
    public static void main(String[] args) {
        int array[] = {1,2,3,4,5,6};
        System.out.println(ff1(array,0,5,3));
    }
}
  • 希尔排序
  • nlog(n)~n^2之间
  • 这里元素少与多是基于希尔排序的分组来定义的,并不是真实的元素数量。
  • 如果排序的元素非常不好,也就是元素少的时候,有序,而元素多的时候,无序,导致
  • 为什么快?:元素少的时候,无序,元素多的时候,已经基本有序。
public class Hello {//希尔排序
    public static int[] ff1(int arr[]){
        for(int interval = arr.length/2; interval>0 ;interval/=2){//控制每一次的排序量
            for(int j = interval;j< arr.length; j++){
                int num = arr[j];
                int index = j-interval;
                while(j>-1&&num<arr[index]){
                    arr[index+interval] = arr[index];       //这就是插入排序, 与之前的插入排序是一样的
                    index -= interval;
                    if(index<0){
                        break;
                    }
                }
                arr[index+interval] = num;
            }
        }
        return arr;
    }
    public static void main(String[] args) {
        int array[] = {3,7,2,9,4,1};
        array = ff1(array);
        for(int i = 0;i< array.length;i++){
            System.out.println(array[i]);
        }
    }
}
  • 希尔排序最不喜欢的状态
    在这里插入图片描述
  • 这个导致希尔排序的时间复杂度为O ( n ^ 2 ),因为后面很大一部分元素要进行插入,加上前面的几次遍历,时间复杂度为O ( n ^ 2)

  • 希尔排序最喜欢的状态
    在这里插入图片描述
  • 当然这里代表一部分情况,只要是大体有序都可以达到这种水平(大体有序指大的数字靠右,且这里假设每次最多交换一次,即每次都为O(n ^ 2),层数是由元素个数/2决定的,是log (n),二者相乘即为最小的时间复杂度。

第二部分、算法复杂度及稳定性

  • 2的幂表
    在这里插入图片描述
  • 如何评估算法复杂度
    在这里插入图片描述
  • 排序算法的稳定性
    在这里插入图片描述
    在这里插入图片描述

第三部分、递归例题

  • 题1:小白上楼梯(递归设计)
  • 小白正在上楼梯, 楼梯有n阶台阶, 小白一次可以上1阶, 2阶或者3阶 , 实现一个方法,计算小白有多少中走完楼梯的方式。
  • 思路:想象一下走完全程,倒退一阶台阶,二阶,三阶,在这三阶台阶上,都可以一步走完全程,故得到递推式 :f(n) = f(n-1) + f(n-2) + f(n-3) ,这里指的是方法数相加 ,是方法数的递推(即走完楼梯的方式),与斐波那契数列非常相似,这里的算法时间复杂度是 O(n^3),而且注意控制出口,即n=1,n=2,n=3的时候都要控制。
  • 代码如下:
import java.util.Scanner;

public class Hello {//小白上楼梯 : 递归设计
    public static int ff1(int n){
        if (n==0) return 1;
        else if (n==1) return 1;
        else if (n==2) return 2; // 因为可能先走一步,再走一步, 或者直接走两步
        else
            return ff1(n-1) + ff1(n-2) + ff1(n-3);
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while(true) {
            int num = sc.nextInt();
            int req = ff1(num);
            System.out.println(req);
        }
    }
}
  • 题2:旋转数组的最小数字(改造二分法)
  • 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
  • 思路 : 我们发现旋转数组中的最小元素是在将数组进行二分切割后无序的那一半(看有序还是无序只要比较数组开头和结尾的元素的大小即可),而最小的那个元素又总是在最大的元素的右边,即当只剩下两元素的时候,右边那个就是最小的元素
  • 注意这种方法的弊端很大,比如旋转数组10111测试出错误答案等等…emm
  • 代码如下:
import java.util.Scanner;

public class Hello {//旋转数组的最小元素
    public static void ff1(int arr[]){
       int begin = 0;
       int end = arr.length - 1;

       //应该没有这种特殊的旋转
       if (arr[begin]<arr[end]) System.out.println(arr[begin]);;

       while (true){
           int mid = (begin + end) / 2;
           if (arr[begin]<arr[mid]){ //左边有序, 去右边找
               begin = mid;
           }else if(arr[mid]<arr[end]){ //右边有序, 去左边找
               end = mid;
           }else if(begin+1==end){
               System.out.println(arr[end]);
               break;
           }
       }

    }

    public static void main(String[] args) {
        int arr[] = {2,3,4,0,1};
        ff1(arr);
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值