计数排序
复杂度分析:
时间复杂度:O(n+k),即线性时间,k为给定数据中的最大值(k为整数范围),而其他的排序方法则最好的情况才是O(nlgn)。这是计数排序用空间换取时间的便利,也是计数排序只在特定情况才能使用的缺陷。
空间复杂度:总的空间复杂度为O(n+k),因为用到了另一个数组O(k)用于存储。
看完此文可以在力扣上轻松解决以下问题:
偶然间在力扣上看到一排序的题,便点了进去。912. 排序数组
题目意思很简单将数组升序排列,如果是为了快速解题的话,直接出结果,用sort()方法一步就行,但看了看难度,怎么是个中等题。于是在题库的归类里赫然看到“计数排序”这四个大字。那便来学习下计数排序
计数排序英文名:Counting Sort,顾名思义。注意这里有个中文读音很像的**“基数排序”**,两者是有所不同的,基数排序英文是:Radix sort,radix英文中意思是基数,而基数在数学中是集合论的一个概念,两个能够建立元素间一一对应的集合称为互相对等集合,基数排序这里暂且不提。
1.算法思想
首先计数排序从字面意思上就可以看出来,计算数字。这是一个稳定的非比较排序算法。一种基于特定范围内的键值的排序。通过计算具有不同键值的对象的数量,然后来得出每个对象在输出序列中的位置。有另一种不借助额外的存储空间的,只使用计数数组作为辅助存储,但是这种修改后的并不稳定。
In computer science, counting sort is an algorithm for sorting a collection of objects according to keys that are small positive integers; that is, it is an integer sorting algorithm. ——from wikipedia
2.排序思路
通过数组实现,将原数组中整数的值,作为索引,在另一个数组上对应的索引累加。也就是说,不通过比较,而是索引下标确定值的位置。然后遍历目标数组,依次输出就是排序后的结果。一般用于比较小的正整数,太大会造成目标数组空间的浪费。keys 可以理解为 index的键, 既然将值作为索引,那负数是不支持的。但是为了对负数也能够排序,在初始化数组空间的时候,可以通过加上一个固定的值(max-min+1),来实现对负数的取正。
3.实际应用
下面以力扣的912.排序数组为例:
示例:
Input:
[1, 3, 2, 8, 5, 1, 5, 1, 2, 7]
Output:
[1, 1, 1, 2, 2, 3, 5, 5, 7, 8]
首先需要初始化三个变量,max用于计算数据最大值,dp初始化为空数组用于输出,ans用于构建计数数组
var sortArray = function(nums) {
let max=Math.max(...nums)
let dp =[]
let ans=new Array(max+1).fill(0)
for(let d of nums){
ans[d]++
}
for(var k = 0; k <= max; k++){
while(ans[k]-- > 0){
dp.push(k);
}
}
return dp
};
这个时候就算是写完了,但是拿去测试的时候你会发现报错了。为什么呢?题目中给出的数据范围是:[-50000,50000],这时候你看完了前面的内容可能会想了,计数排序无法适用于负数的原因就是将值作为索引,那么索引一定不能是负的。出错的原因就在这!前面也提到了有解决的办法,其实很简单。只需要稍加修改.
添加变量min用于计算数据中的最小值,在数组初始化的时候,空间大小初始化为max-min+1,也就是间接的将负数对应的索引”取正“。这里在后面的几个循环中需要注意循环截止的条件!
var sortArray = function(nums) {
let max=Math.max(...nums)
let min=Math.min(...nums)
let dp =[]
let ans=new Array(max-min+1).fill(0)
for(let d of nums){
ans[d-min]++
}
for(var k = 0; k <= max-min+1; k++){
while(ans[k]-- > 0){
dp.push(k+min);
}
}
return dp
};
写到此,计数排序的代码已经完成了,有兴趣的可以试试原地排序。
特别需要提到的,在翻阅了很多资料中,并没有给出过Js的计数排序模板,Js的数组也不是实际意义上的数组,而是对象,还有很多方法可以用来优化以上代码,但思路始终是这个思路,这里暂且不提。
全文代码通过JavaScript实现
参考资料: