基数排序(radix sort)属于“分配式排序”(distribution sort),
又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,
将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,
其时间复杂度为O(nlog(r)m) ,其中r为所采取的基数,而m为堆数,在某些时候,
基数排序法的效率高于其它的稳定性排序法。
解释
以上是 百度百科 关于 基数排序
的描述。核心点如下:
- 是一种
非比较
的排序 - 计数排序的
进阶版
- 按照相同位
有效数字
的值分组排序
- 有
桶
的概念 通过一个桶
从右至左
按照每一位的大小依次
进行排序
- 每一位的
排序
都遵循队列
进行先入先出
重新写入
这样讲比较晦涩,下面用一个例子
来完整的叙述基数排序
:
1⃣️ 初始化
比如说现在有一个这样的无序数组 arr
const arr = [10, 200, 13, 12, 7, 88, 91, 24]
而且也有这样的一个桶 buckets
const buckets = Array.from({length:10},()=>[]);
// 通过Array.from新建一个长度为10的二维数组
// 等同于:
// const buckets = [[],[],[],[],[],[],[],[],[],[]]
// 为啥长度必须是10 因为是十进制啊
2⃣️ 取原数组的每个元素的个位
那么分别取原数组arr
的个位
就是:
// const arr = [10, 200, 13, 12, 7, 88, 91, 24]
[0, 0, 3, 2, 7, 8, 1, 4]
3⃣️ 根据个位值的大小
放到对应的桶中
其实这一步也可以理解为根据个位值的大小
进行第一次排序
。
把个位数组
的每个元素
对应到桶的索引
,(是不是很熟悉,不熟悉请回忆上节的计数排序
),通过此步骤 目前桶
就变成了:
buckets = [[10, 200], [91], [12], [13], [24], [], [], [7], [88]]
4⃣️ 按照桶的索引
进行取值
通过此步 就已经完成了原数组
的个位排序
arr = [10,200,91,12,13,24,7,88]
5⃣️ 那接下来就是按照十位
进行排序
6⃣️ 取原数组的每个元素的十位
// arr = [10,200,91,12,13,24,7,88]
[1, 0, 9, 1, 1, 2, 0, 8]
// 如果某个元素没有该位, 就为0
// 比如7仅是个位数 那么它的十位就为0
7⃣️ 根据十位值的大小
放到对应的桶中
buckets = [[200,7], [10,12,13], [24], [], [], [], [], [], [88], [91]]
8⃣️ 按照桶的索引
进行取值
通过此步 就已经完成了原数组
的十位排序
arr = [200,7,10,12,13,24,88,91]
重复以上通过同样步骤来处理百位
9⃣️ 按照百位
进行排序
并取原数组的每个元素的百位
,最后就完成了基数排序
// 按照百位进行排序
[2,0,0,0,0,0,0,0,0,0]
// 取原数组的每个元素的百位
[[7,10,12,13,24,88,91],[],[200],...]
// 完成排序
[7,10,12,13,24,88,91,200]
基数排序
确实有点绕,主要是有了桶
这个东西。如果知道计数排序
,而且能看懂上方的步骤分解
,那么就很容易了。
JS实现基数排序
/*
* 基数排序
* @param{无序数组} arr
*/
function radix_sort(arr) {
// 取最大值 最大值的位数就是要循环遍历的次数
const max = Math.max(...arr);
// 定义一个桶
const buckets = Array.from({ length: 10 }, () => []);
// 定义当前要遍历的位数 个位 十位 百位...
let m = 1;
while (m < max) {
// m < 最大值
// 下方m要 m*=10 -> 每次遍历增加一位
// 保证遍历完所有可能的位数
// 放入桶
arr.forEach(number => {
// digit表示某位数的值
const digit = ~~((number % (m * 10)) / m);
// 把该位数的值放到桶buckets中
// 通过索引确定顺序 类比计数排序
buckets[digit].push(number);
});
// 从桶buckets中取值
// 完成此步后 就完成了一次位数排序
let ind = 0;
buckets.forEach(bucket => {
while (bucket.length > 0) {
// shift从头部取值
// 保证按照队列先入先出
arr[ind++] = bucket.shift();
}
});
// 每次最外层while循环后m要乘等10
// 也就是要判断下一位 比如当前是个位 下次就要判断十位
m *= 10;
}
}
上方还有一个点,就是计算当前位数值digit
。 为了代码的阅读性,没有在上方代码中说明。在此稍作说明:
const digit = ~~((number % (m * 10)) / m);
上段代码的意思就是计算出某个数字的某位数的值
比如说 一个数字521
,如何分别拿到1
, 2
, 5
呢?
首先 ~~
=== Math.floor()
也就是向下取整
m
首次是1
以后每次乘等10
取 个位1
:
a. 取模: 521 % 10 = 1
b. 除以m: 1 / 1 = 1
c. 向下取整: 1
取 十位2
:
a. 取模: 521 % 100 = 21
b. 除以m: 21 / 10 = 2.1
c. 向下取整: 2
取 百位5
:
a. 取模: 521 % 1000 = 521
b. 除以m: 521 / 100 = 5.21
c. 向下取整: 5
测试一下
const arr = [10, 200, 13, 12, 7, 88, 91, 24];
radix_sort(arr);
console.log(arr);
/*
[
7, 10, 12, 13,
24, 88, 91, 200
]
*/