一、认识计数排序
这个排序我是真心喜欢,它帮助我打破了我的思维禁锢,以前我认为给序列排序是一定要在元素之间比较的,而且排序算法的时间复杂度的下限就是O(nlogn),比如我们熟知的快排、希尔、堆排序等等;
计数排序在某些情况下是快过上面说的比较排序算法的,比如0-100排序,计数排序是最好的;
当输入的元素是n个0~k之间的整数时,它的运行时间是O(n+k);
计数排序的核心思想就是用统计数组的下标来表示原始数组的值,而统计数组对应下标的值就是原始数组中值出现的次数;排序就只需要遍历统计数组,按照值的大小来输出下标即可;
二、非稳定计数排序
如果统计数组中的值是大于1的,那么怎样区分这两个相同值的顺序呢?可以区分那说明算法是稳定的,不能区分就说明算法不稳定;话不多说,先上第一个版本的:
function countSort(array) {
const C = [];
// 1.计数
for (let i = 0; i < array.length; i++) {
const j = array[i];
C[j] >= 1 ? C[j]++ : (C[j] = 1);
}
console.log("统计数组", C);
const D = [];
// 2.将下标加入到结果数组(不稳定)
for (let j = 0; j < C.length; j++) {
if (C[j]) {
while (C[j] > 0) {
D.push(j);
C[j]--;
}
}
}
return D;
}
这个算法的逻辑并不复杂,统计数组的长度直接取决于原始数组的最大值,为原始数组最大值+1;这会导致0~最小值之间的位置都浪费掉,等下讲解决方法;拿到统计数组后,直接从前往后遍历统计数组,有就将值加入结果数组;
但缺点也很明显,空间的浪费,以及不是稳定的;
三、改良版计数排序
像[70, 72, 73, 74, 90]这样的数组,用前面的算法的话,70之前不都得空着吗,多浪费呀!
我们可以先或取原始数组中的最大值以及最小值,统计数组的长度就等于最大值-最小值+1;
而最小值即为偏移量,统计数组的索引+偏移量 就是原始数组里面的值了;
let countArray = new Array(max - min + 1).fill(0);
这里填步填充0都可以,我是为了打印美观才填充的;
最后是解决稳定性的问题,我们可以采用对统计数组中所有的计数累加,就是每一项是本身和前一项之和(第一个元素除外),这样的好处就是知道自己的顺序,因为增加的数就代表比自己小的元素的个数;最后我们反向遍历原始数组,依次按照统计数组记录的值输出即可;
function countingSort(array) {
let length = array.length;
let resultArray = [];
let min = array[0];
let max = array[0];
// 1.max min
for (let i = 0; i < length; i++) {
min = min <= array[i] ? min : array[i];
max = max >= array[i] ? max : array[i];
}
// 统计数组的长度为max-min+1;min作为偏移量
let countArray = new Array(max - min + 1).fill(0);
// 2.统计次数
for (let i = 0; i < length; i++) {
countArray[array[i] - min] = countArray[array[i] - min]
? countArray[array[i] - min] + 1
: 1;
}
// 3.累加计数
for (let i = 0; i < countArray.length - 1; i++) {
countArray[i + 1] = (countArray[i + 1] || 0) + (countArray[i] || 0);
}
console.log("统计数组:", countArray);
// 4.反向遍历
for (let i = length - 1; i >= 0; i--) {
resultArray[countArray[array[i] - min] - 1] = array[i];
countArray[array[i] - min]--;
}
return resultArray;
}
这里面比较难的代码应该是下面这个了:
resultArray[countArray[array[i] - min] - 1] = array[i];
我们先来分析一下,统计数组里面值表示了对应的索引在结果数组里对应的位置,如值为5,说明其排第5,那对应在结果数组中的索引是多少?当然是5-1了
所以countArray[array[i] - min] - 1是为了拿到索引;
我们上上面也分析了统计数组的索引+偏移量就是原始数组里面的值,那array[i] - min就是为了拿到统计数组的索引,拿到了索引才能拿到顺序,拿到了顺序我们才能加入正确的值;
这个改良版是我自己写的,还不够浓缩,所以可能有些地方看着比较赘余,欢迎各位提出建议!