排序算法之计数排序

这里是传送门⇒总结:关于排序算法



平均时间复杂度 最优时间复杂度 最差时间复杂度 空间复杂度 稳定性
计数排序 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;
}
posted @ 2021-02-23 22:27  有机物与鱼  阅读(102)  评论(0编辑  收藏  举报