一、基本思想
基数排序是高效稳定排序,它通过键值的各个位的值,将要排序的元素分配到某些桶中以达到排序的目的,也叫桶子法,是桶排序的扩展。
基数排序图文说明
以这一组数为例: {12,8,29,384,0,912,6}
-
先按照各位数,将数值放入对应的桶中,遍历完后再从桶中取出存入原数组;
-
再将数组中的数按照十位上的数,将其放入对应的桶中,遍历完后再从桶中取出存入原数组;
-
最后将数组中的数按照百位上的数,将其放入对应的桶中,遍历完后再从桶中取出存入原数组;
由于上面这一组数据中,最大的数是三位数,经过3轮排序后,所有数值已经有序排列。
二、代码实现
/**
*
* 实现思路:
* 创建10个桶,下标分别是0~9,每个桶中用于存放数组,将整数按位数切割成不同的数字,再分别按各个位上的数字,将数放到对应下标的桶中。
* 比如一组数中{12,8,29,384,0,912,6},最大的数是三位数,则:
* 1)将所有数先按个位上的数大小,将这些数放到对应下标的桶(数组)中;
* 2)将所有数先按十位上的数大小(不足两位的,十位补零),将这些数放到对应下标的桶(数组)中;
* 3)将所有数先按百位上的数大小(不足三位的,百位、十位补零),将这些数放到对应下标的桶(数组)中;
* 最大的数是几位数,就进行几轮分派,直到最后一轮结束,所有数字按已经是有序的了。
*/
public class radixSort {
public static void main(String[] args) {
// int[] arr = {12,8,29,384,10,912,6};
int[] arr = new int[80000000];
for(int i=0;i<arr.length;i++){
int random =(int)(Math.random()*10);
arr[i] = random;
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.println("排序前:"+ format.format(new Date() ));
radixSort(arr);
System.out.println("排序后:"+format.format(new Date() ));
// System.out.println(Arrays.toString(arr));
}
public static void radixSort(int[] arr){
//一、创建10个桶(一个10*arr.length 的二维数组)
//1. 定义一个二维数组,表示10个桶,每个桶是一个一维数组,用于排序时存放数组中的数。
//2. 为了防止溢出,每个一维数组(桶)的长度为 arr.length 。
//3. 基数排序是以空间换时间。
int[][] bucket = new int[10][arr.length];
//二、从数组中找到最大的数,用来限制循环的轮数
int maxVal = arr[0];
for(int i=0;i<arr.length;i++){
if(arr[i]>maxVal){
maxVal=arr[i];
}
}
int maxLength = (maxVal+"").length();
//三、开始对数组中的数进行分派
//3.1 记录每个桶中存了几个数
int[] bucketElementCounts = new int[10];
for(int j = 0,n=1; j<maxLength; j++, n*=10 ){
for(int k=0; k<arr.length; k++){
int digitOfElement = arr[k] / n % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[k];
bucketElementCounts[digitOfElement]++;
}
//3.2 一轮结束后,将各个桶中的数取出存到原数组中,取出时根据bucketElementCounts数组可知道每个桶中可以取几个数
int index = 0;
for(int p = 0; p<10; p++){
//如果当前桶中有数则取出
if(bucketElementCounts[p]!=0){
for(int q = 0; q<bucketElementCounts[p]; q++){
arr[index++] = bucket[p][q];
}
}
//从当前桶中获取数完之后,最后将该桶中存储元素的个数设置成0
bucketElementCounts[p] = 0;
}
}
}
}
三、性能对比
以80000个数据测试,耗时大约12ms
800000个数据耗时大约220ms
但是当数据量再大一些的时候,比如80000000个数据,就会报OutOfMemoryError,由于基数排序是采用空间换时间,因此当数据比较大的时候,需要的内存空间会特别大,容易产生空间不足的问题
基数排序所需要的内存空间计算
以80000000个数为例,需要10个桶(数组),每个桶的大小都是80000000,还需要一个长度为10的数组来临时保存每个桶中存了几个数,每个int占4个字节
80000000114/1024/1024/1024约等于 3G,一次计算需要3G的内存空间。
因此在数据量不是很大的时候,基数排序非常快,但是在对海量数据进行排序时,容易造成OutOfMemoryError。
四、 基数排序是稳定的
当对一组数进行排序时,即使这组数中有若干个大小相同的值,但是它们在排序后的相对为止依然保持不变,如:{5,2,7,2,4,2} ,排序后变成 {2,2,2,4,5,7},此时的3个2的相对顺序依然不变,愿数组中的第一个2,在排序后依然排在所有2的第一个位置。这一特性可以被用在将对象多维排序中,比如一组图片按照日期排序后,再按照大小排序,此时,两张同样大小的照片,他们的相对次序依然是按照日期有序排列的。