选择排序
核心思想:数组有 n 个元素,进行 n 次循环,每次循环会从数组中找到最小的那个元素,然后将其交换到数组前面来,经过 n 次循环,数组就是从小到大排序好了。
function selectionSort(arr, n) {
for(let i = 0; i < n; i++) {
// 最小元素所在区间:[i, n)
let minIndex = i
for(let j = i + 1; j < n; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j
}
}
let temp = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = temp
}
}
两轮 for 循环,外层 for 循环有一个 minIndex 用来记录当前最小元素的索引,内层循环找到比当前最小元素还要小的元素并更新最小索引,内层循环结束后,minIndex 就是数组最小元素的索引了,将最小元素交换到前边来。
插入排序
核心思想:默认数组第一个元素是有序的,外层循环从第二个元素开始向后遍历,内层循环从当前 i 位置逐个与前一个元素作比较,只要比它小就做一次交换,直到当前元素比前面位置元素要大,那当前元素就插入当前的这个位置。
function insertionSort(arr, n) {
for(let i = 1; i < n; i++) {
for(let j = i; j > i; j--) {
if(arr[j] < arr[j - 1]) {
let temp = arr[j]
arr[j] = arr[j - 1]
arr[j - 1] = temp
}
}
}
}
这里有简写的地方,将判断内容作为 for 循环的条件,如下代码:
function insertionSort(arr, n) {
for(let i = 1; i < n; i++) {
for(let j = i; j > i && arr[j] < arr[j - 1]; j--) {
let temp = arr[j]
arr[j] = arr[j - 1]
arr[j - 1] = temp
}
}
}
最优的情况,每次内层循环只要执行一次,那就是当前元素直接大于上一个元素了,说明前边的元素已经有序,不需要继续执行循环。
当然还有性能优化的版本,就是上边内层循环当前元素不断与前一个元素做比较,比它小就做交换,最坏情况数组最后一个元素是数组中最小的那个,那就要与前边所有元素交换一次了。所以我们优化点就是,找到当前元素合适的插入位置后再将元素插入进行,核心比较逻辑没变,代码如下:
// 插入排序优化版
export function insertionSort1(arr, n) {
for(let i = 1;i < n; i++) {
// 默认要插入的元素
let e = arr[i]
// j 保存 e 要插入的位置
let j
for(j = i;j > 0 && arr[i] < arr[j - 1]; j--) {
arr[j] = arr[j - 1]
}
arr[j] = e
}
}
冒泡排序
核心思想:每次循环 n - 1 轮,每轮都会将当前元素与上一个元素做比较,通过设置一个标志位来判断当前数组是否有序了,如果有序了循环就停止,而且还有一个优化点,那就是每次 for 循环结束都会将一个最大元素冒到最后,此时 n-- 后,下次循环就不用考虑最后一个元素了。
function buddleSort(arr, n) {
let swapped
do() {
swapped = false
for(let i = 1; i < n; i++) {
if(arr[i - 1] > arr[i]) {
let temp = arr[i]
arr[i] = arr[i - 1]
arr[i - 1] = temp
// 将 swapped 设为 true 表示当次有进行交换
swapped = true
}
}
// 优化:每次循环结束后数组最后一个元素就是最大的了,下次循环不用考虑了
n--
} while(swapped)
}
通过 swapped 标志位来判断当次循环是否有交换元素,如果有交换的话说明当前数组可能还不是有序的,设置 swapped 为 true,这里有优化的点就是 n-- 是因为每次循环能将最大一个元素冒到数组的最后,下次循环不用考虑了。如果当次循环没有元素交换,说明当前数组已经有序了,那么 swapped 为 false 循环就终止了。
封装交换逻辑
上边选择/插入/冒泡排序都有交换元素这一逻辑,我们可以把这一部分抽成一个交换函数
export function swap(arr, i, j) {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
总结
选择排序:不管数组有没有序,每次内层循环都要完整执行,无法提前终止,效率低下。
插入排序:最优情况下,内层循环只要执行一次。
冒泡排序:最优情况下,数组完全有序,只需在 do 循环体执行一遍即可。