排序算法
交换函数
function swap(arr, a, b) {
[arr[b], arr[a]] = [arr[a], arr[b]]
}
冒泡排序

function bubbleSort(arr, sort = (a, b) => a - b) {
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]) {
swap(arr, j, j + 1)
}
}
}
}
通过第二个参数,控制正序和逆序
// 默认从小到大
function bubbleSort(arr, sort = (a, b) => a - b) {
const len = arr.length
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (sort(arr[j], arr[j + 1]) > 0) {
swap(arr, j, j + 1)
}
}
}
}
bubbleSort(arr, (a, b) => b - a) // 从大到小
选择排序
每一次内循环遍历寻找最小的数,记录下 minIndex,并在这次内循环结束后交换 minIndex 和 i 的位置

function selectionSort(arr) {
const len = arr.length
for (let i = 0; i < len - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
插入排序
像玩扑克牌一样,抽到一张牌,往手上已排序的牌堆里放

function insertionSort(arr) {
const len = arr.length
for (let i = 1; i < len; i++) {
const temp = arr[i];
let pre = i - 1
while(arr[pre] > temp) {
arr[pre + 1] = arr[pre]
pre--
}
arr[pre + 1] = temp
}
}
希尔排序
希尔排序就是按 gap 分组,对每组做插入排序。gap 不断变小,最后 gap = 1 时,就是一次普通插入排序。
原数组下标:
0 1 2 3 4 5 6 7 8 9
gap = 5
0--------------5
1--------------6
2--------------7
3--------------8
4--------------9
gap = 2
0-----2-----4-----6-----8
1-----3-----5-----7-----9
gap = 1
0--1--2--3--4--5--6--7--8--9
function shellSort(arr) {
let len = arr.length
let gap = Math.floor(len / 2)
while (gap > 0) {
// 插入排序
for (let i = gap; i < len; i++) {
const temp = arr[i]
let pre = i - gap
while (arr[pre] > temp) {
arr[pre + gap] = arr[pre]
pre -= gap
}
arr[pre + gap] = temp
}
gap = Math.floor(gap / 2)
}
}
归并排序

function mergeSort(arr) {
const len = arr.length
if (len < 2) return arr
const min = Math.floor(len / 2)
const left = arr.slice(0, min)
const right = arr.slice(min)
return merge(mergeSort(left), mergeSort(right))
}
function merge(left, right) {
const res = []
while (left.length > 0 && right.length > 0) {
res.push(left[0] <= right[0] ? left.shift() : right.shift())
}
return res.concat(left, right)
}
快速排序
- 从数列中挑出一个元素,称为 "基准"(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

function quickSort(arr, left = 0, right = arr.length - 1) {
if (left < right) {
const point = sort(arr, left, right)
quickSort(arr, left, point - 1)
quickSort(arr, point + 1, right)
}
return arr
}
function sort(arr, left, right) {
// 以 left 为基准值
let index = left + 1 // 可交换的位置
for (let i = index; i <= right; i++) {
if (arr[i] < arr[left]) {
swap(arr, i, index) // index 已交换,即此时 index 位置的就是小于基准值的,所以需要右移一位
index++
}
}
// index - 1 是最后一个小于基准值的,调换 index + 1 和 left
// 则 left 左边都是小于 left 的,右边都是大于 left 的
swap(arr, left, index - 1)
return index - 1 // 把基准值下标返回出去
}
堆排序
堆是一棵完全二叉树,常用数组来存。
- 大顶堆:每个父节点都大于等于它的左右子节点
- 小顶堆:每个父节点都小于等于它的左右子节点
从小到大排序,一般建大顶堆。每次把堆顶最大值换到数组末尾,再对剩下的部分重新堆化。
下标从 0 开始时,公式很重要:
const parent = Math.floor((i - 1) / 2)
const left = i * 2 + 1
const right = i * 2 + 2
function heapSort(nums: number[]): number[] {
const len = nums.length
// 从最后一个非叶子节点开始建堆
for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
heapify(nums, len, i)
}
for (let i = len - 1; i > 0; i--) {
swap(nums, 0, i)
heapify(nums, i, 0)
}
return nums
}
function heapify(nums: number[], len: number, i: number) {
let largest = i
const lson = i * 2 + 1
const rson = i * 2 + 2
if (lson < len && nums[lson] > nums[largest]) {
largest = lson
}
if (rson < len && nums[rson] > nums[largest]) {
largest = rson
}
if (largest !== i) {
swap(nums, largest, i)
heapify(nums, len, largest)
}
}
function swap(nums: number[], i: number, j: number) {
[nums[i], nums[j]] = [nums[j], nums[i]]
}
时间复杂度:O(nlogn),空间复杂度:O(1),不稳定。