JS 计数排序解析

446 阅读4分钟

将给定数组使用计数排序,从小到大排序,输出排序后的数组

计数排序: 计数排序是一个非基于比较的排序算法。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)

技术排序非常类似于桶排序,其主要实现是: 遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值。遍历完成后,遍历count数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到result数组中去,每处理一次,count中的该元素值减1,直到该元素值不大于0,依次处理count中剩下的元素

分析:

假设有给定数组 s = [3,2,6,4,5,3,7]

数组中每个元素可以看作一个球,数组中最大的元素是 7,那么就设置7个桶,(如果最大为100,那么就要设置100个桶,这是计数排序的缺点,牺牲空间,所以计数排序是牺牲空间换取时间的排序算法)

image.png

7个桶在代码中可以被抽象为元素数量为桶数量的数组,即长度为7的数组,桶内放置球的个数为每个数组的元素值,刚开始都是0,那么最初的模型应当是: counstList = [0,0,0,0,0,0,0]

将球遍历,根据球上的数字放入对应下标的桶中,例如: s[0] = 3 ,那么第一个球放入到第三个桶中。s[1] = 2,放入到第二个桶中

做到这里,可能会发现这样去考虑会有一些问题:

  • 假设 s = [91,90,100] ,最大值是100,那么我们需要放置 100 个桶
  • 假设 s 中包含负数, 例如 s[0] = -1, 那么他应该放到哪个桶??第 -1 个桶??

再深入一些思考会发现,假设 s = [91, 90, 100], 那么实际上我们紧紧是对 > 90 且 < 100 的数进行排序,90之前的桶根本用不上,也不会有一个球放进去(因为没有数据是 < 90 的)。所以最好的办法,第一个桶对应的应该是数组中最小的数,即第一个桶放入的数据应该是90,那么由此可以得出:

第一个桶 应当放入 90

第二个桶 应当放入 91

第三个桶 应当放入 92

第N个桶 应当放入 90 + N - 1 【即 min(最小值) + N - 1】

得到: 应当放入第 N 个桶的数据 = min + N - 1

由此可得: N = 放入的数据 - min + 1

这样一来负数的问题也解决了, 假设 s = [-1, 3,5] ,会有 s[0] = -1, 那么他应当放入 第【 -1 - (-1) + 1 】个,即 第 1 个桶中, s[1] = 3, 放入第 【 3 - (-1) + 1 】 即第 5个桶中 。。以此类推

总结:

假设数组中最小值 为 min, 最大值为 max, 遍历的数组单个元素值为 val

  • 桶的长度应为 max - min + 1
  • 球上的数字(val)应放入第 val - min + 1 个桶中

由此,可根据待排数组,得出下图:

image.png

过程:

  1. min = 2 , max = 7 设置 max - min + 1 = 7 - 2 + 1 = 6 个桶

2.根据 N = 放入的数据 - min + 1

  • s[0] = 3, N = 3 - 2 + 1, 放入第2个桶

  • s[1] = 2, N = 2 - 2 + 1, 放入第1个桶

  • s[2] = 6, N = 6 - 2 + 1, 放入第5个桶

  • s[3] = 4, N = 4 - 2 + 1, 放入第3个桶

  • s[4] = 5, N = 5 - 2 + 1, 放入第4个桶

  • s[5] = 3, N = 3 - 2 + 1, 放入第2个桶

  • s[6] = 7, N = 7 - 2 + 1, 放入第6个桶

最终得到:

image.png

最后将桶中的数据依次取出,得到的数组就是排序后的数组了

ScreenRecorderProject4.gif

代码实现:

const f = () => {
  let min = s[0]
  let max = s[0]
  // 找出最小, 最大值
  for (let i = 1; i < s.length; i++) {
    let num = s[i]
    min = min > num ? num : min
    max = max < num ? num : max
  }
  console.log({ min, max })
  // 创建 max - min + 1 长度的数组
  const countList = new Array(max - min + 1).fill(0)

  s.forEach(num => {
    countList[num - min] = countList[num - min] + 1
  })

  console.log(countList)
  const res = []
  countList.forEach((val, index) => {
    while (val > 0) {
      res.push(index + min)
      val--
    }
  })
  console.log(res)
}


const s = [3, 2, 6, 4, 5, 3, 7]
f(s)

输出结果: [ 2, 3, 3, 4, 5, 6, 7 ]