「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
排序方式千千万,到底哪个才是最牛逼的?
我想我今天终于找到了答案:没有最牛逼的算法, 只有最适合的场景。
今天给大家带来的计数排序,时间复杂度上是O(n + k); 其中k代表着数组最大值 - 最小值的差;
这意味着计数排序适合什么时候使用呢?当然是数值越接近的数组越适合用计数排序。
我们直接来看个例子, [1, 2, 2, 2, 3, 3, 5, 4, 2, 3, 4, 3]
像咱们拿到这个数组,想对它进行排序的时候,想要快,第一是想到能不能统计每个元素出现的次数,然后对去重后的数组进行排序。 如: map = { 1: 1, 2: 4, 3: 4, 4: 2, 5: 1}, 然后对键值 1-5进行排序就只需要比对五个数字了。但是很遗憾,查阅了MDN中对Object和Map的解释中发现键的顺序并不能自动帮我们排好。而是会以插入的顺序记录。这意味着我们要对键值再做一轮排序,太复杂了不看了不看了。
那有没有其他方法呢?突然联想到数组的索引,是已经排好序的了。而且是不会改变的,这就是计数排序的原理。
我们可以通过一个for循环。把每个元素出现的次数记录在它对应的索引上,但是一开始我们并不知道应该要创建一个多大的数组, 于是我们又继续去找到数组的最大值,创建数组,记录每个元素出现的次数,相关代码如下:
// for (let i = 0; i < arr.length; i++) {
// if (arr[i] > maxValue) {
// maxValue = arr[i];
// }
// }
let maxValue = Math.max(...arr); // es6的骚操作, 跟上面的代码意思一样
// 因为数组是从0开始的,我们要记录第n个元素需要创建长度为n+1的数组
let array = new Array(maxValue + 1).fill(0);
// 记录出现的次数
for (let i = 0; i < arr.length; i++) {
array[arr[i]] = (array[arr[i]] || 0) + 1;
}
然后遍历一下我们创建的这个数组去取值即完成了排序
let result = [];
for (let i = 0; i < array.length; i++) {
let cur = array[i];
while (cur) {
result.push(i);
cur--;
}
}
相应的流程图是这样子的
这就是计数排序的原理,懂了之后你会发现: 就这?
实际上每个算法本身并不难,难的是你怎么把一个大的问题通过分治的思想转换成若干个小问题。然后在合适的场景用上合适的算法,像计数排序如果你拿到就用, 在[1, 5, 100, 7, 23]这样的数组中,你会发现性能不止没有提高反而大大降低了。
完整代码如下:
function countSort(arr) {
let maxValue = Math.max(...arr); // es6的骚操作
// 因为数组是从0开始的,我们要记录第n个元素需要创建长度为n+1的数组
let array = new Array(maxValue + 1).fill(0);
for (let i = 0; i < arr.length; i++) {
array[arr[i]] = (array[arr[i]] || 0) + 1;
}
let result = [];
for (let i = 0; i < array.length; i++) {
let cur = array[i];
while (cur) {
result.push(i);
cur--;
}
}
return result;
}
今日的算法解析就到这吧,喜欢的朋友点个赞谢谢!有什么好的建议欢迎在评论区留言讨论,大家一起学算法,从小白向前冒泡,卷死他们!