排序算法之计数排序
这里是传送门⇒总结:关于排序算法
平均时间复杂度 | 最优时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 | |
---|---|---|---|---|---|
计数排序 | O(n+m) | O(n+m) | O(n+m) | O(n+max) | 稳定 |
优化后 | O(n+m) | O(n+m) | O(n+m) | O(n+m) | 稳定 |
注:m为待排序列取值范围大小,max为待排序列最大值
计数排序一种是“空间换时间”的排序,只能直接应用于非负整数的排序中(因为数组下标是非负整数,相当于用待排元素i作为索引,数组元素的值count[i]是待排序列中小于或者等于该下标i的元素数量)。
优势在对于元素取值范围m较小的待排序列的排序中,(优化后)时间复杂度是线性的Ο(n+m),快于任何键值比较排序算法。当然如果O(m)>O(nlogn)
,其效率反而不如基于键值比较的排序,若数据范围m比排序数据的数量n大很多,就不适合用计数排序。
虽然算法中有比较,但并非是键值间的比较,而是通过对元素值的计数和计数值的累加来确定的。
- 算法描述
- 对于待排序列中的每一个元素,确定序列中小于等于该元素的元素个数。通过这个信息将每个元素放在序列中正确的位置
- 当然,如果有多个元素具有相同的值时,我们不能将这些元素放在序列的同一个位置上
- 过程举例:初始化后count[0] = 1, count[1] = 2(此时意为序列中有1个0和2个1),则count[1] = count[0] + count[1] = 3,即序列中小于等于“1”的元素个数count[1]为3,则第一个值为“1”的元素应该放在序列的第count[1]个位置,即第3个位置array[2];然后让count[1]--为2,即现在待排序列中小于等于“1”的元素只有2个,那么第二个值为“1”的元素放在序列的第count[1]个位置,即第2个位置array[1]。为了维持其稳定性,让填充操作方向为序列末尾往前
- JS实现
// 此处没有改变array
function CountingSort(array) {
var len = array.length;
if (len <= 1) {
return array;
}
var max = array[0];
var count = [],
res = [];
for (var i = 1; i < len; i++) {
if (array[i] > max) {
max = array[i];
}
}
var countLen = max + 1;
for (var i = 0; i < countLen; i++) {
count[i] = 0;
}
for (var i = 0; i < len; i++) {
count[array[i]]++;
}
for (var i = 1; i < countLen; i++) {
count[i] += count[i - 1];
}
for (var i = len - 1; i >= 0; i--) {
res[count[array[i]] - 1] = array[i];
count[array[i]]--;
}
return res;
}
-
分析
- 设待排序列的长度为n,取值范围大小为m,最大值max
- 不管什么情况下,操作C(n) = O(3n + m) = O(n + m),即时间复杂度T(n) = O(n + m)
- 空间复杂度主要看count数组和res数组占用的内存,即
CountingSort
的S(n) = O(n + max) - 根据分析,计数排序的局限性在于如果取值范围m太大,复杂度就不够优秀和元素必须是非负整数(下面的优化可以兼容负整数,而在js中把count数组换成“对象”的话就可以兼容小数了)
- 由于在算法中重复元素在序列的位置是是先给靠近末尾的,填充方式是从末尾向前的,所以计数排序是稳定的
-
优化
- 当前的count数组的长度是由待排序列的最大值决定的,当待排序列中元素的取值范围为8-10时,count数组长度为11,count数组中0-7位置会闲置,造成浪费
- 优化为count数组长度为元素取值范围,偏移量为最小值。即当待排序列中元素的取值范围为8-10时,count数组长度为3(即10-8+1),映射偏移量为8,意为待排序列中小于等于8的元素个数存放在
count[8-偏移量8] = count[0]
中 - 这样做不仅节省了空间,而且利用偏移量,可以让计数排序兼容负数(还是只能整数)的情况,因为每个元素减去偏移量(序列最小值)之后必定为非负数,可作为count的下标
- 优化后的
CountingSortPlus
的S(n) = O(m + n)
-
优化版的JS实现
// 此处没有改变array
function CountingSortPlus(array) {
var len = array.length;
if (len <= 1) {
return array;
}
var min = max = array[0];
var count = [],
res = [];
for (var i = 1; i < len; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
var countLen = max - min + 1;
for (var i = 0; i < countLen; i++) {
count[i] = 0;
}
for (var i = 0; i < len; i++) {
count[array[i] - min]++;
}
for (var i = 1; i < countLen; i++) {
count[i] += count[i - 1];
}
for (var i = len - 1; i >= 0; i--) {
res[count[array[i] - min] - 1] = array[i];
count[array[i] - min]--;
}
return res;
}