s002-认识O(NlogN)的排序

s002-认识O(NlogN)的排序

什么是Master公式?

T(N)=a*T(N/b)+O(N^d)
母问题的数据量是N级别,即母问题是N规模的;子问题是N/b的规模,a是子问题的个数, O(N^d)是除了子问题之外剩下问题的复杂度是多少,满足这个条件的递归公式可以通过master公式来统一求解问题的时间复杂度,使用master公式需要保证子问题等规模

master公式如何求解时间复杂度

如果 $log^a_b<d$ 时间复杂度 $O(N^d)$
如果 $log^a_b>d$ 时间复杂度 $O(N{log_ba})$
如果 $log^a_b==d$ 时间复杂度 $O(N^d*logN)$

归并排序

原理

  1. 整体就是一个简单递归,左边排好序、右边排好序、让其整体有序
  2. 让其整体有序的过程里用到了外排序方法,外排序指的是利用外部的数组空间,将左边排好序的数组和右边排好序的数组通过两边指针移动的方法进行排序
  3. 利用master公式来求解时间复杂度
  4. 并归排序的实质

复杂度
时间复杂度:$O(NlogN)$
空间复杂度:$O(N)$

实现

package com.kuang.example01;

/**
 * 本类中实现一些时间复杂度为NlogN的排序方法
 *
 * @since 2022-10-09
 */
public class NlogN {

    public void process(int[] arr, int left, int right){
        if(left == right){
            return;
        }
        int mid = left + ((right-left)>>1);
        process(arr,left,mid);
        process(arr,mid+1, right);
        merge(arr,left,mid,right);
    }

    public void merge(int[] arr, int left, int mid, int right){
        int[] help = new int[right-left+1];
        int p1 = left;
        int p2 = mid+1;
        int i = 0;
        while(p1<=mid && p2<=right){
            help[i++]= arr[p1]<= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1<=mid){
            help[i++]= arr[p1++];
        }
        while (p2<=right){
            help[i++]= arr[p2++];
        }
        for (int j = 0; j < help.length; j++) {
            arr[left+j]=help[j];
        }
    }

    public static void main(String[] args) {
        int[] arr = {2,5,4,3,6,2,3,5,6,9,5,4};
        NlogN nlogN = new NlogN();
        nlogN.process(arr,0, arr.length-1);
        System.out.println(arr.toString());
    }

}

小和问题
问题描述
求一个数组的所有的左边比右边小的数的和

通常思路:两重for循环第一轮遍历数组中每个元素,第二轮遍历数组中第一轮元素的所有前面的数,进行比较大小,求和。 时间复杂度$O(n^2)$
遇到$O(n^2)$的时间复杂度,就要思考是否能转化为$O(nlogn)$的时间复杂度
归并排序是很好的降时间复杂度的方法,其优势在于没有浪费比较,对于merge阶段,会利用双指针进行再次排序,之前的排序结果没有被浪费

并归思路
合并的时候,如果后一个数组的当前指针的值比前一个数组的当前指针的值大则结果需要加上前一个数组的当前值*(后一个数组的长度-当前指针+1),这个是整个问题的核心,另外,如果前数组当前值和后数组当前值相等,拷贝后面的数组
这里推荐左程云的视频

实现

package com.kuang.example01;

/**
 * 功能描述
 *
 * @since 2022-10-11
 */
public class SmallSum {
    public int process(int[] arr, int l, int r) {
        if(l==r){
            return 0;
        }
        int mid = l+ ((r-l)>>1);
        return process(arr, l, mid)+process(arr, mid+1,r)+merge(arr, l ,mid,r);
    }

    public int merge(int[] arr, int l ,int mid,int r){
        int ptr1 = l;
        int ptr2 = mid+1;
        int i = 0;
        int result= 0;
        int[] temp = new int[r-l+1];
        while(ptr1<=mid && ptr2<=r){
            if(arr[ptr1]>arr[ptr2]){
                temp[i++] = arr[ptr2++];
            } else if(arr[ptr1]==arr[ptr2]) {
                temp[i++] = arr[ptr2++];
            } else {
                result+=arr[ptr1]*(r-ptr2+1);
                temp[i++] = arr[ptr1++];
            }
        }
        while(ptr1<=mid){
            temp[i++] = arr[ptr1++];
        }
        while(ptr2<=r){
            temp[i++] = arr[ptr2++];
        }
        for (int j = 0; j < temp.length; j++) {
            arr[l+j]=temp[j];
        }
        return result;
    }

    public static void main(String[] args) {
        SmallSum smallSum = new SmallSum();
        int[] arr = {1,3,4,2,5};
        int process = smallSum.process(arr, 0, 4);
        System.out.println(process);

    }

}

快排

原理
快排有三个版本
快排3.0版本是时间复杂度为$O(nlogn)$的算法

快排解决的是荷兰国旗问题,荷兰国旗是由三种颜色的竖条组成的,快排的思想是将数组的最后一个值作为目标值,经过一次遍历把小于这个值的数都放到数组的左边,等于这个值的数都放在中间,大于这个值的数都放在右边

简单讲一下快排三个版本的区别

  1. 快排1.0: 将数组的最后一个值作为目标值,经过一次遍历把小于等于这个值的数都放到数组的左边,大于这个值的数都放在右边, 每遍历一次,只排好了一个数
  2. 快排2.0: 将数组的最后一个值作为目标值,经过一次遍历把小于这个值的数都放到数组的左边,等于这个值的数都放在中间,大于这个值的数都放在右边, 每遍历一次,排好了等于目标值的所有数
  3. 快排3.0: 在数组中随机选择一个数,将其与数组的最后一个值交换,将数组的最后一个值作为目标值,经过一次遍历把小于这个值的数都放到数组的左边,等于这个值的数都放在中间,大于这个值的数都放在右边, 与2.0的差别在于目标值是随机选取的

原理核心
快排的核心是如何经过一次遍历将小于目标值的数放在左边,等于目标值的数放在中间,大于目标值的数放在右边
最开始 小于目标值的边界是0,大于目标值的边界是arr.length-1 当前数组下标0
如果当前数组值<目标值,将当前数组值与小于目标值的边界值交换,小于目标值的边界++,当前数组下标++
如果当前数组值==目标值,当前数组下标++
如果当前数组值>目标值,将当前数组值与大于目标值的边界值交换,大于目标值的边界--

复杂度
时间复杂度:$O(NlogN)$
空间复杂度:$O(logN)$

快排1.0和2.0的最差时间复杂度是$O(n^2)$ 可以构造无数个排列好的有序的数组进行快排算法,导致其时间复杂度是$O(n^2)$
快排3.0避免了这个问题,使得其时间复杂度是$O(nlogn)$,具体的证明就不给出了,需要用到数学中概率论的知识
快排1.0和2.0的最差空间复杂度是$O(n)$,快排中额外用到的空间是小于目标值的边界和大于目标值的边界,总的数量与递归压栈的层数有关,最差就是数组有序的情况,每个数据都被压栈
快排3.0避免了这个问题,使得其空间复杂度是$O(logn)$,具体的证明就不给出了,需要用到数学中概率论的知识

实现
直接实现快排3.0的代码

package com.kuang.example01;

/**
 * 功能描述
 *
 * @since 2022-10-21
 */
public class QuickSort {
    public void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public int[] partition(int[] arr, int l, int r){
        int tar = arr[r];
        int minBoard = l;
        int maxBoard = r;
        int i = minBoard;
        while (i<= maxBoard){
            if(arr[i]<tar){
                swap(arr, i++, minBoard++);
            } else if(arr[i]==tar){
                i++;
            } else {
                swap(arr, i, maxBoard--);
            }
        }
        return new int[]{minBoard, maxBoard};
    }

    public void quickSort(int[] arr, int l, int r){
        if(l<r){
            int s = (int) (Math.random()*(r-l+1));
            swap(arr, l+s, r);
            int[] result = partition(arr, l, r);
            quickSort(arr, l,result[0]-1);
            quickSort(arr, result[1]+1, r);
        }
    }

    public static void main(String[] args) {
        int[] arr = {2,5,6,4,3,6,8,6};
        QuickSort quickSort = new QuickSort();
        quickSort.quickSort(arr,0,7);
        System.out.println(arr);
    }
}
posted @ 2022-10-21 20:37  Oh,mydream!  阅读(22)  评论(0编辑  收藏  举报