基数排序算法
2022年12月30日 · 282 字 · 2 分钟
基数排序又叫桶排序,是一种时间复杂度为$O(n)$的排序算法,但是相比于其他排序算法有$O(n)$的空间复杂度。
思路
基数排序的核心思路如下:
- 准备0~9的10个桶,根据数字当前比较位的值来决定放入哪个桶。如当前比较个位,则数字13应该放入索引为3的桶中;当前比较百位,则123应该放入索引为1的桶中。
- 当所有数字全部放入桶之后,遍历0~9这10个桶,然后依次将数字保存到待排序数组,因为桶是有序的,所以本轮放回去的数字是有序的。
- 当前比较的位数左移,比如本轮比较个位,下一轮应该比较百位。
- 重复步骤1~3。
举例
现在我们来看一个实际例子。
待排序数字:717, 328, 803, 422, 586, 944, 557, 308, 496, 624
第1轮比较个位
直接按照个位放入桶中。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 
|---|---|---|---|---|---|---|---|---|---|
| 422 | 803 | 624 | 586 | 717 | 328 | ||||
| 496 | 557 | 308 | 
按照从左到右,从上到下的原则将数字归位:422,803,624,586,496,717,557,328,308
第2轮比较十位
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 
|---|---|---|---|---|---|---|---|---|---|
| 803 | 717 | 422 | 557 | 586 | 496 | ||||
| 308 | 624 | ||||||||
| 328 | 
按照从左到右,从上到下的原则将数字归位:803,308,717,422,624,328,557,586,496
第3轮比较百位
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 
|---|---|---|---|---|---|---|---|---|---|
| 308 | 422 | 557 | 624 | 717 | 803 | ||||
| 328 | 496 | 586 | 
按照从左到右,从上到下的原则将数字归位:308,328,422,496,557,586,624,717,803
可以发现,比较的轮次由数组中最大的数字决定,以上面的例子来说,如果还存在一个1234数字,那么需要比较4轮才可以完成排序。
代码实现
class RadixSort {
    // 基数排序
    public void sort(int[] nums) {
        if (nums.length <= 1) {
            return;
        }
        var max = Arrays.stream(nums).max().getAsInt();
        // 当前处理位数
        var exp = 1;
        // 桶
        var bucket = new int[10][nums.length];
        // 记录每个桶有几个数字
        var bucketCount = new int[10];
        while (max >= exp) {
            // 求得每个数字当前位数的值
            for (var num : nums) {
                // 求得当前位余数
                var bitNumber = (num / exp) % 10;
                // 放入桶, index是桶的index,在同一个桶的数字需要index来标识位置
                var index = bucketCount[bitNumber];
                bucket[bitNumber][index] = num;
                // 桶内数量+1
                bucketCount[bitNumber]++;
            }
            // 桶内数字归位
            int k = 0; // 已归位的数字下标
            for (var i = 0; i < 10; i++) {
                if (bucketCount[i] > 0) { // 当前桶有数字
                    for (int j = 0; j < bucketCount[i]; j++) { // 遍历同一个桶的数字
                        nums[k] = bucket[i][j];
                        k++;
                    }
                }
                // 桶数字清空
                bucketCount[i] = 0;
            }
            // 位数左移
            exp *= 10;
        }
        max = Integer.MIN_VALUE;
        for (int i = 1; i < nums.length; i++) {
            var gap = nums[i] - nums[i - 1];
            if (gap > max) {
                max = gap;
            }
        }
    }
    public static void main(String[] args) {
        var sort = new RadixSort();
        var list = new int[]{422, 803, 624, 586, 496, 717, 557, 328, 308};
        sort.sort(list);
        System.out.println(Arrays.toString(list));
    }
}
时间复杂度: $O(n)$ ,严格来说是$O(log(n))$。$n$是待排序数组长度,在数据量小的情况下,最外层的while循环遍历次数可以认为是常数,内部嵌套的for循环次数为数组长度$n$,因此时间复杂度为$O(n)$;在数据量大的情况下,最外层的while循环次数为$O(log(n))$,内部嵌套的for循环次数依旧是$n$,因此时间复杂度为$O(nlog(n))$。
空间复杂度:$O(n)$。$n$是待排序数组长度,$bucket$的大小为$10*n$,$bucketCount$大小为$n$,因此总体空间复杂度为$O(n)$。
