「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」
计数排序
引言
数据结构的 排序算法 有10种,总结在封面。
上面几篇文章已经 讲解了右边的 7 种 排序算法,总结一下,它们都是 比较排序。都是某两个数进行比较大小,这种 比较的思想
接下来 讲解左边的 3 种 排序算法。它们都是 非 比较排序,是一种 桶 的思想。
桶 就是我们平时装的东西,也就是不同的 这个 桶 来进行收集不同的东西。
桶 类似于:大米桶装大米,小米桶装小米,杂粮桶装杂粮,红枣桶装红枣,枸杞桶装枸杞 等等...
当然,这不是绝对的。也可以是某个桶 装着两种,甚至三种其他的桶,类似于:
- 干粮桶 装 大米桶,小米桶...
- 水桶 装 水桶,可乐桶,雪碧桶...
所以不同的情况,用到不同的桶,但是思想是不变的,就是基于 桶 能够统计的思想。
介绍
计数排序 它是基于 桶 的思想,来进行的排序算法。
-
给定一组数据,通过找到该数据中,最大值 和 最小值
-
循环遍历该数组中的每一项值,将其放入 对应的桶中,桶的内部进行++操作,默认每个桶的内部值为0
-
反向填充,循环将每个桶的里面的值 从后往前 进行放入到 结果 数组中即可。
动图演示
步骤详解
给定数据为 const arr = [15, 5, 25, 20, 30, 10, 35, 15]
-
找到最大值和最小值
首先默认 最大值和最小值为 数组的第一项
遍历数组的每一项,然后进行比较,获取到最大值 和 最小值。
代码为:
let min = arr[0] let max = arr[0] for (let i = 0; i < arrLength; i++) { min = min <= arr[i] ? min : arr[i] max = max >= arr[i] ? max : arr[i] }-
min: 5
-
max: 35
-
-
获得每个 桶
也是通过遍历数组中的每一项值,将这个值作为下标放入到桶中,如果没有这个下标,则创建该下标,并且默认值为1,否则进行++操作。
const Count = [] // 桶 数组 for (let i = 0; i < arrLength; i++) { Count[arr[i]] = Count[arr[i]] ? Count[arr[i]] + 1 : 1 }-
Count:
[ <5 empty items>, 1, <4 empty items>, 1, <4 empty items>, 2, <4 empty items>, 1, <4 empty items>, 1, <4 empty items>, 1, <4 empty items>, 1 ]Count 里面代表的意思就是:
下标 出现次数 5 1 10 1 15 2 20 1 25 1 30 1 35 1
-
-
所在位置
这一块有点难理解,就是 Count 本应该是 下标值 对应 出现次数
通过下面代码,Count 就变成 下标值 对应 排序后的 所在位置
// temp 用于存储每次更新后的排序值 let temp = min for (let j = min; j < max; j++) { if (Count[j + 1]) { Count[j + 1] = Count[temp] + Count[j + 1] temp = j + 1 } }[ <5 empty items>, 1, <4 empty items>, 2, <4 empty items>, 4, <4 empty items>, 5, <4 empty items>, 6, <4 empty items>, 7, <4 empty items>, 8 ]下标 所在位置 5 1 10 2 15 4 20 5 25 6 30 7 35 8 -
反向填充
这一块总结有点难,各位可以看下面描述 和 代码来理解。
可以看到上一个步骤得到的 Count 所对应的 所在位置 的值,不是最终 Result 数组想要的结果,因为 所在位置 不是从下标 0 开始。
所以注意,在用 所在位置 这个值的时候,要进行 减一 操作。
还要注意,我们将 arr[k] 的值放到 Result 后,要进行
Count[CountIndex]--这个操作。for (let k = arrLength - 1; k >= 0; k--) { // 对数组 arr 从后往前 依次遍历,得到的值,是当前需要放置的值,也是 Count 的下标值 const CountIndex = arr[k] // 获取到 Count[CountIndex] 的值,需要减一,才是 Result 的下标的值 const ResultIndex = Count[CountIndex] - 1 // 最后 将当前需要放置的值===arr[k] 和 对应在 Result 数组的下标===ResultIndex 填充到 Result 中 Result[ResultIndex] = arr[k] // 最后将 Count 数组中所对应的值 进行 -- 操作 Count[CountIndex]-- }
完整代码
const countSort = (arr) => {
const arrLength = arr.length
const Result = [] // 存放最终排序结果 数组
const Count = [] // 桶 数组
let min = arr[0]
let max = arr[0]
// 将arr数置入Count数组中,进行统计出现次数,并查找最大最小值(后续作为下标使用)
for (let i = 0; i < arrLength; i++) {
// 注意这一块
Count[arr[i]] = Count[arr[i]] ? Count[arr[i]] + 1 : 1
min = min <= arr[i] ? min : arr[i]
max = max >= arr[i] ? max : arr[i]
}
// temp 用于存储每次更新后的排序值
let temp = min
for (let j = min; j < max; j++) {
if (Count[j + 1]) {
Count[j + 1] = Count[temp] + Count[j + 1]
temp = j + 1
}
}
// 反向填充
for (let k = arrLength - 1; k >= 0; k--) {
// 对数组 arr 从后往前 依次遍历,得到的值,是当前需要放置的值,也是 Count 的下标值
const CountIndex = arr[k]
// 获取到 Count[CountIndex] 的值,需要减一,才是 Result 的下标的值
const ResultIndex = Count[CountIndex] - 1
// 最后 将当前需要放置的值===arr[k] 和 对应在 Result 数组的下标===ResultIndex 填充到 Result 中
Result[ResultIndex] = arr[k]
// 最后将 Count 数组中所对应的值 进行 -- 操作
Count[CountIndex]--
}
}
总结
计数排序 是 非比较排序,是一种稳定的排序方式。
但是缺点也很明显:
-
因为数组下标的原因,所以只能对数组里 全是整数 的数据,而不能存在小数。这让 计数排序 有很大的局限性。
-
开辟空间较大,牺牲空间来得到排序速度,很浪费。