【数据结构与算法】四、排序算法总结

本文介绍了桶排序和计数排序这两种非基于比较的排序算法,强调了稳定性在某些场景中的重要性。计数排序适用于数据量小且重复值多的情况,而基数排序则适用于全为数字的数据。作者还对各种排序算法的时间复杂度、空间复杂度和稳定性进行了总结,指出在工程实践中,通常选择快排作为平衡时间与空间的最优解。此外,还提到了在实际应用中如何结合不同排序算法的优点进行优化。
摘要由CSDN通过智能技术生成

前言

大家好,我是春风。

截止目前,是看左神算法视频的第二周,虽然进度不太快,但还是认真看完了视频,并通过输出文章,做了自己的理解。

目前所谓的十大排序除了桶排序外,已全部完结,今天先补一个桶排序,然后就来总结一下所有的排序算法!

补一个桶排序

和冒泡、选择这种基于两个数比较的排序不同,桶排序不是基于两个数比较的排序,而更是像找同类的方式。

桶排序可以分为计数排序和基数排序两种

计数排序:

当数据量比较小时,且有很多重复数据,就可以使用计数排序,类似于词频统计

  1. 根据数据情况,准备好一系列排好序的桶
  2. 遍历数组,将同样的数放到一个桶里 - 入桶
  3. 最后遍历桶中每个元素,计算他的下标 - 出桶

基数排序

基数排序就是把每个数字拆分个位数字、十位数字、百位数字...依次按顺序入桶出桶,因为最高位最后排,所以优先级最高,而且还能继承之前低位的顺序,所以就能保证整体有序。

因此基数排序只能用于全是数字的情况。

代码:

public static void radixSort(int[] arr) {
    radixSort(arr, 0, arr.length-1, maxbits(arr));
}

public static int maxbits(int[] arr) {
    // 找出最大的数
    int max = Integer.MIN_VALUE;
    for (int i=0; i<arr.length; i++) {
        max = Math.max(max, arr[i]);
    }

    int res = 0;

    // 从最大的数找最多有多少位数
    while (max !=0) {
        res ++;
        max /= 10;
    }

    return res;
}

public static void radixSort(int[] arr, int l, int r, int digit) {
    final int radix = 10;
    int i=0, j=0;

    // 辅助空间
    int[] bucket = new int[r-l+1];

    for(int d=1; d<=digit;d++) {
        // 有多少位,就进出桶多少次
        int[] count = new int[radix];
        for (i=l; i<= r; i++) {
            j = getDigit(arr[i], d);
            count[j]++;
        }
        
        for (i=1; i<radix;i++) {
            count[i] = count[i] + count[i-1];
        }
        for (i =r;i>=l;i--) {
            j = getDigit(arr[i],d);
            bucket[count[j]-1] = arr[i];
            count[j]--;
        }
        
        for(i=l,j=0;i<=r;i++,j++) {
            arr[i]=bucket[j];
        }
    }
}

排序算法总结

我们在学习所有排序算法的时候,每个算法的时间复杂度和空间复杂度都是我们最关心的内容,而且在时间不可再生、空间可叠加的现实面前,时间复杂度往往比空间复杂度优先级更高。

但是我们其实还遗漏了一项参考指标,就是稳定性

什么是稳定性指值相同的情况下,排完序,相同的值是否还能保持原来的相对顺序

这种稳定性的要求,在我们前面经常举例的对基础类型的值做排序的场景中基本不需要考虑,但是很多非基础类型的值做排序的场景确是需要我们考虑的。

比如淘宝对商品做排序,先根据价格排,然后再根据销量排,这种排序的合并,如果我们在第二、三次的排序打乱了原来的顺序,就不满足需求。

计数排序和基数排序也是稳定的,先入先出

时间复杂度空间复杂度稳定性
选择排序O(N^2)O(1)
冒泡排序O(N^2)O(1)是(相等时不交换)
插入排序O(N^2)O(1)
归并排序O(N*logN)O(N)是(相等时先拷贝左边的)
快排排序(随机数版本)O(N*logN)O(logN)
堆排序O(N*logN)O(1)

结论:

  1. 综上考虑,一般情况下,我们选择使用快排,是时间和空间复杂度最低的。
  2. 基于比较的排序,像选择、冒泡、插入,在不增加空间的情况下,还不能做到时间复杂度在O(N*logN)以下。
  3. 奇数放左边,偶数放右边的排序,奇数偶数还要保持稳定性,额外空间复杂度O(logN)的情况下,参考快排的0-1分区,基本做不到。

工程上对排序的改进:

  1. 利用O(NlogN)和O(N^2)排序的各自的优势,大样本时,采用O(NlogN)算法的分区调度,当分区内样本量小时,又采用O(N^2)的排序。(小样本常数项操作的时间低)
  2. Arrays.sort()对类型的判断,如果是基础类型,就考虑用不需要稳定性的排序,否则就用需要稳定性的排序

最后的最后,求点赞!非常感谢!

我是春风,春风和煦,不负归期! 公H:程序员春风

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值