基数排序

发布时间 2023-07-03 21:36:15作者: 白露~

什么是基数排序?

基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。12

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。12

LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序或计数排序。12

MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序或递归的基数排序。12

基数排序的过程

以LSD为例,假设我们要对数组 arr = {53, 3, 542, 748, 14, 214, 154, 63, 616} 进行基数排序,其过程如下:

第一步:按个位进行排序

首先,我们需要准备10个桶(0-9),然后根据数组中每个元素的个位数字,将其放入对应的桶中。

例如,53的个位是3,所以放入第3个桶中;542的个位是2,所以放入第2个桶中;以此类推。

 

接着,我们按照桶的顺序,依次将桶中的元素取出,并放回原数组中。

例如,第0个桶中没有元素,所以跳过;第1个桶中有一个元素3,所以将3放入原数组的第一个位置;第2个桶中有两个元素542和214,所以将它们依次放入原数组的第二和第三位置;以此类推。

 

这样,我们就完成了按个位进行排序的过程。

第二步:按十位进行排序

接下来,我们重复第一步的操作,只不过这次是根据数组中每个元素的十位数字来分配桶。

例如,3的十位是0,所以放入第0个桶中;53的十位是5,所以放入第5个桶中;以此类推。

 

然后,我们同样按照桶的顺序,依次将桶中的元素取出,并放回原数组中。

例如,第0个桶中有两个元素3和14,所以将它们依次放入原数组的第一个和第二位置;第1个桶中有一个元素616,所以将616放入原数组的第三位置;以此类推。

 

这样,我们就完成了按十位进行排序的过程。

第三步:按百位进行排序

最后,我们再次重复第一步的操作,只不过这次是根据数组中每个元素的百位数字来分配桶。

例如,3的百位是0,所以放入第0个桶中;616的百位是6,所以放入第6个桶中;以此类推。

 

然后,我们同样按照桶的顺序,依次将桶中的元素取出,并放回原数组中。

例如,第0个桶中有四个元素3, 14, 53, 63,所以将它们依次放入原数组的第一个到第四位置;第1个桶中有一个元素154,所以将154放入原数组的第五位置;以此类推。

 

这样,我们就完成了按百位进行排序的过程。

由于数组中最大的数是748,只有三位数,所以我们不需要再进行更高位的排序了。此时,原数组已经变成了一个有序序列。

基数排序的代码实现

下面是用JAVA语言实现基数排序的代码:

public class RadixSort {

    // 辅助函数,求数据的最大位数
    public static int maxBit(int[] data) {
        int max = data[0]; // 假设第一个数是最大数
        for (int i = 1; i < data.length; i++) { // 遍历找出最大数
            if (data[i] > max) {
                max = data[i];
            }
        }
        int d = 1; // 最大位数
        int p = 10; // 基数
        while (max >= p) { // 循环判断最大数有几位
            max /= 10;
            d++;
        }
        return d;
    }

    // 基数排序
    public static void radixSort(int[] data) {
        int n = data.length; // 数组长度
        int d = maxBit(data); // 最大位数
        int[][] bucket = new int[10][n]; // 桶数组,二维数组,每个桶是一个一维数组
        int[] count = new int[10]; // 记录每个桶中存放了多少个元素
        int radix = 1; // 基数,从个位开始,依次乘以10
        for (int i = 0; i < d; i++) { // 进行d轮排序
            for (int j = 0; j < n; j++) { // 遍历数组,按照当前位数分配到对应的桶中
                int k = (data[j] / radix) % 10; // 计算当前位数的数字
                bucket[k][count[k]] = data[j]; // 将元素放入桶中
                count[k]++; // 桶中元素个数加一
            }
            int index = 0; // 记录取出元素的位置
            for (int k = 0; k < 10; k++) { // 遍历每个桶,按照顺序取出元素
                if (count[k] != 0) { // 如果桶中有元素才取出
                    for (int l = 0; l < count[k]; l++) { // 遍历桶中的元素
                        data[index++] = bucket[k][l]; // 取出元素放回原数组
                    }
                    count[k] = 0; // 取出后将桶中元素个数置为0,方便下一轮使用
                }
            }
            radix *= 10; // 基数乘以10,进入下一位的排序
        }
    }

    public static void main(String[] args) {
        int[] data = {53, 3, 542, 748, 14, 214, 154, 63, 616};
        System.out.println("排序前:" + Arrays.toString(data));
        radixSort(data);
        System.out.println("排序后:" + Arrays.toString(data));
    }
}

输出结果:

排序前:[53, 3, 542, 748, 14, 214, 154, 63, 616]
排序后:[3, 14, 53, 63, 154, 214, 542, 616, 748]

基数排序的复杂度分析

时间复杂度:O(d * (n + r)),其中d是最大数的位数,n是数组长度,r是基数(桶的数量)。

空间复杂度:O(n + r),其中n是数组长度,r是基数(桶的数量)。

稳定性:稳定,因为每一轮排序都是按照相同的位数进行比较,不会改变相同元素的相对顺序。

基数排序的优缺点

优点:

  • 适用于整数排序,尤其是位数较多的情况。
  • 稳定,不会改变相同元素的相对顺序。
  • 时间复杂度较低,当d较小且r较大时,接近于线性时间。

缺点:

  • 需要额外的空间来存储桶和计数器。
  • 不适用于小数或负数的排序。
  • 对于位数较少或者范围较小的整数,可能不如其他排序算法快。

参考资料

【算法】排序算法之基数排序 - 知乎

最详细的【基数排序】—排序算法,思路清晰动图保姆级讲解,五分钟搞懂! _Steve_hanhaiLong的博客-CSDN博客

1.10 基数排序 | 菜鸟教程

基数排序 - 维基百科,自由的百科全书

基数排序 - 如果天空不死 - 博客园