开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
大家在日常的开发过程中,如果遇到要对一个数组进行某个字段的排序,我想肯定会写出下面这段代码
array.sort((a,b) => a.key - b.key)
很快啊,就实现了一个数组按照某个字段来排序。但是当数组很大时,如一个数组长度成千上万,这样遍历会不会造成性能浪费呢?
答案是肯定的。那我们有其他的方法进行排序吗?一定有,下面介绍一下几个常规的排序方法。
一、 冒泡排序
平均时间复杂度: O(n*n) 稳定性: 稳定 相信冒泡排序对于大家来说肯定是很熟悉的了。通常的冒泡排序就是用两个for循环来遍历数组。
// 冒泡排序
function bubbleSort(arr) {
const len = arr.length
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
return arr
}
我们可以从代码中看到,每遍历一次就拿当前的元素与下一个元素进行对比,根据要倒序还是顺序来决定是否要进行交换。比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
二、 选择排序
平均时间复杂度: O(n*n) 稳定性: 不稳定
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
function selectSort(arr) {
const len = arr.length
let minIndex = 0
for (let i = 0; i < len - 1; i++) {
// 当前最小值得索引为i
minIndex = i
for (let j = i + 1; j < len; j++) {
// 遍历i后的元素
if (arr[j] < arr[minIndex]) {
// 找出最小的下标
minIndex = j
}
}
// 找到最小的下标后,与i的下标元素进行替换
let temp = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = temp
}
return arr
}
三、 插入排序
平均时间复杂度: O(n*n) 稳定性: 稳定
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
function insertSort(arr) {
const len = arr.length
let current = null
for (let i = 0; i < len; i++) {
// 当前需要插入的元素
current = arr[i]
for (let j = i - 1; j >= 0; j--) {
// 这里为啥不能直接用arr[i]来比较?
// 因为如果前一个比arr[i]大,那就会把arr[i]替换成arr[i-1]
if (arr[j] > current) {
// 交换
arr[j + 1] = arr[j]
arr[j] = current
} else {
arr[j + 1] = current
break
}
}
}
return arr
}
看到这里,感觉各位大哥们已经有点迷糊了,这里我说一下选择排序和插入排序的区别:
选择排序和插入排序区别:
- 选择排序是每一次从后面没有排序的数组里找最小的往前面放。
- 插入排序是每一次遍历拿当前元素与已经排序的序列进行比较,并放到正确顺序位置
四、 希尔排序
平均时间复杂度: O(nlogn) 稳定性: 不稳定
受不了了,开始整名词了。希尔是啥?啥英雄?哈哈哈,其实希尔排序是插入排序的一个优化版本,其主要思想就是
- 首先定义一个增量
- 以增量为间隔进行分组
- 组内进行排序
- 增量变为原来的二分之一,重复执行
- 直到增量小于一
function shellSort(arr) {
const len = arr.length
let gap = Math.floor(len / 2)
let current = null
while (gap > 0) {
// 进行分组插入排序
for (let i = gap; i < len; i++) {
current = arr[i]
// for (var j = i - gap; j >= 0 && arr[j] > current; j = j - gap) {
// arr[j + gap] = arr[j]
// }
// arr[j + gap] = current
for (let j = i - gap; j >= 0; j = j - gap) {
if (arr[j] > current) {
arr[j + gap] = arr[j]
arr[j] = current
} else {
arr[j + gap] = current
break
}
}
}
// 开始下一个循环
gap = Math.floor(gap / 2)
}
return arr
}
四、 快速排序
平均时间复杂度: O(nlogn) 稳定性: 稳定
听名字就知道很厉害,确实。快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。
- 从数列中挑出一个元素,称为 "基准"(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
// 交换
function swap(arr, left, right) {
let temp = arr[left]
arr[left] = arr[right]
arr[right] = temp
}
function quickSort(arr, left, right) {
if(left >= right ){
return
}
// 取第一个头节点
let point = arr[left]
let i = left
let j = right
// 当左索引小于右索引,退出
while (i != j) {
// 从右边找第一个小于point的元素
while (i < j && arr[j] > point) {
j--
}
// 从左边开始,找第一个大于point的元素
while (i < j && arr[i] <= point) {
i++
}
// 交换左右节点
if(i < j){
swap(arr, i, j)
}
}
// i j 重合. 交换重叠索引和point
swap(arr, left, i)
// 数组左边递归
quickSort(arr,left, j)
// 数组右边递归
quickSort(arr, j + 1, arr.length - 1)
}