常见的数组排序算法有哪些?
1. JS内置排序方法 Array.prototype.sort()
默认按字符串Unicode码排序
- 升序 arr.sort((a, b) => a - b);
- 降序 arr.sort((a, b) => b - a);
底层实现(V8引擎)
小数组(小于等于10):插入排序(Insertion Sort);
大数组:Timsort (稳定、自适应、混合归并+插入,时间复杂度O(nlogn)。
特点: 原地排序(会修改原数组)、稳定排序(ES209起规范要求)
2. 快速排序
平均时间复杂度O(n logn),空间复杂度O(logn),不稳定
- 先从一个数列中取出一个数作为“基准”。
- 分区过程:将比这个“基准”大的数全放在“基准”的右边,比这个“基准”小的数放在左边。
- 再对左右区间重复第二步,直到各区间只有一个数。
const arr = [23,54,34,8,35,98,21,45,76];
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)]; //基准数
const left = [];
const right = [];
const equal = [];
for (let item of arr) {
if (item < pivot) {
left.push(item);
} else if (item > pivot) {
right.push(item);
} else {
equal.push(item);
}
}
return [...quickSort(left), ...equal, ...quickSort(right)];//链接左数组、基准数数组、右数组
console.log(quickSort([...arr1])); // [8, 21, 23, 34, 35, 45, 54, 76, 98]
3. 归并排序
平均时间复杂度O(nlogn),空间复杂度O(n),稳定
- 将数组递归地二分,直到每个子数组只有一个元素(或空)。
- 将两个已排序的子数组合并成一个有序数组。
- 整个过程像二叉树,从根(原数组)不断拆分到叶子(单个元素),再从叶子向上逐层合并回有序数组。
// 归并排序
const arr = [23,54,34,8,35,98,21,45,76];
function mergeSort(arr:number[]):number[] {
//基线条件
if(arr.length<=1)return arr;
//分:找到中点,分割数组
const mid = Math.floor(arr.length/2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
//递归排序左右两部分,并合并
return merge(mergeSort(left), mergeSort(right));
}
// 合并两个已排序数组
function merge(left: number[], right: number[]){
const result = [];
let i = 0, j =0;
//双针比较,取较小者
while(i<left.length && j <right.length){
if(left[i] <=right[j]){
result.push(left[i]);
i++;
}else{
result.push(right[j]);
j++;
}
}
// 将剩余元素拼接(left或right必有一个已空)
return result.concat(left.slice(i)).concat(right.slice(j));
}
console.log(mergeSort(arr1)); // [8, 21, 23, 34, 35, 45, 54, 76, 98]
4. 选择排序
平均时间复杂度O(n2),空间复杂度O(1),不稳定
- 首先在未排序序列中找到最小(最大)元素,存放在排序序列的起始位置;
- 再从剩余未排序元素中继续寻找最小(最大)元素,存放在已排序序列的末尾;
- 重复第二步,直到所有元素均排序完毕。
//选择排序
const arr = [23,54,34,8,35,98,21,45,76];
function selectionSort(arr: number[]):number[] {
let minIndex: number, temp: number;
for(var i =0; i<arr.length -1; i++){
minIndex = i;
for(var j=i+1; j< arr.length; j++){
if(arr[j]< arr[minIndex]){ //寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i]= arr[minIndex];
arr[minIndex]= temp;
}
return arr;
}
console.log(selectionSort([...arr1]));// [8, 21, 23, 34, 35, 45, 54, 76, 98]
5. 冒泡排序
平均时间复杂度O(n2),空间复杂度O(1),稳定
- 比较两个相邻的元素。如果第一个比第二个大,就交换它们两个位置。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function bubbleSort(arr: number[]): number[] {
for(var i=0;i<arr.length-1;i++){
for(var j=0; j< arr.length-1-i; j++){
if(arr[j]>arr[j+1]){ //相邻两个元素对比
var temp =arr[j+1]; //元素交换
arr[j+1]=arr[j];
arr[j]=temp;
}
}
}
return arr;
}
console.log(bubbleSort([...arr1]));// [8, 21, 23, 34, 35, 45, 54, 76, 98]
6. 插入排序
平均时间复杂度O(n2),空间复杂度O(1),稳定
- 将第一个元素看作已排序序列,将第二个到最后一个元素当成未排序序列。
- 从头到尾以此扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
- 如果待插入的元素和有序序列中的某个元素相等,则将待插入元素插入到相等元素后面
//插入排序
function insertionSort(arr: number[]): number[] {
let j:number, current:number;
for(var i=1;i<arr.length;i++){
j = i-1;
current = arr[i]; //当前要插入的元素
// 在已排序部分[0,i-1]中从后往前找插入的位置
while(j>=0 && arr[j] > current) {
arr[j+1]=arr[j]; // 元素后移
j--;
}
arr[j+1] = current; // 插入到正确位置
}
return arr;
}
console.log(insertionSort([...arr1])); // [8, 21, 23, 34, 35, 45, 54, 76, 98]