排序算法-计数排序

170 阅读4分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

计数排序

引言

数据结构的 排序算法 有10种,总结在封面。

上面几篇文章已经 讲解了右边的 7 种 排序算法,总结一下,它们都是 比较排序。都是某两个数进行比较大小,这种 比较的思想

接下来 讲解左边的 3 种 排序算法。它们都是 非 比较排序,是一种 的思想。

就是我们平时装的东西,也就是不同的 这个 来进行收集不同的东西。

类似于:大米桶装大米,小米桶装小米,杂粮桶装杂粮,红枣桶装红枣,枸杞桶装枸杞 等等...

当然,这不是绝对的。也可以是某个桶 装着两种,甚至三种其他的桶,类似于:

  • 干粮桶 装 大米桶,小米桶...
  • 水桶 装 水桶,可乐桶,雪碧桶...

所以不同的情况,用到不同的桶,但是思想是不变的,就是基于 桶 能够统计的思想

介绍

计数排序 它是基于 的思想,来进行的排序算法。

  • 给定一组数据,通过找到该数据中,最大值 和 最小值

  • 循环遍历该数组中的每一项值,将其放入 对应的桶中,桶的内部进行++操作,默认每个桶的内部值为0

  • 反向填充,循环将每个桶的里面的值 从后往前 进行放入到 结果 数组中即可。

动图演示

2a83cc7731e103d198795e64b07f4f37.gif

步骤详解

给定数据为 const arr = [15, 5, 25, 20, 30, 10, 35, 15]

  • 找到最大值和最小值

    首先默认 最大值和最小值为 数组的第一项

    遍历数组的每一项,然后进行比较,获取到最大值 和 最小值。

    代码为:

        let min = arr[0]
        let max = arr[0]
        
        for (let i = 0; i < arrLength; i++) {
            min = min <= arr[i] ? min : arr[i]
            max = max >= arr[i] ? max : arr[i]
          }
    
    • min: 5

    • max: 35

  • 获得每个 桶

    也是通过遍历数组中的每一项值,将这个值作为下标放入到桶中,如果没有这个下标,则创建该下标,并且默认值为1,否则进行++操作。

      const Count = []  // 桶 数组
      
       for (let i = 0; i < arrLength; i++) {
           Count[arr[i]] = Count[arr[i]] ? Count[arr[i]] + 1 : 1
       }
    
    • Count:

          [
            <5 empty items>, 1,
            <4 empty items>, 1,
            <4 empty items>, 2,
            <4 empty items>, 1,
            <4 empty items>, 1,
            <4 empty items>, 1,
            <4 empty items>, 1
          ]
      

      Count 里面代表的意思就是:

      下标出现次数
      51
      101
      152
      201
      251
      301
      351
  • 所在位置

    这一块有点难理解,就是 Count 本应该是 下标值 对应 出现次数

    通过下面代码,Count 就变成 下标值 对应 排序后的 所在位置

      // temp 用于存储每次更新后的排序值
      let temp = min
      for (let j = min; j < max; j++) {
        if (Count[j + 1]) {
          Count[j + 1] = Count[temp] + Count[j + 1]
          temp = j + 1
        }
      }
    
    [
      <5 empty items>, 1,
      <4 empty items>, 2,
      <4 empty items>, 4,
      <4 empty items>, 5,
      <4 empty items>, 6,
      <4 empty items>, 7,
      <4 empty items>, 8
    ]
    
    下标所在位置
    51
    102
    154
    205
    256
    307
    358
  • 反向填充

    这一块总结有点难,各位可以看下面描述 和 代码来理解。

    可以看到上一个步骤得到的 Count 所对应的 所在位置 的值,不是最终 Result 数组想要的结果,因为 所在位置 不是从下标 0 开始。

    所以注意,在用 所在位置 这个值的时候,要进行 减一 操作。

    还要注意,我们将 arr[k] 的值放到 Result 后,要进行 Count[CountIndex]-- 这个操作。

        for (let k = arrLength - 1; k >= 0; k--) {
            // 对数组 arr 从后往前 依次遍历,得到的值,是当前需要放置的值,也是 Count 的下标值
            const CountIndex = arr[k]
    
            // 获取到 Count[CountIndex] 的值,需要减一,才是 Result 的下标的值
            const ResultIndex = Count[CountIndex] - 1
    
            // 最后  将当前需要放置的值===arr[k]  和 对应在 Result 数组的下标===ResultIndex  填充到 Result 中
            Result[ResultIndex] = arr[k]
    
            // 最后将 Count 数组中所对应的值 进行 -- 操作
            Count[CountIndex]--
        }
    

完整代码

  const countSort = (arr) => {
  const arrLength = arr.length

  const Result = [] // 存放最终排序结果 数组
  const Count = [] // 桶 数组
  let min = arr[0]
  let max = arr[0]

  // 将arr数置入Count数组中,进行统计出现次数,并查找最大最小值(后续作为下标使用)
  for (let i = 0; i < arrLength; i++) {
    // 注意这一块
    Count[arr[i]] = Count[arr[i]] ? Count[arr[i]] + 1 : 1

    min = min <= arr[i] ? min : arr[i]
    max = max >= arr[i] ? max : arr[i]
  }

  // temp 用于存储每次更新后的排序值
  let temp = min
  for (let j = min; j < max; j++) {
    if (Count[j + 1]) {
      Count[j + 1] = Count[temp] + Count[j + 1]
      temp = j + 1
    }
  }
  
  // 反向填充
  for (let k = arrLength - 1; k >= 0; k--) {
    // 对数组 arr 从后往前 依次遍历,得到的值,是当前需要放置的值,也是 Count 的下标值
    const CountIndex = arr[k]

    // 获取到 Count[CountIndex] 的值,需要减一,才是 Result 的下标的值
    const ResultIndex = Count[CountIndex] - 1

    // 最后  将当前需要放置的值===arr[k]  和 对应在 Result 数组的下标===ResultIndex  填充到 Result 中
    Result[ResultIndex] = arr[k]

    // 最后将 Count 数组中所对应的值 进行 -- 操作
    Count[CountIndex]--
  }
}

总结

计数排序 是 非比较排序,是一种稳定的排序方式。

但是缺点也很明显:

  • 因为数组下标的原因,所以只能对数组里 全是整数 的数据,而不能存在小数。这让 计数排序 有很大的局限性。

  • 开辟空间较大,牺牲空间来得到排序速度,很浪费。