详解排序算法(四)之计数排序

计数排序

计数排序不是一个比较排序算法,该算法于1954年由 Harold H. Seward提出。

01 算法步骤

  1. 找到数列的最大值,计为 max

  2. 新建一个长度为 max + 1 的数组,计为 bucket

  3. 遍历数列,在 bucket 中找到值对应的下标,若对应下标里已有值,值加 1,若无值,将值设置为 1,例如

    值为5,则找到 bucket 中下标为 5 的位置,即 bucket [5],若 bucket [5] 不存在,则设置 bucket [5] 为1,即 bucket [5] = 1 ,若 bucket [5] 存在,则值加 1,即 bucket [5]++

  4. 遍历 bucket,对于存在值的元素,设其值为 count,下标为 index,往结果数组中插入 count 个元素,元素的值为 index例如

    bucket [5] = 3,则 count = 3index = 5,往结果数组中插入 3 个 5。

02 示例

我们取 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2 来示范

  1. 找到最大值为 9

  2. 新建一个长度为 10 的数组,[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

  3. 找到下标 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]

  4. 遍历 [0, 2, 7, 2, 2, 0, 1, 2, 2, 2]

    插入 21,得 [1, 1]

    插入 72,得 [1, 1, 2, 2, 2, 2, 2, 2, 2]

    插入 23,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3]

    插入 24,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4]

    插入 16,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6]

    插入 27,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7]

    插入 28,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8]

    插入 29,得 [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]

    排序完成。

03 动态图

img

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 算法步骤

  1. 找到数列的最小值、最大值,分别计为 minmax

  2. 新建一个长度为 max + 1 的数组,计为 bucket

  3. 遍历数列,在 bucket 中找到 值 - min 对应的下标,若对应下标里已有值,值加 1,若无值,将值设置为 1,例如

    值为5,min = 2,则找到 bucket 中下标为 3 的位置,即 bucket [3],若 bucket [3] 不存在,则设置 bucket [3] 为 1,即 bucket [3] = 1 ,若 bucket [3] 存在,则值加 1,即 bucket [3]++

  4. 遍历 bucket,对于存在值的元素,设其值为 count,下标为 index,往结果数组中插入 count 个元素,元素的值为 min + index例如

    bucket [3] = 3min = 2,则 count = 3index = 3min + index = 5,往结果数组中插入 3 个 5。

02 示例

我们取 101, 99, 99, 100 来示范

  1. 找到最小值为 99,最大值 101,101 - 99 = 2

  2. 新建一个长度为 3 的数组,[0, 0, 0]

  3. 找到下标 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

  4. 遍历 [2, 1, 1]

    插入 299 + 0 = 99,得 [99, 99]

    插入 199 + 1 = 100,得 [99, 99, 100]

    插入 199 + 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 使用场景

  • 适用场景:元素为整数且排列密集
  • 不适用场景:元素存在小数或排列不密集
posted @   Programing_Monkey  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示