基数排序法

一、前言

基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

二、原理

它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始

最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

三、图例

动态图(借鉴):

数组:{73, 22, 93, 43, 55, 14, 28, 65, 39, 81}  ,设置编号0到9,数组元素按个位 十位 百位 千位等从最右边开始。

第一步个位开始,如下表所示:

编号0123456789
元素08122731455002839
   93 65    
   43      

接下来将以上桶子中的数值重新串接起来,成为以下的数列:81, 22, 73, 93, 43, 14, 55, 65, 28, 39

第二步十位开始,如下表所示:

编号0123456789
元素0142239435565738193
  28       
          

接下来将以上桶子中的数值重新串接起来,成为以下的数列:14, 22, 28, 39, 43, 55, 65, 73, 81, 93

至此,数组的元素排序完毕,得到后的数组:{14, 22, 28, 39, 43, 55, 65, 73, 81, 93}

元素有个十百千万等高位数字,则持续进行以上的动作直至最高位数为止。

四、代码

第一种方法:

/**
     * 实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。
     * 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
     *
     * 实现:1、定义二维数组,根据元素的位数  存元素的位置
     *      2、根据 步骤1 的结果,重新遍历,排序新的元素位置
     *      3、根据 步骤2 的结果,重新遍历,依次类推,最后得到想要的 排序数组
     *
     *      https://baike.baidu.com/item/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F/7875498?fr=aladdin
     */
    @Test
    public void test(){
        int[] number =
                {73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 100};
        // 数字的位数
        int d = 3;
        int k = 0;
        int n = 1;
        //控制键值排序依据在哪一位
        int m = 1;
        //数组的第一维表示可能的余数0-9,第二维表示出现的次数
        int[][] temp = new int[10][number.length];
        //数组order[i]用来表示该位是i的数的个数
        int[] order = new int[10];
        // 遍历数字的位数
        while(m <= d)
        {
            // 遍历数组元素
            for(int i = 0; i < number.length; i++)
            {
                // 根据位数取这个数字的余数
                int lsd = ((number[i] / n) % 10);
                // 二为数组表示多个值,todo 结合0-9及相同位数的次数确定数组元素
                temp[lsd][order[lsd]] = number[i];
//                System.out.println("lsd:"+lsd+"    order[lsd]:"+order[lsd]+"  number[i]: "+number[i]);
                // 位数出现次数+1
                order[lsd]++;
            }

            // 遍历 0-9 的元素
            for(int i = 0; i < 10; i++)
            {
                // 判断 位数出现的次数 大于 0
                if(order[i] != 0){
                    // 遍历 位数出现的次数
                    for(int j = 0; j < order[i]; j++)
                    {
                        // 找到数组元素对应的二维数组元素
                        number[k] = temp[i][j];
                        // 寻找下一个下标的元素
                        k++;
                    }
                }
                // 位数没有出现元素,则默认为 0
                order[i] = 0;
            }
            System.out.println(Arrays.toString(number));
            // 个位为 1,十位为 10,百位为 100 .。。
            n *= 10;
            // 元素下标从 0 开始
            k = 0;
            // 元素的位数增加
            m++;
        }

        // 获取排序后的数组
        for(int i = 0; i < number.length; i++)
        {
            System.out.print(number[i] + " ");
        }
    }

第二种方法:

    @Test
    public void test2(){
        int[] arr =
                {73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 100};

        int maxDigit = getMaxDigit(arr);
        // 位数,个位时取 10 ,十位时 取 100,百位时取 1000
        int mod = 10;
        // 位置计算,如个位 十位 百位 千位
        int dev = 1;
        // for 循环,遍历位数如 个位 十位 百位 。。。
        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            // mod * 2 定义数组长度
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                // [0-9]对应负数,[10-19]对应正数 ,bucket + 10 表示下标的位置
                int bucket = ((arr[j] % mod) / dev) + mod;
                // todo counter[bucket] 为什么这么存?
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);

            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }
        System.out.println("基数排序:"+Arrays.toString(arr));
    }

    /**
     * 获取最高位数
     */
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    /**
     * 获取数组元素种最大的值
     * @param arr
     * @return
     */
    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    /**
     * 获取数组中元素做多的位数如 100 三位
     * @param num
     * @return
     */
    protected int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        // 利用 /= 取整数,最后的结果为 0,结束循环
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        // 返回 位数
        return lenght;
    }

 /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }

第一种方法比较好理解,利用二维数组存储数据;第二种方法比较完善比较细腻,但 counter[bucket] 的处理比较抽象。

 

学习地址:

https://baike.baidu.com/item/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F

https://www.geekxh.com/2.0.%E6%8E%92%E5%BA%8F%E7%B3%BB%E5%88%97/10.radixSort.html#_2-lsd-%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E5%8A%A8%E5%9B%BE%E6%BC%94%E7%A4%BA

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值