对sort的疑惑
自从es6出现后,我们不知不觉就用上了sort进行排序,毕竟人家又快又好。那么sort究竟怎么实现的呢?
参考了一下大佬的文章(三种快速排序以及快速排序的优化),我便悄悄拿起了键盘。
思路分析这些我就不写了,请移步大佬博客参照,由于大佬使用的java代码,我便用js来进行验证一下。
排序条件以及优化结果
根据大佬的分析,数组排序要面对的有以下四种情况,我们用1w个随机值为例进行一下测试,结果如下:
提示:由于每次随机值不一样,测试结果有一定出入,但大致应该差不多
数组情况 | 随机数组 | 升序数组 | 降序数组 | 重复数组 | 解决的问题 |
---|---|---|---|---|---|
固定基准值 | 29 | 2953 | 2920 | 282 | 解决随机数组的排序问题 |
随机基准值 | 36 | 20 | 11 | 277 | 解决有序数组的排序问题 |
三数取中 | 30 | 11 | 11 | 256 | 解决反向有序数组的排序问题 |
三数取中+插排 | 30 | 11 | 11 | 263 | 优化排序效率,1w可能看不太出来,100w个数据效果更明显 |
三数取中+插排+聚同 | 22 | 8 | 8 | 21 | 解决重复数据较多时的排序问题 |
sort | 18 | 8 | 2 | 9 | 解决了以上的几个问题 |
- 随机数组
let arr1 = []; for(let i = 0; i < 10000; i++){ arr1.push(Math.floor(Math.random()*10000) ) }
- 升序数组
let arr2 = []; for(let i = 0; i < 10000; i++){ arr2.push(i) }
- 降序数组
let arr3 = []; for(let i = 10000; i > 0; i--){ arr3.push(i) }
- 重复数组
let arr4 = []; for(let i = 0; i < 10000; i++){ arr4.push(Math.floor(Math.random()*10) ) }
- 比较方式,使用了
performance.now
来计算时间let arrs = [arr1, arr2, arr3, arr4] for(let i = 0; i< arrs.length; i++){ const a1 = performance.now(); sort( arrs[i] ) const a2 = performance.now(); console.log(a2 - a1); }
快速排序优化1之固定基准值
const sort = arr => {
if (arr.length < 2) return arr;
// 固定基准值
let pivot = arr[0];
let left = [];
let right = [];
// 从1开始
for (let i = 1, total = arr.length; i < total; i++) {
if (arr[i] < pivot) left.push(arr[i]);
else right.push(arr[i]);
};
return [
...sort(left),
pivot,
...sort(right)
];
};
快速排序优化2之随机基准值
const sort = arr => {
if (arr.length < 2) return arr;
// 使用随机基准值
let random = Math.floor(Math.random()*arr.length);
let pivot = arr[random];
arr.splice(random, 1);
let left = [];
let right = [];
// 从0开始
for (let i = 0, total = arr.length; i < total; i++) {
if (arr[i] < pivot) left.push(arr[i]);
else right.push(arr[i]);
};
return [
...sort(left),
pivot,
...sort(right)
];
};
快速排序优化3之三数取中
const sort = arr => {
if (arr.length < 2) return arr;
// 使用三数取中
let start = arr[0];
let middle = arr[Math.floor(arr.length/2)];
let end = arr[arr.length-1];
let pivot = middle;
if (start > middle && start < end) {
pivot = start;
arr.splice(0, 1)
} else if (end > middle && end < start) {
pivot = end;
arr.splice(arr.length - 1, 1)
} else if (pivot === middle) {
arr.splice(Math.floor(arr.length / 2), 1)
}
let left = [];
let right = [];
for (let i = 0, total = arr.length; i < total; i++) {
if (arr[i] < pivot) left.push(arr[i]);
else right.push(arr[i]);
};
return [
...sort(left),
pivot,
...sort(right)
];
};
快速排序优化4之三数取中+插排
function insertArr(arr){
for(let i = 1 , len = arr.length; i < len ; i++){
let preIndex = i - 1;
let current = arr[i];
while(preIndex >= 0 && current < arr[preIndex] ){
arr[preIndex+1] = arr[preIndex]
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
const sort = arr => {
if (arr.length < 2) return arr;
// 当数组长度小于10用插入排序
if (arr.length < 10) {
return insertArr(arr);
}
// 使用三数取中
let start = arr[0];
let middle = arr[Math.floor(arr.length / 2)];
let end = arr[arr.length - 1];
let pivot = middle;
if (start > middle && start < end) {
pivot = start;
arr.splice(0, 1)
} else if (end > middle && end < start) {
pivot = end;
arr.splice(arr.length - 1, 1)
} else if (pivot === middle) {
arr.splice(Math.floor(arr.length / 2), 1)
}
let left = [];
let right = [];
for (let i = 0, total = arr.length; i < total; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
};
return [
...sort(left),
pivot,
...sort(right)
];
};
快速排序优化5之三数取中+插排+聚同
function getMiddle(a, b, c) {
var min = Math.min(a, b, c);
var max = Math.max(a, b, c);
var middle = a + b + c - min - max;
return middle;
}
function insertArr(arr){
for(let i = 1 , len = arr.length; i < len ; i++){
let preIndex = i - 1;
let current = arr[i];
while(preIndex >= 0 && current < arr[preIndex] ){
arr[preIndex+1] = arr[preIndex]
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
const sort = arr => {
if (arr.length < 2) return arr;
// 当数组长度小于10用插入排序
if (arr.length < 10) {
return insertArr(arr);
}
// 使用三数取中
let start = arr[0];
let middle = arr[Math.floor(arr.length / 2)];
let end = arr[arr.length - 1];
let pivot = getMiddle(start, middle, end);
let left = [];
let right = [];
let alike = [];
for (let i = 0, total = arr.length; i < total; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else if (arr[i] > pivot) {
right.push(arr[i]);
} else {
alike.push(arr[i])
}
};
return [
...sort(left),
...alike,
...sort(right)
];
};
结语
使用最后一层优化后,结果依然和原生sort有一定的差距,但是大致相差不是远。本想用100w个数据来测试,但是前两种方法直接让控制台死机,但只要能得出结果就ok,可见直接用快速排序并不能达到最佳效果。
追加总结1
今天对第五种方案进行了一些优化,去掉了多余 if-else
的判断。并且重新对 原生 js 的 Array.prototype.sort
进行测试,它的表现依旧非常出色。在写 sort
的时候,我也发现了一个非常重要的注意事项,那就是一定要记着 return 0
,这个方法其实就是为了解决高浓度重复数组的排序问题。效果等同,甚至优于聚同方案。
写法如下:
arr.sort((a, b) => {
if( a > b ){
return 1;
}else if( a < b ){
return -1;
}else{
// 等同聚同方案
return 0
}
})
参考文献