Java排序算法之基数排序

Java排序算法之基数排序

算法概述

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。本文基于正整数来讲解。

算法思想

基数排序的主要思路是,将所有待比较数值(注意,必须是正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次稳定排序(我们常用上一篇blog介绍的计数排序算法, 因为每个位可能的取值范围是固定的从0到9).这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
基数排序可以采用两种方式:
LSD(Least Significant Digital):从待排序元素的最右边开始计算(如果是数字类型,即从最低位个位开始)。
MSD(Most Significant Digital):从待排序元素的最左边开始计算(如果是数字类型,即从最高位开始)。
我们以LSD方式为例,从数组R[1..n]中每个元素的最低位开始处理,假设基数为radix,如果是十进制,则radix=10。基本过程如下所示:

  1. 计算R中最大的元素,求得位数最大的元素,最大位数记为distance;
  2. 对每一位round<=distance,计算R[i] % radix即可得到;
  3. 将上面计算得到的余数作为bucket编号,每个bucket中可能存放多个数组R的元素;
  4. 按照bucket编号的顺序,收集bucket中元素,就地替换数组R中元素;
  5. 重复2~4,最终数组R中的元素为有序。

补充知识点

顺便介绍下计数排序和桶排序,便于理解基数排序。

计数排序

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量内存。计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

算法的步骤如下:
1. 找出待排序的数组中最大和最小的元素
2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

桶排序

桶排序假设序列由一个随机过程产生,该过程将元素均匀而独立地分布在区间[0,1)上。我们把区间[0,1)划分成n个相同大小的子区间,称为桶。将n个记录分布到各个桶中去。如果有多于一个记录分到同一个桶中,需要进行桶内排序。最后依次把各个桶中的记录列出来记得到有序序列。

桶排序的平均时间复杂度为线性的O(N+C),其中C为桶内快排的时间复杂度。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。

算法源码

参考拾毅者的源码

/**
 * 基数排序
 * @author Administrator
 *
 */
public class RadixSort {
    public static void main(String[] args) {    
        Integer[] array = new Integer[] { 1200, 292, 121, 72, 233, 44, 12 ,8};    
        radixSort(array, 10, 4);    
        System.out.println("排序后的数组:");    
        print(array);    
    }    

    /* 
     * array    代表待排序数组 
     * radix    代表基数 。十进制为10
     * d        代表排序元素的位数 ,待排序数组 中最大的数的位数
     */  
    public static void radixSort(Integer []array, int radix, int d){  
        // 临时数组    
        Integer[] tempArray = new Integer[array.length];    
        // count用于记录待排序元素的信息,用来表示该位是i的数的个数    
        Integer[] count = new Integer[radix];    

        int rate = 1;  
        for (int i = 0; i < d; i++) {    
            //重置count数组,开始统计下一个关键字    
            Arrays.fill(count, 0);  
            //将array中的元素完全复制到tempArray数组中    
            System.arraycopy(array, 0, tempArray, 0, array.length);    

            //计算每个待排序数据的子关键字    
            for (int j = 0; j < array.length; j++) {    
                int subKey = (tempArray[j] / rate) % radix;    
                count[subKey]++;  
            }    
            //统计count数组的前j位(包含j)共有多少个数  
            for (int j = 1; j < radix; j++) {    
                count[j] = count[j] + count[j - 1];   
            }    
            //按子关键字对指定的数据进行排序 ,因为开始是从前往后放,现在从后忘前读取,保证基数排序的稳定性  
            for (int m = array.length - 1; m >= 0; m--) {    
                int subKey = (tempArray[m] / rate) % radix;    
                array[--count[subKey]] = tempArray[m]; //插入到第--count[subKey]位,因为数组下标从0开始  
            }    
            rate *= radix;//前进一位    
            System.out.print("第" + (i+1) + "次:");  
            print(array);  
        }    
    }  
    //输出数组===============  
    public static void print(Integer[] array) {    
        for (int i = 0; i < array.length; i++) {    
            System.out.print(array[i] + "\t");    
        }    
        System.out.println();    
    }    
}

测试效果:

第1次:1200    121 292 72  12  233 44  8   
第2次:1200    8   12  121 233 44  72  292 
第3次:8   12  44  72  121 1200    233 292 
第4次:8   12  44  72  121 233 292 1200    
排序后的数组:
8   12  44  72  121 233 292 1200    

算法分析

时间复杂度
设待排序的数组R[1..n],数组中最大的数是d位数,基数为r(如基数为10,即10进制,最大有10种可能,即最多需要10个桶来映射数组元素)。处理一位数,需要将数组元素映射到r个桶中,映射完成后还需要收集,相当于遍历数组一遍,最多元素书为n,则时间复杂度为O(n+r)。所以,总的时间复杂度为O(d*(n+r))。

空间复杂度
设待排序的数组R[1..n],数组中最大的数是d位数,基数为r。基数排序过程中,用到一个计数器数组,长度为r,还用到一个r*n的二位数组来做为桶,所以空间复杂度为O(r*n)。

排序稳定性
通过上面的排序过程,我们可以看到,每一轮映射和收集操作,都保持从左到右的顺序进行,如果出现相同的元素,则保持他们在原始数组中的顺序。 可见,基数排序是一种稳定的排序。

总结

LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好,MSD的方式恰与LSD相反,是由高位数为基底开始进行分配,其他的演算方式则都相同。

参考链接:
基數排序法
八大内部排序算法(中)-基数排序(java实现)
计数排序、桶排序和基数排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值