前言
学习与分享,两者是一个相辅相成的关系。写下笔记,记录下当前的学习记录,待下次需要复习的时候,能有个很好的借鉴,也希望借此加深自己对已学知识的理解。我将尽量以通俗易懂的方式来描述,如果有什么描述不当的地方,还请各位大大指出,谢谢。
正文
1. 选择排序 (将数组内元素从小到大进行排序,下同), eg: [3, 5, 6, 2, 1]
> 核心思想是,从左往右,找出最小的元素值对应的索引`min`,与需要替换的值进行替换

const selectionSort = (array) => {
// 从左往右进行排序,先排第一个
let index = 0
while (index < array.length - 1) {
// 假定第一个值是最小的
let min = index
// 遍历该数组
for (let i = min + 1; i < array.length; i++) {
// 寻找比它小的值,如果能找到比它小的,就记录最小值对应索引,以便循环完毕后进行替换
if (array[i] < array[min]) {
min = i
}
}
// 替换元素值,并让index++,进行下一轮循环找最小值, 直到倒数第二个值为止
swap(array, index++, min)
}
}
2. 插入排序
> 核心思想是, 从左往右,如果下一个元素比前一个元素小,则进行互换,否则跳出循环进入下一轮比对


const insertSort = (array) => {
let index = 0
while (index < array.length) {
// 向后加一位,然后对这个数组进行值对比
for (let i = ++index; i > 0; i--) {
if (array[i] < array[i - 1]) {
swap(array, i - 1, i)
} else {
break
}
}
}
}
3. 归并排序
> 核心思想是,将数组递归分割为各个小数组,并给各个小数组各自进行排序,最后合并回原数组中,
- 以四个元素的数组为例
arr = [5, 6, 2, 1] - 获取中间值作为递归分割点
middle = Math.floor((0 + 3) / 2) => middle = 1 - 第一个小数组为索引
0 - middle对应的元素, 第二个小数组为索引middle - arr.length -1对应的元素 - 也就是将
arr分为两个小数组,分别为[5, 6],[2, 1] - 先对小数组进行排序,这里直接看右边的小数组(因为第一个小数组相当于已经排好序了)
- 循环小数组
[2, 1](在arr数组中对应的位置为索引2和3) - 获取中间值作为递归分割点
middle = Math.floor((0 + 1) / 2) => middle = 0 - 由于再往后进行的时候,两个元素的数组会分割成两个只有一个元素的数组,那个时候已经没必要进行单个元素自己对比自己。因此,可以对小数组内元素进行排序
- 先建立辅助数组 aux,并且该数组是原数组的浅拷贝
aux = [...arr] - 以
middle为分割点,将小数组模拟划分为两个小数组,左数组入口i为元素2对应的索引值2,右数组入口j为middle + 1,也就是元素1对应的索引值3 - 循环
aux对应的索引2和3

- 这样就将一个小数组排好序了(可能没有讲的很明白,如果有问题,评论区里问一下,我在空闲的时候回复你,另附可视化跳链)

- 具体代码实现。 PS: 这里的代码只是排序的代码,并没有包括分组的代码(看下去)
// array => 原数组
// aux => 辅助数组
// low => 需要排序的索引最小值
// mid => 分割点
// high => 需要排序的索引最大值
const _merge = (array, aux, low, mid, high) => {
// 重新给辅助数组赋值,防止辅助数组已被污染
for (let k = low; k <= high; k++) {
aux[k] = array[k]
}
let i = low // 设置左入口
let j = mid + 1 // 设置右入口
for (let k = low; k <= high; k++) {
if (i > mid) { // 如果左边的入口索引值已经大于mid,则证明左边的值都已经排序完成
array[k] = aux[j++]
} else if (j > high) { // 同理,如果右边的入口索引值已经大于最大长度,则证明右边的值都已经排序完成
array[k] = aux[i++]
} else {
// 如果左边当前索引值小于右边的索引值,则将左边索引对应值赋值给数组
aux[i] < aux[j] ? array[k] = aux[i++] : array[k] = aux[j++]
}
}
}
- 归并算法详尽代码
let array = [5, 6, 2, 1]
const mergeSort = (array) => {
const _sort = (array, aux, low, high) => {
if (low >= high) {
return
}
const mid = Math.floor((low + high) / 2)
// 以 mid 为分割点,分割数组至更小的数组然后再进行 _merge 排序
_sort(array, aux, low, mid)
_sort(array, aux, mid + 1, high)
_merge(array, aux, low, mid, high)
}
const _merge = (array, aux, low, mid, high) => {
for (let k = low; k <= high; k++) {
aux[k] = array[k]
}
let i = low // 设置左入口
let j = mid + 1 // 设置右入口
for (let k = low; k <= high; k++) {
if (i > mid) { // 如果左边的入口索引值已经大于mid,则证明左边的值都已经排序完成
array[k] = aux[j++]
} else if (j > high) { // 同理,如果右边的入口索引值已经大于最大长度,则证明右边的值都已经排序完成
array[k] = aux[i++]
} else {
// 如果左边当前索引值小于右边的索引值,则将左边索引对应值赋值给数组
aux[i] < aux[j] ? array[k] = aux[i++] : array[k] = aux[j++]
}
}
}
// 设置辅助数组
let aux = [...array]
_sort(array, aux, 0, array.length - 1)
}
mergeSort(array)
console.log(array)
// [1, 2, 5, 6]
4. 快速排序
核心思想是,选定一个值作为支点,然后根据该支点对应的值将比它小的值划分到左边,将比它大的值划分到右边,此时该支点对应的值已经排好,然后再对支点左边的值进行排序,对右边的值进行排序。(不分先后,都可以)
- 以首位
index = 0为支点pivat,也可以认为是分区点 - 设定一个让索引往右移的指针,为支点将要去往的分支点的右边,
storeIndex = index + 1 - 循环数组,只要找到比支点小的值,就让它与
storeIndex指针对应的值替换,并且让storeIndex往右移(++) - 直到数组已经循环完毕后,
storeIndex对应的值还是分支点的右边,此时只需要将storeIndex - 1的值与pivat进行替换,pivat的值就已经排好了。

- 对应代码实现
const _quickSort = (array, low, high) => {
let pivat = low // 设定支点值
let storeIndex = low + 1 // 以支点后第一个值作为划分比支点值小以及比支点值大的索引值,称为阈值
let index = storeIndex // 指针从分区值之后第一个值开始
while (index <= high) {
// 遇到比支点值小的,就和阈值进行交换,并让阈值索引后移一位
if (array[index] < array[pivat]) {
swap(array, storeIndex++, index)
}
index++
}
// 此时的阈值是处于划分两侧值的阈值右方,因此需要 - 1 拿到真正的支点分区值,与设定的支点进行替换,此时左侧的值都是比分区值小的,右侧都是比分区值大的
swap(array, pivat, storeIndex - 1)
}
- 快速排序详细代码
// 快速排序
let array = [3, 5, 6, 2, 1]
const quickSort = (array) => {
const _sort = (array, low, high) => {
if (low >= high) {
return
}
let j = _quickSort(array, low, high)
_sort(array, low, j - 1)
_sort(array, j + 1, high)
}
const swap = (array, i, j) => {
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
const _quickSort = (array, low, high) => {
let pivat = low // 设定支点值
let storeIndex = low + 1 // 以支点后第一个值作为划分比支点值小以及比支点值大的索引值,称为阈值
let index = storeIndex // 指针从分区值之后第一个值开始
while (index <= high) {
// 遇到比支点值小的,就和阈值进行交换,并让阈值索引后移一位
if (array[index] < array[pivat]) {
swap(array, storeIndex++, index)
}
index++
}
// 此时的阈值是处于划分两侧值的阈值右方,因此需要 - 1 拿到真正的支点分区值,与设定的支点进行替换,此时左侧的值都是比分区值小的,右侧都是比分区值大的
swap(array, pivat, storeIndex - 1)
return storeIndex - 1
}
_sort(array, 0, array.length - 1)
}
quickSort(array)
console.log(array)
写在最后
写着写着就6.1了,5月的随后一天,在写这篇文章的前5分钟,我都没有想过要写下来,不过后来在学习的过程中,总会遇到一些当时难以理解的问题,如果能把它记录下来,那当我下次在使用到,但那一时半会没法想起具体的思想的时候,可以回来看看自己当时的想法,如果有什么理解错误的地方,还请指出,谢谢。看到这里的同行者,谢谢你,能让你读到这里,就表示我还是有进步的了,后续继续加油!当然,如果我的这篇花了不少心思的文章能够刚好帮到你,我也很荣幸与开心哈哈。