详解排序算法(四)之计数排序
计数排序
计数排序不是一个比较排序算法,该算法于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 使用场景
- 适用场景:元素为整数且排列密集
- 不适用场景:元素存在小数或排列不密集

浙公网安备 33010602011771号