计数排序
计数排序不是交换排序,它的核心思想是将数组进行遍历并新建一个新的数组,新数组的索引就是原数组里的元素,索引对应的元素是该元素在原数组出现的次数,最后遍历新数组,根据元素出现的次数打印几次元素。计数排序可以分为基础版本和优化版本,基础版本不是稳定排序(如果有相同元素排序后元素的位置不变),优化后的计数排序是稳定排序。计数排序平均时间复杂度O(n+m) m为最大数与最小数的差值,空间复杂度O(n)。
计数排序有两个很明显的缺点:
- 最大值与最小值的差值太大不适用计数排序,差值太大会大大浪费空间,并且随之时间复杂度会升高
- 数组元素不是正整数也不适用计数排序,计数排序的核心思想是统计元素出现次数,而元素本身是一个索引
代码实现
基础版
基础版的计数排序并不是稳定排序,排序后相同的元素位置不明确
function countSort(array) {
// 找到数组最大值
const max = Math.max(...array);
// 找到数组最小值
const min = Math.min(...array);
// 偏移量
const offset = max - min;
// 统计数组
const countArray = Array(offset + 1).fill(0);
// 遍历原数组并做统计
for(let i = 0; i < array.length; i++){
countArray[array[i] - min] += 1;
}
// 定义排序后的数组
const sortedArray = [];
// 排序数组的索引
let index = 0;
// 遍历统计数组
for(let i = 0; i < countArray.length; i++){
// 根据元素出现的次数遍历
for(let j = 0; j < countArray[i]; j++){
sortedArray[index++] = i + min;
}
}
return sortedArray;
}
const array = [95,94,91,98,99,90,99,93,91,92];
console.log(countSort(array));
优化版
由于基础版的计数排序不是稳定排序,排序后相同元素位置不明确,所以需要优化一下。具体优化方案是: 统计数组不再是统计元素出现的次数,而是从第二个元素开始,每一个元素都加上前面所有元素之和。然后倒序遍历原始数组,每遍历一次,统计数组元素值就会减一。这样一来相同元素的也会出现在正确的位置上。
function countSort(array) {
// 找到数组最大值
const max = Math.max(...array);
// 找到数组最小值
const min = Math.min(...array);
// 偏移量
const offset = max - min;
// 统计数组
const countArray = Array(offset + 1).fill(0);
// 遍历原数组并做统计
for(let i = 0; i < array.length; i++){
countArray[array[i] - min] += 1;
}
// 优化 countArray 元素是之前元素的总和
let sum = 0;
for(let i = 0; i < countArray.length; i++){
sum += countArray[i];
countArray[i] = sum;
}
// 定义排序后的数组
const sortedArray = Array(array.length).fill(0);
// 优化 倒序遍历原始数组,从统计数组找到正确位置,输出到结果数组
for(let i = array.length - 1 ; i >=0; i--){
// 统计数组的元素值其实就是该元素在排序数组的位置
sortedArray[countArray[array[i] - min] - 1] = array[i];
// 将统计数组元素值 减一,下次遇到同样的元素位置才会正确
countArray[array[i] - min]--;
}
return sortedArray;
}
const array = [95,94,91,98,99,90,99,93,91,92];
console.log(countSort(array));