作者前言

大家好,我是阿濠,今篇内容跟大家分享的是排序算法之基数排序,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.

一、基数排序的介绍

基本介绍

1.基数排序(radixsort) 属于“分配式排序”(distributionsort) ,又称“桶子法”( bucket sort)或binsort, 顾名思义,它是通过键值的各个位的值,将要排序的元素分配某些“桶”中达到排序的作用
2.基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
3.基数排序(Radix Sort)桶排序的扩展
4.基数排序是1887年赫尔曼何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较

基本思想

1.第1轮待排序每个元素个位数取出,依次放入对应的桶里,并从桶依次取出数据放入原来的数组
2.第2轮待排序每个元素十位数取出,依次放入对应的桶里,并从桶依次取出数据放入原来的数组
3.第n轮待排序元素按每个位数分别比较,并从桶依次取出数据放入原来的数组,以此类推
4.按照桶的顺序依次取出数据放入原来的数组里

二、通过应用示例认识基数排序

有一群小牛,售价分别是{53,3,542,748,14,214},请从小到大使用基数排序,进行排序

图片.png

温馨提示:
1.获取位数:个位%10、十位 /10 %10 、百位/100 %10...
2.本次步骤思路与代码未考虑负数情况

图解步骤思路与实现代码:

1.第1轮待排序每个元素个位数取出,然后依次放入对应的桶里,按照桶的顺序依次取出放入原来的数组arr={542,53,3,14,214,748}

图片.png

//第1轮(针对每个元素的个位进行排序处理)
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//说明
//1.二维数组包含10个一维数组
//2.为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.1ength
//3.名明确, 基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的放入的数据个数
//可以这里理解
//比如: bucketElementCounts[0],记录的就是bucket[0] 桶的放入数据个数
//比如:bucket[0] 第一个桶放入数字:40、30 那么bucketElementCounts[0]=2
int[] bucketElementCounts = new int[10];

//第1轮(针对每个元素的个位进行排序处理)
for(int j = 0; j < arr.length; j++) {
     //取出每个元素的个位的值
     //比如说放入第一个数字:45
     //45 /10 % 10  =5
     int digitOfElement = arr[j] /10 % 10;
     //放入到对应的桶中 按图所示应该是数字为5 的桶
     //按照bucket 数组来算 则是bucket[5][0] 是第六个桶第一个数
     //bucketElementCounts[5]对应bucket[5]
     //此时bucket[5][0]存入一个45 则bucketElementCounts[5]=1记录下来
     bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
     //bucketElementCounts[5] 记录下来
     bucketElementCounts[digitOfElement]++;
}
System.out.println("此时各个桶的放入的数据个数:"+ Arrays.toString(bucketElementCounts));

//按照桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
//原来的数组:arr[]={ 53, 3, 542, 748 , 14, 214};
//放入原数组从下标0开始;
int index=0;
//遍历每一桶,并将桶中是数据,放入到原数组
//此时bucketElementCounts对应的下标记录的是有效个数
for(int k = 0; k < bucketElementCounts.length; k++) {

    //如果桶中,有数据,我们才放入到原数组
    //因为bucketElementCounts对应的下标记录的是有效个数
    //如果对应的桶中没有数据则无需取出数据
    //比如说放入第一个数字:45 第一轮取个数 45 /10  % 10
    //按照bucket数组来则是bucket[5][0] 第六个桶第一个数
    //bucketElementCounts[5]对应bucket[5] bucketElementCounts[5]=1
    //此时取出数据则只需判断 bucketElementCounts[5]!=0 即可
    if (bucketElementCounts[k] != 0) {
        //比如说取出45 则只需要找到bucket[5][0]即可
        //循环该桶即第k个桶(即第k个一维数组),放入
        for (int l = 0; l < bucketElementCounts[k]; l++) {
            //取出元素放入到arr
            //bucketElementCounts[5]=1 对应的是bucket[5][0]
            //所以k=5时 l=0时则代表找到刚刚放入的数字:45
            //放入原数组的第一个位置下标=0
            //放入成功后进行++ 对下一个位置进行处理
            arr[index++] = bucket[k][l];
        }
    }
    
    //第一轮处理完后需要将每个bucketElementCounts[k] =0
    //方便第二轮进行下一次统计
    bucketElementCounts[k]=0;

}

System.out.println("第一轮,对个数进行排序处理,此时arr="+Arrays.toString(arr));


运行结果如下:
此时各个桶的放入的数据个数:[0, 0, 1, 2, 2, 0, 0, 0, 1, 0]
第一轮,对个数进行排序处理,此时arr=[542, 53, 3, 14, 214, 748]

2.第2轮待排序每个元素十位数取出,然后依次放入对应的桶里,按照桶的顺序依次取出放入原来的数组arr={3,14,214,542,748,53}

图片.png

//第2轮(针对每个元素的十位进行排序处理)
for(int j = 0; j < arr.length; j++) {
    //取出每个元素的十位的值
    //比如说放入第二个数字:65
    //65 % /10 % 10 = 6
    int digitOfElement = arr[j] /10 % 10;
    //放入到对应的桶中 按图所示应该是数字为6 的桶
    //按照bucket 数组来算 则是bucket[6][0] 是第七个桶第一个数
    //bucketElementCounts[6]对应bucket[6]
    //此时bucket[6][0]存入一个65 则bucketElementCounts[6]=1记录下来
    bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
    //bucketElementCounts[6] 记录下来
    bucketElementCounts[digitOfElement]++;
}
System.out.println("此时各个桶的放入的数据个数:"+ Arrays.toString(bucketElementCounts));

//按照桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
//放入原数组从下标0开始;
index=0;
//遍历每一桶,并将桶中是数据,放入到原数组
//此时bucketElementCounts对应的下标记录的是有效个数
for(int k = 0; k < bucketElementCounts.length; k++) {

    //如果桶中,有数据,我们才放入到原数组
    //因为bucketElementCounts对应的下标记录的是有效个数
    //如果对应的桶中没有数据则无需取出数据
    //比如说放入第二个数字:63 第二轮取十数 63 / 10 %10 
    //按照bucket数组来则是bucket[6][0] 第七个桶第一个个数
    //bucketElementCounts[6]对应bucket[6] bucketElementCounts[6]=1
    //此时取出数据则只需判断 bucketElementCounts[6]!=0 即可
    if (bucketElementCounts[k] != 0) {
        //比如说取出65 则只需要找到bucket[6][0]即可
        //循环该桶即第k个桶(即第k个一维数组),放入
        for (int l = 0; l < bucketElementCounts[k]; l++) {
            //取出元素放入到arr
            //bucketElementCounts[6]=1 对应的是bucket[6][0]
            //所以k=6时 l=0时则代表找到刚刚放入的数字:65
            //放入原数组的第一个位置下标=0
            //放入成功后进行++ 对下一个位置进行处理
            arr[index++] = bucket[k][l];
        }
    }
    //第二轮处理完后需要将每个bucketElementCounts[k] =0
    //方便第三轮进行下一次统计
    bucketElementCounts[k]=0;

}

System.out.println("第二轮,对个数进行排序处理,此时arr="+Arrays.toString(arr));

运行结果如下:
此时各个桶的放入的数据个数:[1, 2, 0, 0, 2, 1, 0, 0, 0, 0]
第二轮,对个数进行排序处理,此时arr=[3, 14, 214, 542, 748, 53]

3.第3轮待排序每个元素百位数取出,然后依次放入对应的桶里,按照桶的顺序依次取出放入原来的数组arr={3,14,53,214,542,748}

图片.png

//第3轮(针对每个元素的百位进行排序处理)
for(int j = 0; j < arr.length; j++) {
    //取出每个元素的十位的值
    //比如说放入第一个数字:145
    //145 % /100 % 10 = 1
    int digitOfElement = arr[j] /100 % 10;
    //放入到对应的桶中 按图所示应该是数字为1 的桶
    //按照bucket 数组来算 则是bucket[1][0] 是第二个桶第一个数
    //bucketElementCounts[1]对应bucket[1]
    //此时bucket[1][0]存入一个145 则bucketElementCounts[1]=1记录下来
    bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
    //bucketElementCounts[1] 记录下来
    bucketElementCounts[digitOfElement]++;
}
System.out.println("此时各个桶的放入的数据个数:"+ Arrays.toString(bucketElementCounts));

//按照桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
//放入原数组从下标0开始;
index=0;
//遍历每一桶,并将桶中是数据,放入到原数组
//此时bucketElementCounts对应的下标记录的是有效个数
for(int k = 0; k < bucketElementCounts.length; k++) {

    //如果桶中,有数据,我们才放入到原数组
    //因为bucketElementCounts对应的下标记录的是有效个数
    //如果对应的桶中没有数据则无需取出数据
    //比如说放入第一个数字:145 第三轮取百数 145 /100 % 10
    //按照bucket数组来则是bucket[1][0] 第二个桶第一个个数
    //bucketElementCounts[1]对应bucket[1] bucketElementCounts[1]=1
    //此时取出数据则只需判断 bucketElementCounts[1]!=0 即可
    if (bucketElementCounts[k] != 0) {
        //比如说取出33 则只需要找到bucket[1][0]即可
        //循环该桶即第k个桶(即第k个一维数组),放入
        for (int l = 0; l < bucketElementCounts[k]; l++) {
            //取出元素放入到arr
            //bucketElementCounts[1]=1 对应的是bucket[1][0]
            //所以k=1时 l=0时则代表找到刚刚放入的数字:145
            //放入原数组的第一个位置下标=0
            //放入成功后进行++ 对下一个位置进行处理
            arr[index++] = bucket[k][l];
        }
    }
    //第三轮处理完后需要将每个bucketElementCounts[k] =0
    //方便进行下一次统计
    bucketElementCounts[k]=0;

}

System.out.println("第三轮,对个数进行排序处理,此时arr="+Arrays.toString(arr));

运行结果如下:
此时各个桶的放入的数据个数:[3, 0, 1, 0, 0, 1, 0, 1, 0, 0]
第三轮,对个数进行排序处理,此时arr=[3, 14, 53, 214, 542, 748]

规律已经出来了,与整合数组与最大数有关每次取%与十的整数倍相关,根据推导进行代码抽整

//基数排序方法
public static void radixSort(int[] arr) {

    //根据前面的推到过程,获取最终的基数排序代码
    //一.获取数组中最大数的位数
    int max = arr[0];//假设数组第一位是最大数
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    
    //直接获取位数,但此方法不适合负数
    int maxLength = (max + "").length();

    //二、定义一个二维数组,表示10个桶,每个桶就是一个一维数组
    //说明
    //1.二维数组包含10个一维数组
    //2.为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.1ength
    //3.名明确, 基数排序是使用空间换时间的经典算法
    int[][] bucket = new int[10][arr.length];
    //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的放入的数据个数
    //可以这里理解
    //比如: bucketElementCounts[0],记录的就是bucket[0] 桶的放入数据个数
    //比如:bucket[0] 第一个桶放入数字:40、30 那么bucketElementCounts[0]=2
    int[] bucketElementCounts = new int[10];

    //三、根据最大位进行循环
    //比如说数组 arr={ 53, 3, 542, 748 , 14, 214};
    //最大数是748 最大位是3 分别要进行个位、十位、百位 3次循环
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10){
        for (int j = 0; j < arr.length; j++) {
            //针对每个元素对应的位数进行排序
            //取出每个元素的个位的值
            int digitOfElement = arr[j] / n % 10;
            //放入到对应的桶中并bucketElementCounts进行对应的记录
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        System.out.println("此时各个桶的放入的数据个数:"+ Arrays.toString(bucketElementCounts));
        int index=0;
        //遍历每一桶,并将桶中是数据,放入到原数组
        //此时bucketElementCounts对应的下标记录的是有效个数
        for(int k = 0; k < bucketElementCounts.length; k++) {

            //如果桶中,有数据,我们才放入到原数组
            //因为bucketElementCounts对应的下标记录的是有效个数
            //如果对应的桶中没有数据则无需取出数据
            //此时取出数据则只需判断!=0 即可
            if (bucketElementCounts[k] != 0) {
                //循环该桶即第k个桶(即第k个一维数组),放入
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    //取出元素放入到arr
                    //放入原数组的第一个位置下标=0
                    //放入成功后进行++ 对下一个位置进行处理
                    arr[index++] = bucket[k][l];
                }
            }
            //第n轮处理完后需要将每个bucketElementCounts[k] =0
            //方便第n+1轮进行下一次统计
            bucketElementCounts[k]=0;
        }
        System.out.println("第"+(i+1)+"轮,对个数进行排序处理,此时arr="+Arrays.toString(arr));
    }
}

三、基数排序说明

1.基数排序是对传统桶排序的扩展速度很快.
2.基数排序是经典的空间换时间的方式占用内存很大,当对海量数据排序时,容易造成QutQfMemoryError(内存不足)
3.基数排序是稳定的

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中r[i]=r[j], 且r[i]在r[j]之前,而在排序后的序列中,r[i]仍存在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的


28640
116 声望25 粉丝

心有多大,舞台就有多大