计数排序和基数排序都是非比较排序。 都是稳定的。

计数排序与计数排序的思想都来自于桶排序,桶排序的大体思想为:若 N 个关键字均可以映射到 0 - M 的整数范围内,则可以建立 M + 1 大小的桶,然后依次遍历 N 个关键字,依次将他们放到自己对应的那个桶里面,最后从 0 开始依次将桶中的元素倒出即可。

一、计数排序

1、用一个数组用来保存每一个数字出现的次数
2、根据每一个元素出现的次数,按照下标在原数组中排列,这样这组数据就有序

假设序列值均为整数,首先获得序列的最大值 max 和最小值 min,建立一个大小为(max - min + 1)的数组 count,遍历该序列 data,count[data[i] - min]++。count 中第 k 个位置上的元素即为 k + min 出现的次数,再根据次数复制到 data 中去,完成排序。

public static void countSort(int[] data) {
    // 找到最小值,最大值
    int max = data[0], min = data[0];
    for (int i = 0; i < data.length; i++) {
        if (data[i] > max) max = data[i];
        if (data[i] < min) min = data[i];
    }
    int[] count = new int[max - min + 1];
    for (int tmp : data) count[tmp - min]++; // 偏移量 min
    // 把值复制到 data 中
    for (int i = 0, k = 0; i < count.length; i++) 
        for (int j = 0; j < count[i]; j++) data[k++] = i + min;        
}

计数排序是 N 的线性范围复杂度的排序,其时间复杂度为 O(N)。空间复杂度由于建了 M 个桶,因此其空间复杂度为 O(M)。

二、基数排序

一般用于多个关键字的排序(如扑克牌中的花色和面值大小),基数排序分为两种主位优先和次位优先两种,更广泛的是次位优先。所谓次位优先,先以次位关键字建立桶,将所有元素按照关键字依次扔进桶中,再依次从小到大从桶中放回到原数组。对于多个关键字依次从次到主重复上述操作即可完成排序。

以三位数的整数排序为例,将三位数的百位、十位、个位作为关键字,显然百位是主位。

public static void radixSort(int[] data) {
    // 以三位数的整数为例
    int N = 10;
    // 创建桶
    List<LinkedList<Integer>> buck = new ArrayList<LinkedList<Integer>>(N);
    for (int i = 0; i < N; i++) {
        buck.add(new LinkedList<Integer>());
    }
    for (int i = 0, k = 1; i < 3; i++, k *= 10) { // 从低位数第i位 0=个位 1=百位
        int temp = 0;
        for (int j = 0; j < data.length; j++) {
            tmp = data[j] % (10 * k) / k; // 取出 i 位上的数
            buck.get(tmp).add(data[j]);
        }
        // 往 data 中倒
        for (int j = 0, m = 0; j < N; j++) {
            while (buck.get(j).size() > 0) {
                data[m++] = buck.get(j).remove();
            }
        }
    }
}

复杂度分析:
时间复杂度为 O(D(N+M)),其中 D 为关键字的个数,M 为桶的个数,N 为序列长度。