[路飞_计数排序]算法学习1.1

594 阅读4分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

什么是计数算法

不清楚---拿来看下官方的定义

当输入的元素是n 到 k 之间的整数时,它的运行时间是O(n + k)。计数排序不是比较排序,因此不被 Ω(nlogn)的下界限制。

由于用来计数的数组C 的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序算法中,能够更有效的排序数据范围很大的数组。

通俗地理解,例如有10个年龄不同的人,统计出有8个人的年龄比A小,那A的年龄就排在第9位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去1。

算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i
  3. 对所有的计数累加C 中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第i项,每放一个元素就将i减去1

分析

上面一大波文字解释了什么叫做计数算法

自己来提取分析下

  1. 计数排序和常用的通过交换位置的排序不一样
  2. 计数排序是通过获取数组中最大最小值,然后构建一最小值最大值 区间的数组 原数组的值必定是最大值最小值中间的 3.通过构建的数组记录(计数)原数组中出现的值的次数 4.最后通过数值出现的次数吧数值一次取出来

代码

第一步:获取最大最小值

var min = Math.min.apply(null, arr)
var max = Math.max.apply(null, arr)

第二步: 构建一个计数的数组

因为要记录最小到最大最小值, 所以长度钥匙 max - min + 1

var counter = Array.from({ length: max - min + 1 }).fill(0) // 给个初始的0 代表max min 之间值出现的次数

第三步:开始计数

// 遍历数组
for(let i = 0; i < arr.length;i++) {
    var counterIndex = arr[i] // 获取arr i项 的值
    counter[counterIndex]++ // i项的值 出现次数 +1
}

第四部: 取出计数

遍历counter计数的数组 把counter的下标一次取出给到新数组

var ret = []
forlet j = 0; j < counter.length; j++) {
    // 当前j 是arr的值 counter[j]记录了j出现的次数
    // 把j按次数取出
    while(counter[j] > 0) {
       ret.push(j)
       counter[j]--
    }
}
// ret 就是我们的值

结果-有问题 。。。 哪里出错了?

第一次做的时候是这个样子

我再思考了下

第二步骤里 计数数组的长度 是 max - min + 1

而我们第三步 计数的时候 值是有问题 我取的是arr[j]项的值去做counter 技术数组的下标 这样没法把 counter下标和 原数组的值对应起来, 所以我们要保证counter的下标的准确性(>=0),这样我们要把arr第i项 + min 这样就只可以对应counter下标 相应的取值的时候那么要把min 加上

最终的代码是

var counterSort = function (arr) {
  var min = Math.min.apply(null, arr)
  var max = Math.max.apply(null, arr)

  var counter = Array.from({ length: max - min + 1 }).fill(0)

  for (var i = 0; i < arr.length; i++) {
    var value = arr[i]
    var counterIndex = value - min

    counter[counterIndex]++
  }

  var ret = []

  for (var i = 0; i < counter.length; i++) {
    while (counter[i] > 0) {
      ret.push(j + min)
      counter[j]--
    }
  }

  return ret
}

结尾

因为对数组排序 需要对改变原数组 最后一步里我们对arr进行重新赋值

var index = 0

for (var i = 0; i < counter.length; i++) {
    while (counter[i] > 0) {
      arr[index++] = j + min
      counter[j]--
    }
}

return arr

总结

计数排序和普通排序不一样 不是通过对比交换位置,是一个全新的思路

另外我们也可以使用Map来计数

var counterSort = function (arr) {
  var min = Math.min.apply(null, arr)
  var max = Math.max.apply(null, arr)
  var counter = new Map()

  for (let i = min; i <= max; i++) {
    counter.set(i, 0)
  }

  for (var i = 0; i < arr.length; i++) {
    var value = arr[i]
    var counterIndex = value
    counter[counterIndex]++
    counter.set(counterIndex, counter.get(counterIndex) + 1)
  }

  var index = 0

  for (j of counter) {
    while (j[1] > 0) {
      arr[index++] = j[0]
      j[1]--
    }
  }

  return arr
}

也可以稍微改动一下写法

var counterSort = function (arr) {
  const max = Math.max.apply(null, arr)
  const min = Math.min.apply(null, arr)

  const counter = Array.from({ length: max - min + 1 }).fill(0)

  for (let i = 0; i < arr.length; i++) {
    const count = arr[i] // 这里就去当前值做
    if (counter[count] === undefined) counter[count] = 0 // 然后塞入一个值
    counter[count]++ // +1
  }

  let index = 0
   // 这里就可以从min开始遍历
  for (let i = min; i < counter.length; i++) {
    while (counter[i] > 0) {
      arr[index++] = i
      counter[i]--
    }
  }

  return arr
}