基数排序--基于计数排序的线性时间复杂度的排序算法

    我们前面提到了一种时间复杂度为O(n+k),其中k是待排序列最大值的排序算法--计数排序,复习请点击此处。本文我们将继续介绍一种线性时间复杂度的排序算法--基数排序,这种排序算法的时间复杂度为Θ(d(n+k)),这种排序基于我们之前将的计数排序,其中n表示待排序列的规模,d表示待排序列的最大位数,k表示每一位数的范围,这也是一种时间换空间的算法。


一、适用情况

    我们看这个算法的时间复杂度我们应该就可以猜出来Θ(d(n+k)),这个算法适用于位数不多,待排序列最大位数不是特别大,每一位数的范围不大的情况下(当然对于数字排序,每一位的范围都是[0,9])。


二、基本思路

    现在对于n个d位数我们想要进行排序,我们可以每一列每一列地进行排序。可能说到这很多人直观上都是觉得,我们应该从高位到低位来进行排序,但是实际上我们应该从低位到高位进行排序。我们从低位到高位一个一个地进行排序,当最高位排序结束的时候整个序列就是有序的了。而我们对每一位排序的算法选择计数排序。

    至此,我们的基数排序就完成了。理解上是不是很简单?至于为什么不是从高位到低位呢,我觉得实际上理解起来也很简单:比如我们还是对上面的序列排序,第一轮我们对最高位排序,然后对十位排序的时候就把之前对最高位排序之后的有一定顺序的序列给打乱了。因此从高位到低位排序是错误的

    那么我们如何证明这种排序的正确性呢?我们可以使用归纳法:假设待排的n个数有d位,那么n个数中的所有d-1位数已经用计数排序排好序了,我们现在开始对第t位开始使用计数排序,有两种可能的情况,如果n个数中的t位有相同的数,那么我们应该假设计数排序是稳定的,对于相同的数,先出现的排在前面,后出现的排在后面。如果n个数中的第t位不相同,那么我们对其再次进行计数排序。直到所有d位数都排好了。

    通过我们的证明也可以看出,我们对基数排序中的每一位的排序算法必须是稳定的,比如我们这里的计数排序。


三、运行过程

    我们画图来说明整个算法过程,我们对329,457,657,839,436,720进行排序:


    我们对最低位排序:

    然后对十位排序:

    最后对百位排序:

    是不是运行过程实际上很简单。


四、代码实现

    同样,我们使用Java来实现这一算法:

    private static int[] countingSortEachNumber(int[] a, int[] b, int k,int number){//依靠a的number位进行排序 最高位为0
        int[] c = new int[k+1];//存放0~k 这里一般是[0,9]
        for(int i = 0; i= 0; i--){
            c[String.valueOf(a[i]).charAt(number)-'0']-- ;
            b[c[String.valueOf(a[i]).charAt(number)-'0']] = a[i];
        }
        return b;
    }
    public static void radixSort(int[] a, int[] b, int d){
        for(int i = d - 1; i >= 0; i --){
            for(int j = 0; j
    我们对前面我们的计数排序做了一些修改,使得比较的时候比较的是元素的指定位。radixSort函数当中a表示待排序列,b表示排序后的存放序列,d表示待排序列元素的最多的位数。

五、复杂度分析

    对于给定的n个d位数,取值范围为[0,k],我们使用计数排序比较元素的每一位,基数排序耗时Θ(n+k),那么基数排序的复杂度为Θ(d(n+k))。

    我们还是和快速排序进行比较,仅仅从渐进性来看,基数排序比快速排序要好,但是隐藏在Θ符号后面的常数项因子是不同的,基数排序循环次数比快速排序循环次数少,但是基数排序每次循环会比快速排序长。更多的时候我们使用哪一种是通过输入数据的特征以及主存容量是否宝贵来决定的。当我们需要原址排序或者主存容量宝贵的时候我们就更倾向于快速排序这样的原址排序。

你可能感兴趣的:(算法)