基数排序原理及Java实现

基数排序算法思路图解

基数排序(桶排序)介绍:

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用

基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法

基数排序(Radix Sort)是桶排序的扩展

基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。

基数排序原理及Java实现

基数排序原理及Java实现

基数排序原理及Java实现
基数排序轮数取决于原数组中最大数的位数

基数排序代码实现

用二维数组表示10个桶,为了防止放入数据时溢出,每个一位数组的大小都定义为arr.length,基数排序是用空间换时间

取每个桶中的数据时不确定每个桶中有几个元素?
每放入一次数据就要记录每一个桶中有几个有效数据(定义一个一位数组来记录放入数据的个数)
int[] bucketElementCounts
第一轮 排序,按个位数存进桶

public static void baseSort(int arr[])
    {
       //创建一个二维数组,每一维存放个位相同的数 比如第0维就存放个位为0的数
        int[][]Digit=new int[10][arr.length];
        //定义一个一维数组,计数每一维是否存有数据
        int[]DimCount=new int[10];
        //第一轮排序,按个位存进十个桶
        System.out.println("第一轮排序,按个位存进十个桶");
        for(int i=0;i<arr.length;i++)
        {
            Digit[arr[i]%10][DimCount[arr[i]%10]]=arr[i];//找出待排序数的个位数,然后存进和这个个位数相应的桶,个位是1,就存进编号为1的桶(即Digit[0][]),个位是10,就存进编号为0的桶(即Digit[1][])
            DimCount[arr[i]%10]++;//哪个桶存进了数,这个桶中的数的数量就加1,方便后续再存入数,DimCount[arr[i]%10]是二维数组Digit的第二个下标
        }
        for(int i=0;i<10;i++)
        {
            for(int j=0;j<arr.length;j++)
            {
                System.out.print(Digit[i][j]+" ");
            }
            System.out.println();//打印一行就换行
        }
    }

测试 int[]arr={123,36,78,58,94,63,72,45};

第一轮排序,按个位存进十个桶
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
72 0 0 0 0 0 0 0 
123 63 0 0 0 0 0 0 
94 0 0 0 0 0 0 0 
45 0 0 0 0 0 0 0 
36 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
78 58 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 

第二轮 排序,按十位数存进桶
每一轮排完记得清桶里面的数据,把负责基数的数组清空即可,这样我们下一次王桶里放数据就是从每个桶的下标0开始存,在把这个下标对应的桶中的元素数量加1,我们排完从桶种取出数据的时候,只取到每一个桶对应的计数就好了。


   //把第一轮拍好序的数从桶里拿出来放进数组,第二轮再对这个数组进行排序
        int count=0;
         for(int i=0;i<10;i++)
         {
             for(int j=0;j<DimCount[i];j++)
             {
                 arr[count++]=Digit[i][j];
             }
             DimCount[i]=0;//把计数清零,以便下一轮排序再往二维数组放数据
         }

 //第二轮排序,按十位存进十个桶
        System.out.println("第二轮排序,按十位存进十个桶");

        for(int i=0;i<arr.length;i++)
        {
            Digit[arr[i]/10%10][DimCount[arr[i]/10%10]]=arr[i];//找出待排序数的十位数,然后存进和这个十位数相应的桶,十位是1,就存进编号为1的桶(即Digit[1][])
            DimCount[arr[i]/10%10]++;
        }

第三轮 排序,按百位数存进桶
每一轮排完记得清桶里面的数据

        //第三轮排序,按百位存进十个桶
        System.out.println("第三轮排序,按百位存进十个桶");

        for(int i=0;i<arr.length;i++)
        {
            Digit[arr[i]/100%10][DimCount[arr[i]/100%10]]=arr[i];//找出待排序数的十位数,然后存进和这个十位数相应的桶,十位是1,就存进编号为1的桶(即Digit[1][])
            DimCount[arr[i]/100%10]++;
        }

        count=0;
        for(int i=0;i<10;i++)
        {
            for(int j=0;j<DimCount[i];j++)
            {
                arr[count++]=Digit[i][j];
            }
            DimCount[i]=0;//把计数清零,以便下一轮排序再往二维数组放数据
        }
        System.out.println(Arrays.toString(arr));

基数排序原理及Java实现
接下来我们把代码合并

public static void BaseSort2(int arr[]) {

        //创建一个二维数组,每一维存放个位相同的数 比如第0维就存放个位为0的数
        int[][] Digit = new int[10][arr.length];
        //定义一个一维数组,计数每一维是否存有数据
        int[] DimCount = new int[10];

        //先获取外循环的轮数,最多3位数就循环3轮,所以需要先找到最大的数是几位的
        int  max=0;
        for(int i=0;i<arr.length;i++)
        {
            if(arr[i]>max)
            {
                max=arr[i];
            }
        }
        int maxLength=(max+"").length();//骚操作,转成字符串看长度

        for (int k = 0,n=1; k < maxLength; k++,n*=10) {//注意n*=10,自己理解一下吧,结合分部循环
            for (int i = 0; i < arr.length; i++ ) {
                Digit[arr[i] / n % 10][DimCount[arr[i] / n % 10]] = arr[i];//找出待排序数的个位数,然后存进和这个个位数相应的桶,个位是1,就存进编号为1的桶(即Digit[1][]),个位是0,就存进编号为0的桶(即Digit[0][])
                DimCount[arr[i] / n % 10]++;//哪个桶存进了数,这个桶中的数的数量就加1,方便后续再存入数,DimCount[arr[i]%10]是二维数组Digit的第二个下标
            }
            //把上一轮排好序的数从桶里拿出来放进数组,下一轮再对这个数组进行排序
            for (int i = 0, count = 0; i < 10; i++) {
                for (int j = 0; j < DimCount[i]; j++) {
                    arr[count++] = Digit[i][j];
                }
                DimCount[i] = 0;//把计数清零,以便下一轮排序再往二维数组放数据
            }
            System.out.println(Arrays.toString(arr));
        }
    }

基数排序原理及Java实现
我们再来测试80个数据
基数排序原理及Java实现好测试无误,我们测试一下80000个数需要排序多久
基数排序原理及Java实现
800000
基数排序原理及Java实现
8000000
基数排序原理及Java实现
80000000
基数排序原理及Java实现

基数排序算法注意事项

当数据量较小,速度比快排和归并还快,但是当数据量比如达到8000万,因为排序之前会开10个桶,每个桶的长度都跟数据量一样大,那么占用内存(空间)就会很大

80000000×11×4/1024/1024/1024=3.3G,占用内存3.3个G(相当于11个数组,一个int是4个字节,1k=1024字节,1M=1024k,1G=1024M)
基数排序原理及Java实现基数排序的稳定性说明
3 1 4 1 5 7 2
排序之后
1 1 2 3 4 5 7
可以看到数组里面有2个 1,排序前后,两个1的顺序是不变的,原来哪一个1在前面,排序之后它依然在前面

基数排序的说明:
基数排序是对传统桶排序的扩展,速度很快.
基数排序是经典的空间换时间的方式,占用内存很大,(因为我们开了一个二维数组) 当对海量数据排序时,容易造成 OutOfMemoryError 。
基数排序是稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的]
有负数的数组,我们不用基数排序来进行排序, 如果要支持负数,参考: https://code.i-harness.com/zh-CN/q/e98fa9

负数排序方案:取余的绝对值放入桶中,再取出来数据时要进行反转

上一篇:vue swiper动态添加轮播图


下一篇:通过swiper5了解vuecli脚手架的底层原理