居然还有比快排更快的排序

时间:2024-05-20 10:16:54
  1. 前言

           快速排序(Quick Sort)是目前应用最广泛的排序算法,因其时间复杂度低和内循环较小,而且不需要太多的额外空间,广泛应用于工业界。比如JDK源码中的排序算法就是使用的快速排序。

           虽然快速排序应用广泛,但其最优时间复杂度仍为O(NlogN),不是O(n)。那么今天,我就给大家介绍几种时间复杂度为O(n)的排序算法。

  2. 总论

           我们把时间复杂度为O(n)的排序算法叫做线性排序,常见的线性排序算法有:桶排序、计数排序、基数排序。这几种算法之所以能做到线性时间内完成排序,是因为其不是基于比较的排序算法。下面我详细介绍下这几种排序算法。

     

  3. 排序算法介绍

     

    桶排序

           桶排序顾名思义就是将数据分几个有序的桶里,然后每个桶内单独进行排序。桶内排完序之后,再按顺序把每个桶内的数据依次取出,组成的序列就是有序的了。

              居然还有比快排更快的排序 

           图示就是桶排序的例子,我们根据这个例子分析下桶排序的时间复杂度。

           如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶,每个桶都有k=n/m个元素。如果桶内部使用快速排序的话,那每个桶内排序的时间复杂度为O(k*logk),m个桶进行排序的时间复杂度为O(m*k*logk)。因为k=n/m,所以整个桶排序的时间复杂度为O(n*logn/m)。当桶的个数m接近数据个数n时,logn/m就是个非常小的常数,这时候桶排序的时间复杂度就是O(n)。

     

    桶排序是否替代广泛使用的快速排序

           当然不能,虽然桶排序时间复杂度能达到O(n),但是它对要排序的数据要求非常严格。1,要排序的数据需要很容易划分到m个桶中,且桶与桶之间有着天然的大小顺序。数据只需要在桶内进行排序后,数据总体就已经有序了。2,数据在各个桶中的分布是比较均匀的。如果数据划分到各个桶中后,有些桶内数据非常多,有些非常少,很不平均,那各个桶内数据排序的时间复杂度就不是一个数量级了。在极端情况下,所有的数据都划分到同一个桶内,那就退化为O(NlogN)时间复杂度的排序算法了。

    计数排序

           我觉得,计数排序是桶排序的一种特殊情况。当要排序的n个数据,所处的数据范围并不大,且最大值与最小值的差值为k,那么我们就可以将数据划分为k个桶。每个桶内的数据数值是相同的,这样就省掉了桶内排序的时间。

           下面根据一个示例来讲解,比如现在有个待排序的整数序列A={-1, 2, 0, 4, 3, 6, 5, 8, -2, 1, 3, 0, 3,6, 5, 2}。首先我们花O(n)的时间扫描一下整个序列,可以得到max=8,min=-2。然后我们建立一个新的数组C,长度为(max-min+1)=11。数组C如下所示:

    居然还有比快排更快的排序

    数组index为0的元素记录的值是-2出现的次数,依次index为1的元素记录的是-1出现的次数。 

    此时我们再扫描一下数组A,比如对于-1,我们的操作是:-1-min=-1-(-2)=1;C[1]++。对于2,我们的操作是:2-(-2)=4;C[4]++。这样我们又花了O(n)的时间。操作结果是:

     居然还有比快排更快的排序 

          最后我们便可以输出目标整数序列了,具体的逻辑是遍历数组C,比如C[0]=1,则输出一次min+index即(-2+0)=-2;C[1]=1,则输出一次-1;C[2]=2,则输出两次0,以此类推。目标序列为:

    -2, -1, 0, 0, 1, 2, 2, 3, 3, 3, 4, 5, 5, 6, 6, 8

          分析可得,我们扫描了两次数组A,一次数组C,所以计数排序的时间复杂度为2(n)+n+k(扫描数组C的时间复杂度是n+k)。空间复杂度是:n+k(n是数组A的空间,最后输出目标整数序列时可以直接存储在A中;k是数组C的空间)。故其空间复杂度和时间复杂度均为O(n+k)。

          计数排序只能适用于数据范围不大的场景中,如果数据范围k比要排序的数据n大很多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型,要将其在不改变相对大小的情况下,转化为非负整数(如上例)。

     

    基数排序

           基数排序的主要思路是,将所有待比较数值(必须是正整数)统一为同样的数位长度,数位较短的数前面补零(补零一般不影响排序结果,如果出现影响排序结果的话,就要考虑如何处理)。然后, 从最低位开始, 依次进行一次稳定排序(因为每个位可能的取值范围是固定的从0到9).这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

    比如这样一个数列排序: 342 58 576 356, 以下描述演示了具体的排序过程(加黑字体表示正在排序的数位)

    第一次排序(个位):

    3 4 2

    5 7 6

    3 5 6

    0 5 8

    第二次排序(十位):

    4 2

    5 6

    5 8

    7 6

    第三次排序(百位):

    0 5 8

    3 4 2

    3 5 6

    5 7 6

    结果: 58 342 356 576

    每一位排序,我们可以用刚讲过的桶排序或者计数排序,它们的时间复杂度为O(n)。我们要排序的数据有k位,那么算法总的时间复杂度就为O(k*n),当k不大时,那么基数排序的算法时间复杂度就近似于O(n)。

     

  4. 总结

    为什么存在这么多线性时间就能完成的排序方式鲜有人知,反而时间复杂度更高的快速排序却广泛应用呢

       1.之前提到了这些线性排序对于要排序的数据要求严格,而快速排序对数据要求不高且优化后出现时间复杂度O(n2)的概率极低;

       2.线性排序的时间复杂度虽然是O(n),但是其做了很多假设,真实情况下复杂度n前面的常量因子很大,但相同规模中,快速排序复杂度前的常量因子较小。

       3.所有的线性排序都需要一定的额外空间复杂度,而快速排序需要的额外空间复杂度是O(1).

       虽然从时间复杂度上说这些线性排序都比快速排序快,但从以上分析可知实际应用中可能并不如快速排序。这也教给我们,判断排序算法好坏的标准,不关只有时间复杂度,还有空间复杂度、问题的规模、应用的场景、数据的特点等多方面的因素。所以,算法无所谓好坏,就看使用的方式是不是正确。

     大家如果想深入了解这些,可以报名下图这个课程,一起学习算法,打好基础,坚实地向前迈出每一步。

居然还有比快排更快的排序