5.2 基数排序:逐位排序的智慧

历史背景

基数排序的历史可以追溯到 1887 年 Hermann Hollerith 发明的制表机,它使用类似基数排序的原理来处理穿孔卡片。在计算机科学中,基数排序由 Harold H. Seward 于 1954 年在 MIT 首次描述。基数排序是一种非比较排序算法,它通过对数字的每一位进行排序来达到整体有序。在早期计算机系统中,基数排序因其线性时间复杂度而受到重视。

算法原理

基数排序分为 LSD(Least Significant Digit,最低位优先)和 MSD(Most Significant Digit,最高位优先)两种:

LSD 基数排序

  1. 从最低位开始
  2. 使用稳定排序(通常用计数排序)按当前位排序
  3. 依次处理更高位
  4. 最终得到有序序列

MSD 基数排序

  1. 从最高位开始
  2. 按当前位分组
  3. 对每个组递归处理下一位
  4. 最终得到有序序列

Java 实现

java
复制代码
import java.util.Arrays;

public class RadixSort {
    
    /**
     * LSD 基数排序(适用于非负整数)
     */
    public static void sortLSD(int[] arr) {
        if (arr.length <= 1) return;
        
        // 找到最大值,确定位数
        int max = arr[0];
        for (int value : arr) {
            if (value > max) max = value;
        }
        
        // 从最低位开始,对每一位进行计数排序
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSortByDigit(arr, exp);
        }
    }
    
    /**
     * 按指定位数进行计数排序
     * @param exp 当前处理的位数(1=个位,10=十位,100=百位...)
     */
    private static void countingSortByDigit(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n];
        int[] count = new int[10];  // 0-9 共 10 个数字
        
        // 统计每个数字出现的次数
        for (int value : arr) {
            int digit = (value / exp) % 10;
            count[digit]++;
        }
        
        // 计算累计次数
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }
        
        // 从后往前遍历,保持稳定性
        for (int i = n - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % 10;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }
        
        // 复制回原数组
        System.arraycopy(output, 0, arr, 0, n);
    }
    
    /**
     * LSD 基数排序(适用于包含负数的整数)
     */
    public static void sortLSDWithNegative(int[] arr) {
        if (arr.length <= 1) return;
        
        // 分离正数和负数
        int negativeCount = 0;
        for (int value : arr) {
            if (value < 0) negativeCount++;
        }
        
        int[] negatives = new int[negativeCount];
        int[] positives = new int[arr.length - negativeCount];
        
        int negIndex = 0, posIndex = 0;
        for (int value : arr) {
            if (value < 0) {
                negatives[negIndex++] = -value;  // 转为正数处理
            } else {
                positives[posIndex++] = value;
            }
        }
        
        // 分别排序
        sortLSD(negatives);
        sortLSD(positives);
        
        // 合并:负数逆序放前面,正数顺序放后面
        int index = 0;
        for (int i = negatives.length - 1; i >= 0; i--) {
            arr[index++] = -negatives[i];
        }
        for (int value : positives) {
            arr[index++] = value;
        }
    }
    
    /**
     * MSD 基数排序(适用于字符串)
     */
    public static void sortStrings(String[] arr) {
        if (arr.length <= 1) return;
        String[] temp = new String[arr.length];
        msdSort(arr, temp, 0, arr.length - 1, 0);
    }
    
    private static void msdSort(String[] arr, String[] temp, int low, int high, int charIndex) {
        if (low >= high) return;
        
        int R = 256;  // ASCII 字符集大小
        int[] count = new int[R + 2];  // 多两个用于处理字符串结束的情况
        
        // 统计频率
        for (int i = low; i <= high; i++) {
            int c = charAt(arr[i], charIndex);
            count[c + 2]++;
        }
        
        // 计算累计频率
        for (int r = 0; r < R + 1; r++) {
            count[r + 1] += count[r];
        }
        
        // 分配
        for (int i = low; i <= high; i++) {
            int c = charAt(arr[i], charIndex);
            temp[count[c + 1]++] = arr[i];
        }
        
        // 复制回原数组
        System.arraycopy(temp, 0, arr, low, high - low + 1);
        
        // 递归处理每个字符组
        for (int r = 0; r < R; r++) {
            msdSort(arr, temp, low + count[r], low + count[r + 1] - 1, charIndex + 1);
        }
    }
    
    private static int charAt(String s, int index) {
        if (index < s.length()) {
            return s.charAt(index);
        }
        return -1;  // 字符串结束
    }
    
    /**
     * 基数排序(泛型版本,适用于实现了整数转换接口的类型)
     */
    public static void sortGeneric(int[] arr, int base) {
        if (arr.length <= 1) return;
        
        int max = arr[0];
        for (int value : arr) {
            if (value > max) max = value;
        }
        
        for (int exp = 1; max / exp > 0; exp *= base) {
            countingSortByDigitBase(arr, exp, base);
        }
    }
    
    private static void countingSortByDigitBase(int[] arr, int exp, int base) {
        int n = arr.length;
        int[] output = new int[n];
        int[] count = new int[base];
        
        for (int value : arr) {
            int digit = (value / exp) % base;
            count[digit]++;
        }
        
        for (int i = 1; i < base; i++) {
            count[i] += count[i - 1];
        }
        
        for (int i = n - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % base;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }
        
        System.arraycopy(output, 0, arr, 0, n);
    }
}

效率分析

指标 最好情况 最坏情况 平均情况
时间复杂度 O(d(n+k)) O(d(n+k)) O(d(n+k))
空间复杂度 O(n+k) O(n+k) O(n+k)

其中 d 是数字的位数,k 是基数(通常为 10 或 256)。基数排序是稳定的排序算法(使用稳定排序作为子程序时),是非比较排序算法。它可以在 O(d·n) 时间内完成排序,当 d 为常数时,这是线性时间复杂度。

应用场景

  • 整数排序
  • 字符串排序
  • 固定长度的数据排序
  • 需要稳定排序且数据范围有限的场景
  • 大量数据的排序(如果位数较少)