跳转至

基数排序

一、算法描述

1.1 核心思想

  • 数据有高低位之分,位之间有递进关系
  • 高位相等的情况下,才去对比低位大小
  • 按照低位到高位的顺序,使用稳定排序算法对每一位排序

1.2 细节解释

数据有高低位之分,比如十进制数字 1232,字符串 dwsc,二进制 1001 等等。

这些类型的数据都有高低位,而且是高位大的数据肯定比高位小的数据大。

比如 123 和 234,123 最高位 1 小于 234 最高位 2,所以 123 < 234。

  • 基数排序就是利用数据的每一位进行排序,最终得到有序序列的

比如说,数组 [12, 3, 154, 78, 9, 245, 35, 92]。

因为数字长度不同,所以首先将所有数字补 0 进行长度对齐:

012
003
154
078
009
245
035
092

然后按照从低位到高位进行排序:

先按“个位”排序:

当前数组     取出“个”位     按“个”位排序

  012           xx2            012
  003           xx3            092
  154           xx4            003
  078    =>     xx8     =>     154
  009           xx9            245
  245           xx5            035
  035           xx5            078
  092           xx2            009

再按“十位”排序:

当前数组     取出“十”位     按“十”位排序

  012           x1x            003
  092           x9x            009
  003           x0x            012
  154    =>     x5x     =>     035
  245           x4x            245
  035           x3x            154
  078           x7x            078
  009           x0x            092

再按“百位”排序:

当前数组     取出“百”位     按“百”位排序

  003           0xx            003
  009           0xx            009
  012           0xx            012
  035    =>     0xx     =>     035
  245           2xx            078
  154           1xx            092
  078           0xx            154
  092           0xx            245

每个“位”都排完之后,就是最终的排序结果了:

[3, 9, 12, 35, 78, 92, 154, 245]

基数排序的关键在于从低位往高位排序,这样能保证高位排序是最后的。

二、算法实现

2.1 计算数据的基数

// 找到最大值
int max = Integer.MIN_VALUE;
for (int a : arr) {
    if (a > max) {
        max = a;
    }
}

// 算出基数(即位数)
int radix = 0;
while(max > 0) {
    max = max / 10;
    radix++;
}

2.2 从低位到高位排序

// 从低位到高位对数组进行排序
int exp = 1;
for (int i = 0; i < radix; i++) {
    countSort(arr, exp);
    exp *= 10;
}

对每一位进行排序,则使用计数排序(或者其他稳定排序算法也行)。

三、算法分析

3.1 时间复杂度

  • 最好时间复杂度:O(n)
  • 最坏时间复杂度:O(n)
  • 平均时间复杂度:O(n)

3.2 空间复杂度

  • 空间复杂度:O(n)
  • 非原地算法

3.3 稳定性

  • 稳定排序算法

四、适用场景

  • 数据可以分割出“位”来比较
  • 数据有高低位之分,位之间有递进关系
  • 每一位的数据范围不能太大,要可以用线性排序算法来排序

附录

/**
 * 基数排序
 * <p>
 * 时间复杂度:O(n)
 * <p>
 * 空间复杂度:O(n)
 * <p>
 * 稳定性:稳定
 *
 * @author weijiaduo
 * @since 2022/9/6
 */
public class RadixSort implements Sort {

    @Override
    public void sort(int[] arr) {
        // 找到数组最大值
        int max = Integer.MIN_VALUE;
        for (int a : arr) {
            if (a > max) {
                max = a;
            }
        }

        // 计算基数大小
        int radix = 0;
        while(max > 0) {
            max = max / 10;
            radix++;
        }

        // 从低位到高位对数组进行排序
        int exp = 1;
        for (int i = 0; i < radix; i++) {
            countSort(arr, exp);
            exp *= 10;
        }
    }

    /**
     * 计数排序
     *
     * @param arr 数组
     * @param exp 指数
     */
    private void countSort(int[] arr, int exp) {
        // 统计每个数字(0-9)的次数
        int[] counts = new int[10];
        for (int a : arr) {
            counts[(a / exp) % 10]++;
        }

        // 累计数字的次数和
        for (int i = 1; i < counts.length; i++) {
            counts[i] += counts[i - 1];
        }

        // 更新排序结果到原数组
        int[] copy = Arrays.copyOf(arr, arr.length);
        for (int i = copy.length - 1; i >= 0; i--) {
            int index = (copy[i] / exp) % 10;
            counts[index]--;
            arr[counts[index]] = copy[i];
        }
    }

}