Veiking百草园


老狗啃骨头之算法-基数排序

老狗啃骨头   @Veiking   2020-11-01

老狗啃骨头之算法-基数排序

摘要:

基数排序是一种不在数据值本身之间比较的排序算法,而是通过数据按位数“切割”对比,从而实现排序的算法,所以基数排序也被认为是一种典型的非比较排序算法。在实际运用中,基数排序的使用场景不局限于整数,凡是整数可以表达的,或者有规律格式的字符串,都适用。基数排序的发明,据说是赫尔曼·霍尔瑞斯在1887年总结出来的

基数排序(Radix Sort)

  基数排序是一种不在数据值本身之间比较的排序算法,而是通过数据按位数“切割”对比,从而实现排序的算法,所以基数排序也被认为是一种典型的非比较排序算法。在实际运用中,基数排序的使用场景不局限于整数,凡是整数可以表达的,或者有规律格式的字符串,都适用。
  基数排序的发明,据说是一位叫赫尔曼·霍尔瑞斯的老先贤基于打孔卡片制表机的使用,在1887年总结出来的。故此算法,至今已有一百三十多年,所谓源远绵长、历史悠久,妥妥的百年算法,脱发致敬。

基本思想

  基数排序是将所有数据元素数值看成长度统一的数位元素,短数位则前面补零;然后从最低位开始,依次进行排序;从低到高依次操作,直至最后,所有数据元素即可成为有序序列。

算法描述

1:取数据元素中最大值为位数样本,数位较短的前面补零(想象中);
2:以数据的末位数值开始进行入桶操作;
3:从最低位至向高位逐次进行类2操作,直至所有数位遍历完成,即完成排序操作。

动图演示



代码实现

import java.util.Arrays;

/**
 * @author :Veiking
 * @version :2020年10月15日 
 * 说明 :基数排序算法
 */
public class RadixSort {
    /**
     * 基数排序
     * @param arrays
     * @return
     */
    public static int[] sort(int[] arrays) {
        // 求出待排数的最大元素
        int maxItem = arrays[0];
        for (int i = 1; i < arrays.length; i++) {
            if (maxItem < arrays[i]) {
                maxItem = arrays[i];
            }
        }
        // 根据最大元素得出其计算位数
        int size = String.valueOf(maxItem).length();
        // 由于我们是对数字序列进行排序,即准备0~9十个临时数字数组作为容器,用于基数排序
        // 因考虑到极端情况,数组的第二维度长度与数据样本等长,为arrays.length
        int[][] buckets = new int[10][arrays.length];
        // 用于记录bucket数组中每个桶内存的数据的数量
        int[] counts = new int[10];
        // 用于记录每个数比较位数的数值
        int num = 0;
        // 用于记录取的元素需要放的位置
        int index = 0;
        // 根据最大元素位数决定排序的操作次数,记为i,figure则用于计算数据位数值,个十百千万等
        for (int i = 0; i < size; i++) {
            int figure = (int) Math.pow(10, i);
            // 将样本按位数计算规则,入桶
            for (int j = 0; j < arrays.length; j++) {
                num = arrays[j] / figure % 10;// 取当前位数据值
                buckets[num][counts[num]] = arrays[j]; // 待排元素入桶
                counts[num]++; // 值为num的 bucket桶元素数量递加
            }
            // 从buckets中取元素重新放到原arrays数组中
            for (int j = 0; j < counts.length; j++) {
                // 逐个放回
                for (int k = 0; k < counts[j]; k++) {
                    arrays[index] = buckets[j][k];
                    index++;
                }
                counts[j] = 0; // 放空则计数归零
            }
            index = 0; // 一轮操作完毕,初始归零
        }
        return arrays;
    }

    // 执行
    public static void main(String[] args) {
        int[] input = new int[] { 163, 604, 196, 5, 561, 2, 73, 0, 18, 81, 495, 113, 220, 9, 284, 666 };
        System.out.println("样本初始样本数据:" + Arrays.toString(input));
        int[] output = RadixSort.sort(input);
        System.out.println("基数排序结果数据:" + Arrays.toString(output));
    }
}

算法延伸

  基数排序其实是桶排序原理的一种应用。桶排序,简单地说,就是把数据元素按照一定的规则分组,类似放在一个个的桶中,然后对每个桶里面的的数据再进行排序。
  这个桶里的排序,是递归这个排序本身也可以,也可以是之前提到的任何一种排序方式,总之,就是将大数据样本按规则顺序拆分,分批处理,然后再统入列。
  基数排序就是这样,按照数位从低到高排序并递归这个操作的。

算法分析

  选择排序算法的基本性能,归纳如下图:


其中,d代表待排最大元素位数,n代表元素个数。

1、 时间复杂度

  基数排序的时间复杂度是O(d×n),其中d是排序元素个数,n是数字位数
  留意这个d,如果待排最大元素位数比较小,基数排序的时间复杂度被认为接近于O(n),即接近线性级别,性能比较优越;如果最大元素位数过长,甚至长过这个n,则它的时间复杂度则退化甚至差于O(n2),此时,采用基数排序将不是最优方案。
  此外,基数排序的桶因素,因为桶数量k的选用是确定的较小常数,即一般不考虑为时间复杂度计算的主要因素。
  根据上边的程序分析说明等可了解到,基数排序的时间复杂度跟原数列是否有序,都需要从数位从低到高依次进行,跟其他因素没有太大关系,起决定作用的主要是d和n,故最好最坏,平均时间复杂度均是O(d×n)

2、 空间复杂度

  跟据基数排序的特性,它的空间复杂度为O(n+k),其中k为桶的数量,所有桶实际占用总容量足以分配n个元素即可。这里有个使用上容易产生误导的地方,当我们在程序层面开辟桶空间的时候,使用数组时,每个桶的长度肯定要考虑极端情况,桶长会被设置为整个待排元素的个数,会造成占用空间为k×n的假象;但如果从链表角度考虑,就完全不是这样子了。

3、稳定性

  在基数排序过程中,每次都是将当前位数上相同数值的元素统一“装桶”,并不需要交换位置。所以基数排序是稳定的算法


老狗啃骨头



慷慨发言

(您提供的信息将用于后续必要的反馈联系,本站会恪守隐私)

潜影拾光

波密雪山

天黑路暗,想看清东西 换个角度或许会比较好

扫码转发

二维码
二维码
二维码
二维码
二维码
二维码

博文标签