算法学习系列(1)——时间复杂度,冒泡,选择,插入,归并排序,小和问题,逆序对

1.时间复杂度

常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
时间复杂度为一个算法流程中,常数操作数量的指标。常用O(读作big O)来表示。具体来说,在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果记为f(N),那么时间复杂度为O(f(N))。
评价一个算法流程的好坏先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是常数项时间。

举例:
1.一个有序数组A,另一个无序数组B,请打印B中的所有不在A中的数,A数组长度为N,B数组长度为M。
算法流程1:对于数组B中的每一个数,都在A中通过遍历的方式找一下;
对于这个算法流程,其时间复杂度为O(N*M),因为在运算的时候,用的是for(B)for(A)的遍历循环。
2.只要高阶项,不要低阶项,也不要高阶项的系数,这句话的理解:找出一个数组中的最小的数放到第一位,需要进行 N+(N-1)+(N-2)+(N-3)+…+3+2+1 这么多次的操作,也就是(N+1)N/2,( N 2 N^{2} / 2+N/2),按照上边的规则,此算法的时间复杂度就是O(N(2))
3.二分查找法的算法复杂度:O(log2n),以2为底。在算法学习中,不写以什么为底的时候,就是以2为底的。
.对于例子一使用不同的流程:
算法流程2:对于数组B中的每一个数,都在A中通过二分的方式找一下;
其时间复杂度为:**O(M
logn)**
4.对于例子一使用不同的流程:
算法流程3:先把数组B排序,然后用类似外排的方式打印所有在A中出现的数;
算法的大概理解如下图所示,即开始的时候在A数组的第一个数字上添加一个指针,在B数组上的第一个数字也添加一个指针,先移动a指针,然后同时准备移动b指针,只要 b 指针指向的数小于等于a的,b指针就移动,否则就直接打印出该数字:
在这里插入图片描述
对于该例子,其时间复杂度为:O(M*logM)+O(M+N)

2.典型排序算法的时间复杂度

1) 冒泡排序的时间复杂度O( N 2 N^{2} )

public class Code_00_BubbleSort {

    public static void bubbleSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        for(int end = arr.length - 1; end > 0;end--){
            for(int i = 0;i < end;i++){
                if(arr[i] > arr[i+1]){
                    swap(arr,i,i+1);
                }
            }
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {2,1,4,3,9,5,6};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

2).选择排序的算法复杂度O( N 2 N^{2} )

public class Code_01_SelectionSort {

    public static void selectionSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        for(int i = 0;i < arr.length;i++){
            int minIndex = i;
            for(int j = i+1;j < arr.length;j++){
                minIndex = arr[j] < arr[minIndex] ? j :minIndex;
            }
            swap(arr,i,minIndex);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {2,1,4,3,9,5,6};
        selectionSort(arr);
        System.out.println(Arrays.toString(arr));
    }

}

3).插入排序

此算法的时间复杂度和数据的状况是有关系的,比如说,最好情况下(1,2,3,4,5)这是一个排好序的数组,使用插入排序的时间复杂度就是O(N),但是对于最坏的情况(5,4,3,2,1)要求从小到大排序的时候,时间复杂度就是O( N 2 N^{2} ),一般对于这种不确定的算法都按最差的情况来评价,就是说插入排序的算法复杂度为O( N 2 N^{2} )

public class Code_02_InsertSort {

    public static void insertSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        for(int i = 1;i < arr.length;i++){
            for(int j = i-1;j >= 0 && arr[j] >= arr[j+1];j--){
                swap(arr,j,j+1);
            }
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {2,1,4,3,9,5,6};
        insertSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

3.对数器(设计评价算法正确与否的工具)

假如是测试排序算法的,可以按照以下步骤来进行:
1)准备一个随机数组发生器;
2)准备一个绝对正确的排序算法(如Arrays.sort()),这个算法的时间复杂度可以很差,但是一定要正确的。
3)准备进行大量本测试(需要复制一个原数组,以方便后边的比较测试),此外还需要一个比较是否相同的方法
为上边的插入排序做一个对数器:

public class Code_02_InsertSort {

    public static void insertSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        for(int i = 1;i < arr.length;i++){
            for(int j = i-1;j >= 0 && arr[j] >= arr[j+1];j--){
                swap(arr,j,j+1);
            }
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    //for test
    public static int[] generateRandomArray(int size,int value){
        int[] arr = new int[(int) ((size+1) * Math.random())];//[0,size]
        for(int i = 0;i < arr.length;i++){
            arr[i] = (int)((value+1) * Math.random()) - (int)(value * Math.random());
        }
        return arr;
    }

    //for test
    public static void rightSortWay(int[] arr){
        Arrays.sort(arr);
    }

    //for test
    public static boolean isEqual(int[] arr1,int[] arr2){
        if((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)){
            return false;
        }
        if (arr1 == null && arr2 == null){
            return true;
        }
        if(arr1.length != arr2.length){
            return false;
        }
        //比较每个元素
        for(int i = 0;i < arr1.length;i++){
            if(arr1[i] != arr2[i]){
                return false;
            }
        }
        return true;
    }

    //for test
    public static int[] copyArray(int[] arr){
        int[] arrNew = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            arrNew[i] = arr[i];
        }
        return arrNew;
    }

    public static void main(String[] args) {
//        int[] arr = {2,1,4,3,9,5,6};
//        insertSort(arr);
//        System.out.println(Arrays.toString(arr));
        int size = 10000;
        int value = 100;
        boolean successed = false;
        int[] arr1 = generateRandomArray(size,value);
        int[] arr2 = copyArray(arr1);
        int[] arr3 = copyArray(arr1);
        System.out.println("原数组:");
        System.out.println(Arrays.toString(arr1));
        System.out.println("使用插入排序:");
        insertSort(arr2);
        System.out.println(Arrays.toString(arr2));
        System.out.println("使用正确的排序方法:");
        rightSortWay(arr3);
        System.out.println(Arrays.toString(arr3));
        successed = isEqual(arr2,arr3);
        System.out.println("比较的结果是:"+successed);
    }

}

笔试的时候应该准备二叉树的对数器(模板),数组的对数器,堆的,排序的。。。

4.递归的理解(分治思想)

时间复杂度:使用公式推断出来:
master公式的使用 :T(N) = aT( n b \frac {n}{b} )+O( N d N^{d} )

  1. log(b,a) > d -> 复杂度为O(N^log(b,a))
  2. log(b,a) = d -> 复杂度为O(N^d * logN)
  3. log(b,a) < d -> 复杂度为O(N^d)
    举个简单例子:
public class Test_GetMax {

    public static int getMax(int[] arr,int left,int right){
        if(left == right){
            return arr[left];
        }
        int mid = (left + right)/2;
        int leftMax = getMax(arr,left,mid);
        int rightMax = getMax(arr,mid + 1,right);
        return Math.max(leftMax,rightMax);
    }

    public static void main(String[] args) {
        int[] arr = {1,2,3,5,8};
        System.out.println(getMax(arr,0,arr.length - 1));
    }

}

这里获取的是全局的最大值,做法是先获取左边的最大,然后右边的最大,然后比较得出全局最大的。按照上边的公式,此算法的时间复杂度为: T(N) = 2T( N 2 \frac {N}{2} )+O(1)
===》 log(2,2) = 1 > d=0
===》时间复杂度为O(N^log(b,a))

5.递归算法的经典算例

5.1 归并排序

public class Code_03_MergeSort {

    public static void mergeSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        mergeCore(arr,0,arr.length - 1);
    }

    public static void mergeCore(int[] arr,int l,int r){
        if(l == r){
            return;
        }
        int mid = (l + r)/2;
        mergeCore(arr,l,mid);
        mergeCore(arr,mid + 1,r);
        mergeProcess(arr,l,mid,r);
    }

    public static void mergeProcess(int[] arr,int l,int mid,int r){
        int help[] = new int[r - l + 1];
        int p1 = l;
        int p2 = mid + 1;
        int i = 0;
        //把最小值放到辅助数组中
        while(p1 <= mid && p2 <= r){
            help[i++] = arr[p1] < arr[p2]?arr[p1++]:arr[p2++];
        }
        //p1或者p2肯定有且只有最先越界的
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while (p2 <= r){
            help[i++] = arr[p2++];
        }
        //把辅助数组拷贝进去原数组
        for(int j = 0;j < help.length;j++){
            arr[l+j] = help[j];
        }
    }

    public static void main(String[] args) {
        int[] arr = {2,1,4,3,9,5,6};
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
    }

}

master公式为:T(n) = 2T(n/2)+O(n),因此时间复杂度就是O(N^d * logN) ===》O(N * logN)

5.2 笔试常用——小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。

例子:
[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16

注意位运算:其实mid=(l+r)/2是不安全的,mid = l+(r-l)/2才是安全的,这可以写成mid=l+(r-l)>>1,位运算运算的速度比较快。
Demo:

public class Code_04_SmallSum {

    public static int mergeSum(int[] arr){
        if(arr == null || arr.length < 2){
            return 0;
        }
        return mergeCore(arr,0,arr.length - 1);
    }

    public static int mergeCore(int[] arr,int l,int r){
        if(l == r){
            return 0;
        }
        int mid = (l + r)/2;
        return mergeCore(arr,l,mid)
                +mergeCore(arr,mid + 1,r)
                +mergeProcess(arr,l,mid,r);
    }

    public static int mergeProcess(int[] arr,int l,int mid,int r){
        int help[] = new int[r - l + 1];
        int p1 = l;
        int p2 = mid + 1;
        int i = 0;
        int res = 0;
        //把最小值放到辅助数组中
        while(p1 <= mid && p2 <= r){
            res += arr[p1] < arr[p2] ? (r - p2 + 1)*arr[p1] : 0;
            help[i++] = arr[p1] < arr[p2]?arr[p1++]:arr[p2++];
        }
        //p1或者p2肯定有且只有最先越界的
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while (p2 <= r){
            help[i++] = arr[p2++];
        }
        //把辅助数组拷贝进去原数组
        for(int j = 0;j < help.length;j++){
            arr[l+j] = help[j];
        }
        return res;
    }

    public static void main(String[] args) {
        int[] arr = {1,3,4,2,5};
        int mergeSum = mergeSum(arr);
        System.out.println(Arrays.toString(arr));
        System.out.println(mergeSum);
    }

}

5.2 笔试常用——逆序对问题

在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序对。
如{1,3,4,2,5}: =====》{3,2},{4,2}
Demo:

public class Code_05_InversePairs {

    //所有流程都喝归并排序的一样的,这里就多了一个在贵哦病的过程中进行一个逆序对的输出
    public static void mergeSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        mergeCore(arr,0,arr.length - 1);
    }

    public static void mergeCore(int[] arr,int l,int r){
        if(l == r){
            return;
        }
        int mid = (l + r)/2;
        mergeCore(arr,l,mid);
        mergeCore(arr,mid + 1,r);
        mergeProcess(arr,l,mid,r);
    }

    public static void mergeProcess(int[] arr,int l,int mid,int r){
        int help[] = new int[r - l + 1];
        int p1 = l;
        int p2 = mid + 1;
        int i = 0;
        //把最小值放到辅助数组中
        while(p1 <= mid && p2 <= r){
            //在这里直接输出逆序对
            if(arr[p1] > arr[p2]){
                for(int k = p1;k <= mid;k++){
                    System.out.println("逆序对:["+arr[k]+","+arr[p2]+"]");
                }
            }
            help[i++] = arr[p1] < arr[p2]?arr[p1++]:arr[p2++];
        }
        //p1或者p2肯定有且只有最先越界的
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while (p2 <= r){
            help[i++] = arr[p2++];
        }
        //把辅助数组拷贝进去原数组
        for(int j = 0;j < help.length;j++){
            arr[l+j] = help[j];
        }
    }

    public static void main(String[] args) {
        int[] arr = {2,1,4,3,9,5,6};
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
    }

}
发布了66 篇原创文章 · 获赞 1 · 访问量 2449

猜你喜欢

转载自blog.csdn.net/qq_36079912/article/details/103850522