zhangdizhangdi

排序算法

冒泡

O(n2) O(1)

相邻元素进行比较,如果前一个元素值大于后一个元素值,则交换,这样一轮下来,将最大的数在最右边冒泡出来。

简单说:把大的往后排

js
function bubbleSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
    console.log(`第${i + 1}轮`, arr)
  }
  return arr
}
console.log('🌰', bubbleSort([3, 9, 1, 4, 2]))
执行结果
第1轮 [ 3, 1, 4, 2, 9 ]
第2轮 [ 1, 3, 2, 4, 9 ]
第3轮 [ 1, 2, 3, 4, 9 ]
第4轮 [ 1, 2, 3, 4, 9 ]
🌰 [ 1, 2, 3, 4, 9 ]

选择

O(n2) O(1)

找到数组中最小的值,选中它并放到第一位,接着找到数组中第二小的值,选中它并放到第二位。

简单说:把小的往前排

js
function selectionSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    let minIndex = i
    for (let j = i; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    if (minIndex !== i) {
      ;[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
    }
    console.log(`第${i + 1}轮`, arr)
  }
  return arr
}
console.log('🌰', selectionSort([3, 9, 1, 4, 2]))
执行结果
第1轮 [ 1, 9, 3, 4, 2 ]
第2轮 [ 1, 2, 3, 4, 9 ]
第3轮 [ 1, 2, 3, 4, 9 ]
第4轮 [ 1, 2, 3, 4, 9 ]
🌰 [ 1, 2, 3, 4, 9 ]

插入

O(n2) O(1)

每次排一个数组项,以此方式构建最后的排序数组。假定第一项已经排序了。接着,它和第二项进行比较——第二项是应该待在原位还是插到第一项之前呢?这样,头两项就已正确排序,接着和第三项比较(它是该插入到第一、第二还是第三的位置呢),以此类推。

简单说:也是把小的往前排

js
function insertionSort(arr) {
  let n = arr.length
  let preIndex, current
  for (let i = 1; i < n; i++) {
    preIndex = i - 1
    current = arr[i]
    //如果不比current小就停止了,prevIndex不会再一直递减到0
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex]
      preIndex--
    }
    arr[preIndex + 1] = current

    console.log(`第${i}轮`, arr)
  }
  return arr
}
console.log('🌰', insertionSort([3, 9, 1, 4, 2]))
执行结果
第1轮 [ 3, 9, 1, 4, 2 ]
第2轮 [ 1, 3, 9, 4, 2 ]
第3轮 [ 1, 3, 4, 9, 2 ]
第4轮 [ 1, 2, 3, 4, 9 ]
🌰 [ 1, 2, 3, 4, 9 ]

归并

分治 递归 双指针 O(nlogn) O(n)

是将原始数组切分成较小的数组,直到每个小数组只有一个位置,接着将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。

js
function mergeSort(arr) {
  const rec = arr => {
    //分解
    const len = arr.length
    if (len === 1) {
      return arr
    }
    const mid = Math.floor(len / 2)
    const leftArr = arr.slice(0, mid)
    const rightArr = arr.slice(mid)

    console.log('分解', 'left:', leftArr, 'right:', rightArr)

    const orderLeftArr = rec(leftArr)
    const orderRightArr = rec(rightArr)

    //合并,双指针
    const res = []
    let li = 0 //left index
    let ri = 0 //right index
    while (li < orderLeftArr.length || ri < orderRightArr.length) {
      const lv = orderLeftArr[li] //left number
      const rv = orderRightArr[ri] //right number
      if ((lv && rv && lv < rv) || (lv && !rv)) {
        res.push(lv)
        li++
      } else if ((lv && rv && lv > rv) || (!lv && rv)) {
        res.push(rv)
        ri++
      } else {
        res.push(lv)
        res.push(rv)
        li++
        ri++
      }
    }
    console.log('🈴️ 合并', res)
    return res
  }

  return rec(arr)
}
console.log('🌰', mergeSort([3, 9, 1, 2, 4, 70, 34, 56]))
执行结果
分解 left: [ 3, 9, 1, 2 ] right: [ 4, 70, 34, 56 ]
分解 left: [ 3, 9 ] right: [ 1, 2 ]
分解 left: [ 3 ] right: [ 9 ]
🈴️ 合并 [ 3, 9 ]
分解 left: [ 1 ] right: [ 2 ]
🈴️ 合并 [ 1, 2 ]
🈴️ 合并 [ 1, 2, 3, 9 ]
分解 left: [ 4, 70 ] right: [ 34, 56 ]
分解 left: [ 4 ] right: [ 70 ]
🈴️ 合并 [ 4, 70 ]
分解 left: [ 34 ] right: [ 56 ]
🈴️ 合并 [ 34, 56 ]
🈴️ 合并 [ 4, 34, 56, 70 ]
🈴️ 合并 [
  1,  2,  3,  4,
  9, 34, 56, 70
]
🌰 [
  1,  2,  3,  4,
  9, 34, 56, 70
]

快速

分治 递归 O(nlogn) O(nlogn)

从数组中任意选择一个基准,所有比基准小的元素放在基准前面,比基准大的元素放在基准后面,递归的对基准前后的子数组再进行划分。

js
function quickSort(array) {
  const rec = arr => {
    const len = arr.length
    //!!!leftArr或rightArr可能为空
    if (len <= 1) {
      return arr
    }
    const mid = arr[0]
    const leftArr = []
    const rightArr = []

    for (let i = 1; i < len; i++) {
      const val = arr[i]
      if (val < mid) {
        leftArr.push(val)
      } else {
        rightArr.push(val)
      }
    }
    console.log(`基准: ${mid} 分解`, 'left:', leftArr, 'right:', rightArr)

    const res = [...rec(leftArr), mid, ...rec(rightArr)]
    console.log(`基准: ${mid} 合并`, res)

    return res
  }

  return rec(array)
}
console.log('🌰', quickSort([3, 9, 1, 2, 4, 70, 34, 56]))
执行结果
基准: 3 分解 left: [ 1, 2 ] right: [ 9, 4, 70, 34, 56 ]
基准: 1 分解 left: [] right: [ 2 ]
基准: 1 合并 [ 1, 2 ]
基准: 9 分解 left: [ 4 ] right: [ 70, 34, 56 ]
基准: 70 分解 left: [ 34, 56 ] right: []
基准: 34 分解 left: [] right: [ 56 ]
基准: 34 合并 [ 34, 56 ]
基准: 70 合并 [ 34, 56, 70 ]
基准: 9 合并 [ 4, 9, 34, 56, 70 ]
基准: 3 合并 [
  1,  2,  3,  4,
  9, 34, 56, 70
]
🌰 [
  1,  2,  3,  4,
  9, 34, 56, 70
]

最大堆可以得到一个升序的数组,最小堆可以得到一个降序的数组

用最大堆排序步骤:

  1. 用数组创建一个最大堆
  2. 堆顶堆尾(堆最后一个值)交换,将堆.legnth -1
  3. 将剩下的元素不断重复 前 2 步,直到堆大小为 1
js
function heapSort(nums) {
  let heapSize = nums.length
  const len = nums.length
  buildMaxHeap(heapSize)

  console.log('init maxHeap', [...nums])

  while (heapSize > 1) {
    swap(nums, 0, --heapSize)
    maxHeapify(0, heapSize)

    console.log('', heapSize, [...nums])
  }
  return nums

  // 自下而上构建一颗大顶堆
  function buildMaxHeap(heapSize) {
    for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
      maxHeapify(i, heapSize)
    }
  }
  // 从左向右,自上而下的调整节点
  function maxHeapify(i, heapSize) {
    let l = i * 2 + 1
    let r = i * 2 + 2
    let largest = i
    if (l < heapSize && nums[l] > nums[largest]) {
      largest = l
    }
    if (r < heapSize && nums[r] > nums[largest]) {
      largest = r
    }
    if (largest !== i) {
      swap(nums, i, largest)
      maxHeapify(largest, heapSize)
    }
  }
  //交换
  function swap(arr, i, j) {
    ;[arr[i], arr[j]] = [arr[j], arr[i]]
  }
}

console.log('🌰', heapSort([0, 1, 2, 2, 3, 7, 6, 1]))
执行结果
init maxHeap [
  7, 3, 6, 2,
  1, 2, 0, 1
]
 7 [
  6, 3, 2, 2,
  1, 1, 0, 7
]
 6 [
  3, 2, 2, 0,
  1, 1, 6, 7
]
 5 [
  2, 1, 2, 0,
  1, 3, 6, 7
]
 4 [
  2, 1, 1, 0,
  2, 3, 6, 7
]
 3 [
  1, 0, 1, 2,
  2, 3, 6, 7
]
 2 [
  1, 0, 1, 2,
  2, 3, 6, 7
]
 1 [
  0, 1, 1, 2,
  2, 3, 6, 7
]
🌰 [
  0, 1, 1, 2,
  2, 3, 6, 7
]

希尔(插入的改进版)【todo⌛️】

计数【todo⌛️】

桶【todo⌛️】

基数【todo⌛️】