详解排序算法(四)之计数排序
计数排序
计数排序不是一个比较排序算法,该算法于1954年由 Harold H. Seward提出。
01 算法步骤
-
找到数列的最大值,计为
max
-
新建一个长度为
max + 1
的数组,计为bucket
-
遍历数列,在
bucket
中找到值对应的下标,若对应下标里已有值,值加 1,若无值,将值设置为 1,例如值为5,则找到 bucket 中下标为 5 的位置,即
bucket [5]
,若bucket [5]
不存在,则设置bucket [5]
为1,即bucket [5] = 1
,若bucket [5]
存在,则值加 1,即bucket [5]++
-
遍历
bucket
,对于存在值的元素,设其值为count
,下标为index
,往结果数组中插入count
个元素,元素的值为index
例如bucket [5] = 3
,则count = 3
,index = 5
,往结果数组中插入 3 个 5。
02 示例
我们取 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2 来示范
-
找到最大值为 9
-
新建一个长度为 10 的数组,[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-
找到下标 2,将其值置为 1,得 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
找到下标 3,将其值置为 1,得 [0, 0, 1, 1, 0, 0, 0, 0, 0, 0]
找到下标 8,将其值置为 1,得 [0, 0, 1, 1, 0, 0, 0, 0, 1, 0]
找到下标 7,将其值置为 1,得 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0]
找到下标 1,将其值置为 1,得 [0, 1, 1, 1, 0, 0, 0, 1, 1, 0]
找到下标 2,将其内值加 1,得 [0, 1, 2, 1, 0, 0, 0, 1, 1, 0]
找到下标 2,将其内值加 1,得 [0, 1, 3, 1, 0, 0, 0, 1, 1, 0]
找到下标 2,将其内值加 1,得 [0, 1, 4, 1, 0, 0, 0, 1, 1, 0]
找到下标 7,将其内值加 1,得 [0, 1, 4, 1, 0, 0, 0, 2, 1, 0]
找到下标 3,将其内值为 1,得 [0, 1, 4, 2, 0, 0, 0, 2, 1, 0]
找到下标 9,将其值置为 1,得 [0, 1, 4, 2, 0, 0, 0, 2, 1, 1]
找到下标 8,将其内值加 1,得 [0, 1, 4, 2, 0, 0, 0, 2, 2, 1]
找到下标 2,将其内值加 1,得 [0, 1, 5, 2, 0, 0, 0, 2, 2, 1]
找到下标 1,将其内值加 1,得 [0, 2, 5, 2, 0, 0, 0, 2, 2, 1]
找到下标 4,将其值置为 1,得 [0, 2, 5, 2, 1, 0, 0, 2, 2, 1]
找到下标 2,将其内值加 1,得 [0, 2, 6, 2, 1, 0, 0, 2, 2, 1]
找到下标 4,将其内值加 1,得 [0, 2, 6, 2, 2, 0, 0, 2, 2, 1]
找到下标 6,将其值置为 1,得 [0, 2, 6, 2, 2, 0, 1, 2, 2, 1]
找到下标 9,将其内值加 1,得 [0, 2, 6, 2, 2, 0, 1, 2, 2, 2]
找到下标 2,将其内值加 1,得 [0, 2, 7, 2, 2, 0, 1, 2, 2, 2]
-
遍历 [0, 2, 7, 2, 2, 0, 1, 2, 2, 2]
插入 2 个 1,得 [1, 1]
插入 7 个 2,得 [1, 1, 2, 2, 2, 2, 2, 2, 2]
插入 2 个 3,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3]
插入 2 个 4,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4]
插入 1 个 6,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6]
插入 2 个 7,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7]
插入 2 个 8,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8]
插入 2 个 9,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]
排序完成。
03 动态图
04 javascript代码
function countSort(arr) {
const max = getMax(arr), // 数组中最大值
bucketLen = max + 1
bucket = new Array(bucketLen)
let sortedIndex = 0
for (let i = 0; i < arr.length; i++) {
if (!bucket[arr[i]]) {
bucket[arr[i]] = 0
}
bucket[arr[i]]++
}
for (let j = 0; j < bucketLen; j++) {
while(bucket[j] > 0) {
arr[sortedIndex++] = j
bucket[j]--
}
}
return arr
}
// 获取数组最大值
function getMax (arr) {
let max = arr[0]
arr.forEach(e => {
max = e > max ? e : max
})
return max
}
优化版 —— 基于最小值的计数排序
经过上面的学习,我们已经掌握了计数排序的过程。但上面的算法有问题吗?当然有!!!
我们不妨举以下两个例子
- 取数列 101, 99, 99, 100 ,按照上面的算法,所需数组长度为 102,但实际上,我们只用到了 3 个位置,空间大大浪费。
- 假设数列中存在负数,如 -1, 10, 11,按照以上算法,数组长度为 12,可适配的值的范围为 0 - 11,但 -1 显然不在这个范围内
基于以上问题,优化版 —— 基于最小值的计数排序 便应运而生。
01 算法步骤
-
找到数列的最小值、最大值,分别计为
min
、max
-
新建一个长度为
max + 1
的数组,计为bucket
-
遍历数列,在
bucket
中找到值 - min
对应的下标,若对应下标里已有值,值加 1,若无值,将值设置为 1,例如值为5,
min = 2
,则找到 bucket 中下标为 3 的位置,即bucket [3]
,若bucket [3]
不存在,则设置bucket [3]
为 1,即bucket [3] = 1
,若bucket [3]
存在,则值加 1,即bucket [3]++
-
遍历
bucket
,对于存在值的元素,设其值为count
,下标为index
,往结果数组中插入count
个元素,元素的值为min + index
例如bucket [3] = 3
,min = 2
,则count = 3
,index = 3
,min + index = 5
,往结果数组中插入 3 个 5。
02 示例
我们取 101, 99, 99, 100 来示范
-
找到最小值为 99,最大值 101,101 - 99 = 2
-
新建一个长度为 3 的数组,[0, 0, 0]
-
找到下标 101 - 99 = 2,将其值置为 1,得 [0, 0, 1]
找到下标 99 - 99 = 0,将其值置为 1,得 [1, 0, 1]
找到下标 99 - 99 = 0,将其内值加 1,得 [2, 0, 1]
找到下标 100 - 99 = 1,将其值置为 1,得 [2, 1, 1]
可以看到,优化后的算法用到的数组长度为 3,而优化前的数组长度为 102
-
遍历 [2, 1, 1]
插入 2 个 99 + 0 = 99,得 [99, 99]
插入 1 个 99 + 1 = 100,得 [99, 99, 100]
插入 1 个 99 + 2 = 101,得 [99, 99, 100, 101]
javascript代码
function countSort(arr) {
const max = getMax(arr), // 数组中最大值
min = getMin(arr), // 数组中最小值,以其作为基数
bucketLen = max - min + 1
bucket = new Array(bucketLen)
let sortedIndex = 0
for (let i = 0; i < arr.length; i++) {
const index = arr[i] - min // 插入位置的下标
if (!bucket[index]) {
bucket[index] = 0
}
bucket[index]++
}
for (let j = 0; j < bucketLen; j++) {
while(bucket[j] > 0) {
arr[sortedIndex++] = min + j
bucket[j]--
}
}
return arr
}
// 获取数组最大值
function getMax (arr) {
let max = arr[0]
arr.forEach(e => {
max = e > max ? e : max
})
return max
}
// 获取数组最小值
function getMin (arr) {
let min = arr[0]
arr.forEach(e => {
min = e < min ? e : min
})
return min
}
总结
01 复杂度及稳定性
排序算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
计数排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 稳定 |
其中 k 为数列不重复元素的个数
02 使用场景
- 适用场景:元素为整数且排列密集
- 不适用场景:元素存在小数或排列不密集
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构