认识O(NlogN)的排序_笔记02


本文知识点记录自左程云 算法与数据结构基础到高级教程 图片截取自视频中

04_归并排序

时间复杂度:O(N*logN)

前三种排序浪费了大量的“比较行为”,而归并排序,在递推到递归出口,要进行回溯的时候,每一次排序都会把两个各自排好序的小部分并在一起,成为一个更大的部分,再使其重新变为一个整体有序的部分。这样不断归并,直到数组的规模已经回到原始大小。

//归并排序
public static void mergeSort(int[] arr, int l, int r){
    if(l == r){
        return;
    }
    int mid = l + ((r - l) >> 1);
    mergeSort(arr,l,mid);
    mergeSort(arr,mid + 1,r);
    merge(arr,l,mid,r);
}
//合并左右两个有序小数组,使其整体有序
public static void merge(int[] arr, int l, int m, int r){
    int[] aux = new int[r-l+1];
    int p1 = l, p2 = m + 1;
    int i = 0;
    while(p1 <= m && p2 <= r){
        aux[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while(p1 <= m){
        aux[i++] = arr[p1++];
    }
    while(p2 <= r){
        aux[i++] = arr[p2++];
    }
    for (int j = 0; j < aux.length; j++) {
        arr[l+j] = aux[j];
    }
}
小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。等价于找当前数右边,比当前数大的数字个数,这个数字个数再乘以当前数,当前数不断在数组中向前推进,把每一个数得到的小和相加就是整个数组的小和。

找一个数左边比其小的数→找当前数右边比它大的数

例如{2,5,8,3}的小和为:11

public class Test_smallSum {
    /***/
    public static int smallSum(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        int sum = 0;
        int mid = l + ((r - l) >> 1);
        sum += smallSum(arr, l, mid) + smallSum(arr, mid + 1, r) + merge(arr, l, mid, r);
        return sum;
    }
    //外排序,合并两个小数组,使其变为较大规模的数组,递增排序
    public static int merge(int[] arr, int l, int m, int r){
        int[] aux = new int[r-l+1];
        int p1 = l, p2 = m + 1;
        int i = 0,ret = 0;
        while(p1 <= m && p2 <= r){
            ret += arr[p1] < arr[p2] ? arr[p1]*(r-p2+1) : 0;
            aux[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= m){
            aux[i++] = arr[p1++];
        }
        while(p2 <= r){
            aux[i++] = arr[p2++];
        }
        for (int j = 0; j < aux.length; j++) {
            arr[l+j] = aux[j];
        }
        return ret;
    }

    public static void main(String[] args) {
        int[] test = new int[]{3, 7, 4, 9, 8, 3, 6, 1, 4, 3, 2};
        System.out.println(smallSum(test,0,test.length-1));
    }
}
//输出结果为53
逆序对问题

逆序对,数学术语,设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。

如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。

找一个数的左边比其大的数→找当前数的右边比它小的数

public class Test_InversePairNum {
    /**逆序对,数学术语,设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。
    如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
     打印所有的逆序对,并返回逆序对数*/
    //归并排序
    public static int inverseNum(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        int num = 0;
        int mid = l + ((r - l) >> 1);
        num += inverseNum(arr, l, mid) + inverseNum(arr, mid + 1, r) + merge(arr, l, mid, r);
        return num;
    }
    //外排序,合并两个小数组,使其变为较大规模的数组,递减排序
    public static int merge(int[] arr, int l, int m, int r){
        int[] aux = new int[r-l+1];
        int p1 = l, p2 = m + 1;
        int i = 0,ret = 0;
        while(p1 <= m && p2 <= r){
            if(arr[p1] > arr[p2]){
                ret += r-p2+1;
                for (int j = p2; j <= r; j++) {
                    System.out.print("<"+arr[p1]+","+arr[j]+">\t");
                }
            }
            aux[i++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= m){
            aux[i++] = arr[p1++];
        }
        while(p2 <= r){
            aux[i++] = arr[p2++];
        }
        for (int j = 0; j < aux.length; j++) {
            arr[l+j] = aux[j];
        }
        return ret;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{3,8,2,4,9,0,1,7,5,6};
        int num = inverseNum(arr,0,arr.length-1);
        System.out.println();
        System.out.println("共有"+num+"对逆序对");
        System.out.println("打印排序后的数组:");
        for(int i : arr){
            System.out.print(i+"\t");
        }
    }
}

//输出
<8,2>	<3,2>	<8,4>	<7,6>	<7,5>	<9,7>	<9,6>	<9,5>	<9,1>	<9,0>	<8,7>	<8,6>	<8,5>	<8,1>	<8,0>	<4,1>	<4,0>	<3,1>	<3,0>	<2,1>	<2,0>	
共有21对逆序对
打印排序后的数组:
9	8	7	6	5	4	3	2	1	0	

小和问题和逆序对问题使用排序的目的是为了让每一次合并后,合并后的新数组不重不漏地统计了满足题意的数。

05_快速排序

问题一:

给定一个数组arr,一个整数num,把arr中小于等于num的数放在数组左边,大于num的数放在数组右边,两部分内部不要求有序

基于上述问题,让数组最后一个值充当num,将num前面的数组划分为两部分以后,让大于num的区域的第一个数与num交换,这样得到问题一的题解。新得到两部分都执行原始数组执行过的操作,开始递归,递归出口为传入的数组规模为2。

这就是快速排序1.0版本的逻辑

荷兰国旗问题

给定一个数组arr,一个整数num,把arr中小于num的数放在数组左边,等于num的数放在数组中间,大于num的数放在数组右边,三部分内部不要求有序

还是让数组最后一个值充当num,num前面的数组划分为三部分以后,将num与大于num的区域第一个值交换,得到三部分。等于num的区间已经排好序,不用再更改位置,在大于num和小于num的两个区间重复执行原始数组执行过的操作。递归出口为传入的数组规模为2。

这是快速排序2.0版本的逻辑

若数组为类似

{9,8,7,6,5,4,3,2,1}{1,2,3,4,5,6,7,8,9}

会导致每次分割的位置很偏,最差情况的时间复杂度为O(N^2)

对于每次的分割点为数组的哪里,是受数据影响的,所以快速排序1.0 和2.0版本的时间复杂度为O(N^2)

若每次随机选数组里的一个值,与数组最后一个元素交换作为num,这样得到的时间复杂度是多种情况时间复杂度的数学期望(各个情况等概率出现,加权求和,每种情况的权重相同),O(NlogN)

这是快速排序3.0版本的逻辑

空间复杂度为O(logN)

也是随数值在O(N)-O(logN)波动的,求大量数据的一个概率值为O(logN)

在这里插入图片描述

//快速排序,时间复杂度为O(NlogN),空间复杂度为O(logN)的版本
public static void quickSort(int[] arr, int l, int r){
    if(arr == null || l >= r){
        return;
    }
    swap(arr, l+(int)(Math.random()*(r-l+1)),r);
    int[] p = process(arr, l, r);
    quickSort(arr, l,p[0]-1);
    quickSort(arr,p[1]+1,r);
}

/**
 * 当arr[i]<num时,交换arr[i]与arr[p1+1],然后让p1++,再让i++;
 * 当arr[i]=num时,i++;
 * 当arr[i]>num时,交换arr[i]与arr[p2-1],然后p2--,i不发生自增!!!
 * 当i==p2时,结束循环,arr已经被分为以num为边界的三部分
 * @param arr
 * @param l
 * @param r
 * @return
 */
public static int[] process(int[] arr, int l, int r){
    int num = arr[r];
    int i = l;
    int p1 = l - 1, p2 = r + 1;    //p1代表小于num的区域右边界,p2代表大于num的区域左边界
    while(i <= r && i < p2){
        if(arr[i] < num){
            swap(arr,i,p1+1);
            p1++;
            i++;
        }else if(arr[i] > num) {
            swap(arr, i, p2 - 1);
            p2--;
        }else{
            i++;
        }
    }
    int[] ret = new int[2];
    ret[0] = p1 + 1;
    ret[1] = p2 - 1;
    return ret;
}

//swap
public static void swap(int[] arr, int a, int b){
    int temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
}

//print
public static void  print(int[] arr){
    for(int i : arr){
        System.out.print(i+"\t");
    }
}
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AbyssPraise

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值