「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」
基数排序
也是一种利用桶来排序的算法。
基数排序有两种方式
- MSD(Most significant digital):先从高位开始进行排序,在每个关键字上,可采用计数排序
- LSD(Least significant digital):先从低位开始进行排序,在每个关键字上,可采用桶排序
我们按LSD来举例
- 先按数字的各位来排序
- 再按数字的十位来排序
- 再按数字的百位...
- 位数最长的数字有几位,我们就排序几轮
如下:[82, 3, 59, 44, 18],经过两轮排序后的结果如下
思路
其实思路很简单,主要就是要明白为什么这样就能让数组排序正确呢?
我这里不会用数学的方法证明,就说一下我的理解
- 先按个位排序,可以保证如果其他位都相同的情况下,数字的顺序可以正确排序
- 再按十位排序,可以保证
- 如果十位不相同的数字,在百位及以上位的数字相同的情况下,数字顺序可以正确排序
- 如果十位相同的数字,由于上一轮根据各位排过序了,所以顺序也可以正确排序
- 再按百位,千位,逻辑相同
实际操作的时候,会遇到有负数的情况,我们操作流程如下
- 先求出最长数字的长度,决定我们要遍历多少轮
- 遍历数组,按个位,将每个数字放进自己对应的桶,由于会有负数,我们桶的下标又只能从0开始,所以,我们将所有个位的余数 + 10 ,来代表桶的下标
- 再遍历桶数组,将所有数字拿出来
- 再遍历一轮,用十位来重复一遍上述操作
- ...百位,千位等等,直到轮数结束
代码如下
/**
* @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)
}
这个代码可以理解起来会有点不方便,有个更好理解的,优化后更好理解,如下
做了以下优化
- 遍历数组,将所有数减去最小值,再进行排序,这样的话我们中间就不用考虑负数的情况。
- 排序完之后,再将所有数字加回最小值即可。
/**
* @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),用于将所有数字存与桶内