「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
哈,大家早上好呀,大家可以看到本系列现在已经加上路飞前缀,不是因为我是一个海贼迷,这是大佬 全栈然叔 发起的一个坚持学习系列,为了积极响应,我会在今后一段时间的文章中都加上路飞字样,和掘金的小伙伴们一起进步,那还不飞龙升天,哈哈。
今天我们来学习最后两个经典的非比较型排序算法。
- 基数排序(radixSort)
- 桶排序(bucketSort)
本篇文章中的代码在这里.
基数排序(radixSort)
基数排序是也是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于基数排序是基于计数排序,所以也是一种稳定排序。
时间复杂度
实现基数排序的方式有两种
方法一:时间复杂度O(d * (n + k)) d是最大值的位数,k是进制。
方法二: 时间复杂度为O(dn)
算法步骤
- 找出待排数组中最大的值
- 从个位上的数字0~9进行计数(0-9)排序
- 从十位、百位、... 直到最大值位数进行 2步骤
图解过程

代码实现
方法一:直接利用之前的计数排序
function radixSort(arr) {
let max = arr[0]
for (let i = 0; i < arr.length; i++) { // 找到最大值
if (arr[i] > max) max = arr[i]
}
for (let i = 1; i <= max; i *= 10) {
countingSort(arr, i) // 从小到大对基数进行排序
}
function countingSort(arr, divider) {
const counts = new Array(10).fill(0)
for (let i = 0; i < arr.length; i++) {
counts[Math.floor(arr[i] / divider) % 10]++ // 保存对应位数的基数个数
}
for (let j = 1; j < counts.length; j++) {
counts[j] += counts[j - 1] // 把整个保存的个数连续起来,以便后面推算出对应值在有序数组中的位置,这一步不懂的请一步到白的算法学习笔记03中的计数数组的改进版进行学习
}
const res = []
for (let k = arr.length - 1; k >= 0; k--) {
res[--counts[Math.floor(arr[k] / divider) % 10]] = arr[k] // 根据关系arr元素在res中的对应关系进行赋值
}
for (let i = 0; i < res.length; i++) {
arr[i] = res[i] // 把有序数列复制给arr
}
}
}
// const radixArr = getRandomArr();
const radixArr = [999999, 888888, 7, 9, 323, 454, 33333, 1, 2]
console.log('before radix sorting ===>', radixArr)
radixSort(radixArr)
console.log('after radix sorting ===>', radixArr)
方法二:利用二维数组存放相同基数的值
function radixSort2(arr) {
let max = arr[0]
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) max = arr[i]
}
const counter = [] //存放排好基数位置的二维数组
for (let i = 1; i < max; i *= 10) {
let index = 0
// 进行基数位排序
for (let j = 0; j < arr.length; j++) {
const radix = Math.floor(arr[j] / i) % 10
if (!counter[radix]) counter[radix] = []
counter[radix].push(arr[j])
}
// 对arr进行重新赋值
for (let k = 0; k < counter.length; k++) {
while (counter[k] && counter[k].length > 0) {
arr[index++] = counter[k].shift()
}
}
}
}
const radixArr2 = [999999, 888888, 7, 9, 323, 454, 33333, 1, 2]
console.log('before radix2 sorting ===>', radixArr2)
radixSort2(radixArr2)
console.log('after radix2 sorting ===>', radixArr2)
桶排序(Bucket Sort)
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
- 在额外空间充足的情况下,尽量增大桶的数量
- 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中(数据均匀分配到每个桶中最快,分配到同一个桶中最慢)
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
算法步骤
- 创建一定数量的桶(比如用数组,链表作为桶)
- 按照一定的规则(不同类型的数据,规则不同),将序列中的元素均匀分配到对应的桶
- 分别对每个桶进行单独排序
- 将所有非空桶的元素合并成有序序列
图解过程

代码实现
由于桶排序比较灵活,在这里我们只举个例子
function bucketSort(arr, size) {
if (arr.length === 0) {
return arr
}
let minValue = arr[0]
let maxValue = arr[0]
for (let i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i] // 输入数据的最小值
}
if (arr[i] > maxValue) {
maxValue = arr[i] // 输入数据的最大值
}
}
//桶的初始化
let DEFAULT_BUCKET_SIZE = 5 // 设置桶的默认数量为5
const bucketSize = size || DEFAULT_BUCKET_SIZE
let bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1
let buckets = new Array(bucketCount)
for (let i = 0; i < buckets.length; i++) {
buckets[i] = []
}
//利用映射函数将数据分配到各个桶中
for (let i = 0; i < arr.length; i++) {
buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i])
}
arr.length = 0
for (let i = 0; i < buckets.length; i++) {
insertionSort(buckets[i]) // 对每个桶进行排序,这里使用了插入排序
for (let j = 0; j < buckets[i].length; j++) {
arr.push(buckets[i][j])
}
}
return arr
}
const bucketArr = getRandomArr()
console.log('before bucket sorting ===>', bucketArr)
bucketSort(bucketArr)
console.log('after bucket sorting ===>', bucketArr)
好了,到此我们一共用了四篇文章,学习了常用的10种经典排序方法,小伙伴们都学会了吗?没有学会的话文章多读几遍,有问题欢迎来留言区留言。
如果觉得还不错,可以点赞,或者关注我,我会持续更新前端和算法学习的文章。