[路飞]_leetcode刷题(javascript)_基数排序

114 阅读3分钟

「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战

基数排序

也是一种利用桶来排序的算法。

基数排序有两种方式

  • MSD(Most significant digital):先从高位开始进行排序,在每个关键字上,可采用计数排序
  • LSD(Least significant digital):先从低位开始进行排序,在每个关键字上,可采用桶排序

我们按LSD来举例

  1. 先按数字的各位来排序
  2. 再按数字的十位来排序
  3. 再按数字的百位...
  4. 位数最长的数字有几位,我们就排序几轮

如下:[82, 3, 59, 44, 18],经过两轮排序后的结果如下

image.png

思路

其实思路很简单,主要就是要明白为什么这样就能让数组排序正确呢?

我这里不会用数学的方法证明,就说一下我的理解

  1. 先按个位排序,可以保证如果其他位都相同的情况下,数字的顺序可以正确排序
  2. 再按十位排序,可以保证
    • 如果十位不相同的数字,在百位及以上位的数字相同的情况下,数字顺序可以正确排序
    • 如果十位相同的数字,由于上一轮根据各位排过序了,所以顺序也可以正确排序
  3. 再按百位,千位,逻辑相同

实际操作的时候,会遇到有负数的情况,我们操作流程如下

  1. 先求出最长数字的长度,决定我们要遍历多少轮
  2. 遍历数组,按个位,将每个数字放进自己对应的桶,由于会有负数,我们桶的下标又只能从0开始,所以,我们将所有个位的余数 + 10 ,来代表桶的下标
  3. 再遍历桶数组,将所有数字拿出来
  4. 再遍历一轮,用十位来重复一遍上述操作
  5. ...百位,千位等等,直到轮数结束

代码如下

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function(nums) {
    let maxVal = Math.max(...nums);
    let minVal = Math.min(...nums);
    // 计算出最长数字的长度
    let maxLength = getMaxLenth(maxVal,minVal);
    // 用来取指定位数的值的模数,1代表取各位,10代表取十位,100代表取百位,依次类推
    let mod = 1;
    let res = [];
    // 整体遍历轮数,由最大值决定
    for(let i=0;i<maxLength;i++,mod*=10){
        // 用来存放值的桶数组
        let bucket = [];
        for(let j=0;j<nums.length;j++){
            // 取出当前用来排序的数,算出它的指定位数的值
            let modVal = parseInt(nums[j]/mod)%10;
            let bucketIndex = 0;
            // 将余数+10,这样桶下标就可以从1开始,而不是负数
            bucketIndex = modVal + 10;
            if(bucket[bucketIndex] == undefined){
                bucket[bucketIndex] = []
            }
            bucket[bucketIndex].push(nums[j])
        }
        res = [];
        for(let j=0;j<bucket.length;j++){
            if(bucket[j] != undefined){
                // 如果是10号桶,即当前尾数为0的桶,桶内既有正数,也有负数,由于负数一定小于正数
                // 我们先将所有的负数取出来
                if(j == 10){
                    for(let k=0;k<bucket[j].length;k++){
                        // 正数先全部跳过
                        if(bucket[j][k] >= 0) continue;
                        // 先把所有负数取出来放到res里
                        res.push(bucket[j][k])
                    }
                    // 再将负数过滤掉,只保留正数
                    bucket[j] = bucket[j].filter((item)=>{
                        return item>=0
                    })
                }
                // 遍历桶内的数,把先放入的先拿出来,这样能保证是稳定排序
                while(bucket[j].length>0){
                    res.push(bucket[j].shift())
                }
            }
        }
        nums = res;
    }
    return nums;
};

function getMaxLenth(maxVal,minVal){
    let maxValLength = Math.abs(maxVal).toString().length;
    let minValLength = Math.abs(minVal).toString().length;
    return Math.max(maxValLength,minValLength)
}

这个代码可以理解起来会有点不方便,有个更好理解的,优化后更好理解,如下

做了以下优化

  1. 遍历数组,将所有数减去最小值,再进行排序,这样的话我们中间就不用考虑负数的情况。
  2. 排序完之后,再将所有数字加回最小值即可。
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function(nums) {
    let minVal = Math.min(...nums);
    // 将所有值都减去最小值,保证我们没有负数,那后面的流程就能简化了
    for(let i=0;i<nums.length;i++){
        nums[i] -= minVal;
    }
    // 计算出最长数字的长度
    let maxLength = Math.max(...nums).toString().length;
    // 用来取指定位数的值的模数,1代表取各位,10代表取十位,100代表取百位,依次类推
    let mod = 1;
    let res = [];
    // 整体遍历轮数,由最大值决定
    for(let i=0;i<maxLength;i++,mod*=10){
        // 用来存放值的桶数组
        let bucket = [];
        for(let j=0;j<nums.length;j++){
            // 取出当前用来排序的数,算出它的指定位数的值
            let bucketIndex = parseInt(nums[j]/mod)%10;
            if(bucket[bucketIndex] == undefined){
                bucket[bucketIndex] = []
            }
            bucket[bucketIndex].push(nums[j])
        }
        res = [];
        for(let j=0;j<bucket.length;j++){
            if(bucket[j] != undefined){
                // 遍历桶内的数,把先放入的先拿出来,这样能保证是稳定排序
                while(bucket[j].length>0){
                    res.push(bucket[j].shift())
                }
            }
        }
        nums = res;
    }
    // 再把每一项的minVal加回来即可
    for(let i=0;i<nums.length;i++){
         nums[i] += minVal;
    }
    return nums;
};

复杂度分析

时间复杂度:O(n*k),n为数组的长度,k为最长数字的长度。

空间复杂度:O(n),用于将所有数字存与桶内