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

基数排序

已同步微信公众号【乐享Coding】,想要一起学习的可以加群,共同交流!

基本思想:

将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。

从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

理论比较晦涩难懂,我们以具体例子进行图解:

本次举例的待排序数组为:[1, 52, 478, 12, 83, 7, 45, 333 ]

大家先可以看看排序的步骤,进一步对思路有个大致理解!

步骤一

准备是10个桶(0-9),取数组中每一个元素个位的值,分别装入对应的桶中。

根据动图可以看到桶应该是个二维数组,既要有编号(0-9),又要存数据(0-arr.length)。

代码实现:

定义桶(二维数组):

int[][] bucket = new int[10][arr.length];

除了桶这个需要额外空间之外,我们还需要一个数组来记录每个桶中的元素个数,目的是方便最后取出。

int[] bucketElementCounts = new int[10]; //桶中元素个数

在此,我们还可以发现一个问题就是可能会存在空桶占用空间资源,所以基数排序典型的占用空间换取时间,文章末尾会对这种排序时间和空间复杂度的分析!

     for (int j = 0; j < arr.length; j++) {
                // 算法:取出个位数字
                int digitOfElement = arr[j] / 1 % 10;
                // 把当前遍历的数据放入指定的数组中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                // 记录数量
                bucketElementCounts[digitOfElement]++;
            }

在个位排序之后bucketElementCounts数组的应该为:

之后根据以下代码将桶中元素取出替换arr数组

            int index = 0;
            // 把各个桶中(10个桶)存放的数字取出来, 放入到arr中
            for (int k = 0; k < bucketElementCounts.length; k++) {
                // 如果这个桶中,有数据才取,没有数据就不取了
                if (bucketElementCounts[k] != 0) {
                    // 循环取出元素
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        // 取出元素
                        arr[index++] = bucket[k][l];
                    }
                    // 把这个桶的对应的记录的数据个数置为0,注意,桶本身数据(前面存的数据还在)
                    bucketElementCounts[k] = 0; //
                }

上述排序后的新arr数组为

步骤二:第一次是找个位接下来就是找十位的数字,重复步骤一,在找出百位的数字,同样重复步骤一。最后arr数组就会按照升序排序结束。

在此只展示代码不同的地方,其余地方完全一样。

//算法:取出十位的数字
int digitOfElement = arr[j] / 10 % 10;

//算法:取出百位的数字
int digitOfElement = arr[j] / 100 % 10;

此时arr数组已经成了完全升序的数组了。

优化

最后我们总结以下可以优化的,此次排序一共进行了三轮,且每轮都有重复代码,因此这样是非常冗余的。

观察发现与最大元素的位数有关,联想简单选择排序我们可知只需找出最大值,最大值占几位,那么就需要循环几轮。

本例最大元素是478,共占三位,所以需要循环三轮,通过改进代码我们得到了最终代码:

   public static void radixSort(int[] arr) {
        // 假定arr[0] 是最大数
        // 1. 通过遍历arr, 找到数组中真正最大值
        // 2. 目的是确定要进行多少轮排序
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        // 计算最大数字是几位数
        int maxLength = (max + "").length();
        // 定义一个二维数组, 就是10个桶
        // 1. 该二维数组有10个一维数组 0-9
        // 2. 为了防止溢出,每个一维数组(桶),大小定为 arr.length
        // 3. 很明确, 基数排序是空间换时间
        int[][] bucket = new int[10][arr.length];
        // 用于记录在每个桶中,实际存放了多少个数据,这样才能正确的取出
        int[] bucketElementCounts = new int[10];
        // 根据最大长度的数决定比较的次数
        // 1. 大循环的次数就是 最大数有多少位,前面分析过
        // 2. n = 1, n *= 10 是为了每轮循环排序时,分别求出各个元素的 个位,十位,百位,千位 ...
        //    就是一个小算法
        // 3. 这个基础排序,完全可以使用 冒泡分步写代码来完成,比较简单!!
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            // 把每一个数字分别计算本轮循环的位数的值,比如第1轮是个位...
            for (int j = 0; j < arr.length; j++) {
                // 计算
                int digitOfElement = arr[j] / n % 10;
                // 把当前遍历的数据放入指定的数组中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                // 记录数量
                bucketElementCounts[digitOfElement]++;
            }
            // 记录取的元素需要放的位置
            int index = 0;
            // 把各个桶中(10个桶)存放的数字取出来, 放入到arr中
            for (int k = 0; k < bucketElementCounts.length; k++) {
                // 如果这个桶中,有数据才取,没有数据就不取了
                if (bucketElementCounts[k] != 0) {
                    // 循环取出元素
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        // 取出元素
                        arr[index++] = bucket[k][l];
                    }
                    // 把这个桶的对应的记录的数据个数置为0,注意,桶本身数据(前面存的数据还在)
                    bucketElementCounts[k] = 0; //
                }
            }
        }
    }
时间和空间复杂度分析

空间复杂度:

  • 最坏情况下:O(m*n) m代表“桶”的个数,一般是10。

时间复杂度:O(m+n)

  • m是保存桶的元素个数数组占的空间,n是保存桶的二维数组占的空间。

最后总结

1)基数排序是对传统桶排序的扩展,速度很快.

2)基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。

3)基数排序时稳定的(值相等的元素排序后前后顺序不变)。

4)有负数的数组,我们不用基数排序来进行排序, 如果要支持负数,需要添加绝对值并反转。

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Steve_hanhaiLong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值