持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
数组排序的总结之冒泡、插入、选择、归并、快排
力扣
对于前端来说,数组排序并不是什么难题,一个sort走天下
- 从小到大
arr.sort((a,b) => { return a - b })
2.从大到小
arr.sort((a,b) => { return b - a })
但对于面试来说,这个sort可能不太够用了,哈哈,卷起来呀~
下面总结一下常用的几种排序算法(掘金修言小册学习记录),主要分五种
- 基础排序算法:
- 冒泡排序
- 插入排序
- 选择排序
- 进阶排序算法
- 归并排序
- 快速排序
冒泡排序
冒泡排序的过程,就是从第一个元素开始,重复比较相邻的两个项,若第一项比第二项更大,则交换两者的位置;反之不动,如下
这个过程中,循环了两次:第一次循环找出需要比较+交换多少轮,第二次循环找出每一轮有多少回的比较+交换,好像有点绕,结合动图和代码理解一下
function bubbleSort(arr) {
// 缓存数组长度
const len = arr.length
// 外层循环用于控制从头到尾的比较+交换 到底 有多少轮
for(let i=0;i<len;i++) {
// 内层循环用于 每一轮 遍历过程中的重复比较+ 交换
for(let j=0;j<len-1;j++) {
if(arr[j]>arr[j+1]) {
// 交换两者
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
}
return arr
}
在冒泡排序的过程中,有一些”动作“是不太必要的。比如,随着外层循环的进行,数组尾部的元素会渐渐变得有序——当我们走完第1轮循环的时候,最大的元素被排到了数组末尾;走完第2轮循环的时候,第2大的元素被排到了数组倒数第2位;走完第3轮循环的时候,第3大的元素被排到了数组倒数第3位......以此类推,走完第 n 轮循环的时候,数组的后 n 个元素就已经是有序的。
为了避免这些冗余的比较动作,我们需要规避掉数组中的后 n 个元素,对应的代码可以这样写
function bubbleSort(arr) {
// 缓存数组长度
const len = arr.length
// 外层循环用于控制从头到尾的比较+交换 到底 有多少轮
for(let i=0;i<len;i++) {
// 内层循环用于 每一轮 遍历过程中的重复比较+ 交换
for(let j=0;j<len-1-i;j++) {
if(arr[j]>arr[j+1]) {
// 交换两者
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
}
return arr
}
冒泡排序的时间复杂度是O(n^2),但有种最好情况,数组本身就是有序的,在最好情况下复杂度是 O(n),改进一下代码
function betterBubbleSort(arr) {
const len = arr.length
for(let i=0;i<len;i++) {
// 区别在这里,我们加了一个标志位
let flag = false
for(let j=0;j<len-1-i;j++) {
if(arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
// 只要发生了一次交换,就修改标志位
flag = true
}
}
// 若一次交换也没发生,则说明数组有序,直接放过
if(flag == false) return arr;
}
return arr
}
选择排序
重点是选择,选择出最小值
找当前范围内的最小值,循环数组,把最小值放到当前范围的头部,然后缩小范围(减去头部),重复上面操作,时间复杂度O(n^2)
function selectSort(arr) {
// 缓存数组长度
const len = arr.length
// 定义minIndex,缓存当前区间最小值的索引,注意是索引
let minIndex
// i 是当前排序区间的起点
for(let i =0;i<len-1;i++) {
// 初始化minIndex = i
minIndex = i
// i,j分别定义为当前区间的上下边界,i是左边界,j是右边界
for(let j =i;j<len;j++) {
// 若j处的数据项比当前最小值还要小,则更新最小值索引为j
if(arr[j] < arr[minIndex]) {
minIndex = j
}
}
// 如果 minIndex 对应元素不是目前 的头部元素,则交换两者
if(minIndex !== i) {
[arr[i],arr[minIndex]] = [arr[minIndex],arr[i]]
}
}
return arr
}
插入排序
重点是,前面的是有序数组,后面元素和前面每个元素比较,小的往前放
找到元素在它前面那个序列中的正确位置
具体来说,插入排序所有的操作都基于一个这样的前提:当前元素前面的序列是有序的。基于这个前提,从后往前去寻找当前元素在前面那个序列里的正确位置。
比如 一个数组 [5, 3, 2, 4, 1]
- 首先5作为一个有序数组,3作为当前元素,和5比较,3<5,因此5给3挪出一个位置变成 [空位置,5],然后看位置前面还有没有元素,这里没有了,把3放到空位置上,有序数组变成 [3,5]
- 然后2作为当前元素,首先和5比较,2<5,5挪出一个位置变成[3,空位置,5],然后继续往前,还有3,2<3,所以3放到空位置上变成,[空位置,3,5],再往前没有元素了,所以把2放到空位置变成[2,3,5]
- 继续下去,把4作为当前元素.....
代码如下
function insertSort(nums) {
// 缓存数组长度
const len = nums.length
// temp 每次遍历到的元素,当前需要插入的元素,
// i 用来标识每次被插入的元素的索引,从1开始(第二个)
for(let i = 1;i<len;i++) {
// j 帮助temp寻找自己应该有的定位,应该去哪
let j = i // 先初始为当前遍历的位置
temp = nums[i] // temp 就是当前遍历的元素,需要操作的元素
// 判断j前面的元素是否比temp大(是否比当前元素大)
while(j>0 && nums[j-1] > temp) {
// 前面的元素比temp(当前元素)大的话,继续往前(将j前面的一个元素后移一位,为temp让出位置
nums[j] = nums[j-1]
j--
}
// 循环让位结束后,得到的j 就是temp的正确索引
nums[j] = temp
}
return nums
}