冒泡排序

核心思路:比较“大”的元素向后冒泡。 时间复杂度:O(n2) 缺点:频繁交换元素,更多的内存读写操作,影响执行效率。

  1. const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]
  2. const bubbleSort = (arr, compare) => {
  3. compare = compare || ((a, b) => a < b)
  4. // 优化1:如果过程中不存在交换的状况,说明已经排序好了
  5. let isSortCompleted = true
  6. // 优化2:记录最后一次交换的index
  7. let lastSwapIndex = arr.length - 1
  8. // 遍历的边界
  9. let sortBorder = arr.length - 1
  10. for (let i = 0;i < arr.length;i++) {
  11. for (let j = 0;j < sortBorder;j++) {
  12. if (!compare(arr[j], arr[j + 1])) {
  13. [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
  14. isSortCompleted = false
  15. lastSwapIndex = j
  16. }
  17. }
  18. sortBorder = lastSwapIndex
  19. if (isSortCompleted) break
  20. }
  21. return arr
  22. }
  23. console.log(bubbleSort(arr, (a, b) => a < b))
  24. // [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]

选择排序

核心思路:依次选择比较“大”的元素,挨个往前面放。 时间复杂度:O(n2) 缺点:不稳定排序,值相同的元素排序后可能顺序发生了变化。

  1. const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]
  2. const selectSort = (arr, compare) => {
  3. compare = compare || ((a, b) => a < b)
  4. for (let i = 0;i < arr.length;i++) {
  5. for (let j = i + 1;j < arr.length;j++) {
  6. if (!compare(arr[i], arr[j])) {
  7. [arr[i], arr[j]] = [arr[j], arr[i]]
  8. }
  9. }
  10. }
  11. return arr
  12. }
  13. console.log(selectSort(arr, (a, b) => a < b))
  14. // [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]

插入排序

核心思路:对比当前元素和前面的元素,如果不符合情况则将前面元素依次后移一位,直到找到正确的位置进行插入。 时间复杂度:最坏情况下O(n2)

  1. const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]
  2. const insertSort = (arr, compare) => {
  3. compare = compare || ((a, b) => a < b)
  4. for (let i = 1;i < arr.length;i++) {
  5. // 需要插入的元素
  6. const insertValue = arr[i]
  7. let j = i - 1
  8. // 与前面元素进行判断,不满足的话依次将元素后移
  9. while (j >= 0 && !compare(arr[j], insertValue)) {
  10. arr[j + 1] = arr[j]
  11. j--
  12. }
  13. arr[j + 1] = insertValue
  14. }
  15. return arr
  16. }
  17. console.log(insertSort(arr, (a, b) => a < b))
  18. // [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]

希尔排序

核心思路:相当于在插入排序之前做了一个预处理,让数据变得更加“有序”。这样在插入排序时,就会进行更少的查找和对比了。这里的”预处理“过程是由大间隔的插入排序到小间隔的插入排序的过程。

  1. const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]
  2. const shellSort = (arr, compare) => {
  3. compare = compare || ((a, b) => a < b)
  4. let hill = 0
  5. // 计算希尔间间隔序列,主要目的是使得计算量尽可能的小
  6. while (hill < arr.length / 3) {
  7. hill = hill * 3 + 1
  8. }
  9. while (hill >= 1) {
  10. for (let i = hill;i < arr.length;i++) {
  11. // 需要插入的元素
  12. const insertValue = arr[i]
  13. // 与前面元素进行判断,不满足的话依次将元素后移
  14. let j = i - hill
  15. for (;j >= 0 && !compare(arr[j], insertValue);j = j - hill) {
  16. arr[j + hill] = arr[j]
  17. }
  18. arr[j + hill] = insertValue
  19. }
  20. hill = (hill - 1) / 3
  21. }
  22. return arr
  23. }
  24. console.log(shellSort(arr, (a, b) => a < b))
  25. // [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]

归并排序

核心思想:将数组递归平分,先将最小的数组排序,然后将这些数组进行合并。由于小数组已经存在顺序了,所以合并的时候只需要遍历一遍即可。 时间复杂度:递归过程为logn,每一层的排序为n,因此复杂度为O(nlogn)

  1. const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]
  2. const merge = (arr, start, end, compare) => {
  3. // 如果只有一个元素,返回
  4. if (end === start) return
  5. // 如果只有两个元素,进行交换
  6. if (end - start === 1) {
  7. if (!compare(arr[start], arr[end])) {
  8. [arr[start], arr[end]] = [arr[end], arr[start]]
  9. }
  10. return
  11. }
  12. const middle = Math.floor((end + start) / 2)
  13. merge(arr, start, middle, compare)
  14. merge(arr, middle + 1, end, compare)
  15. let leftIndex = 0
  16. let rightIndex = 0
  17. let tempArray = []
  18. // 遍历这一整段数组
  19. for (let i = start;i <= end;i++) {
  20. // 左侧数组的值
  21. const leftValue = arr[start + leftIndex]
  22. // 右侧数组的值
  23. const rightValue = arr[middle + 1 + rightIndex]
  24. if (leftIndex <= middle - start && compare(leftValue, rightValue)) {
  25. tempArray.push(leftValue)
  26. leftIndex += 1
  27. } else {
  28. tempArray.push(rightValue)
  29. rightIndex += 1
  30. }
  31. }
  32. // 更改数组的原始顺序
  33. for (let i = 0;i < tempArray.length;i++) {
  34. arr[start + i] = tempArray[i]
  35. }
  36. }
  37. const mergeSort = (arr, compare) => {
  38. compare = compare || ((a, b) => a < b)
  39. merge(arr, 0, arr.length - 1, compare)
  40. return arr
  41. }
  42. console.log(mergeSort(arr, (a, b) => a < b))
  43. // [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]

快速排序

核心思路:选取基准值,遍历数组,小于基准值的放入左侧,大于基准值的放入右侧,递归执行,直到最小的数组均完成排序。 时间复杂度:最坏情况下O(n2),最好情况下O(nlogn)

const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]

const quickSort = (arr, compare) => {
  if (arr.length <= 1) return arr
  let lesser = []
  let greater = []
  let pivot = arr[0]
  for (let i = 1;i < arr.length;i++) {
    if (compare(arr[i], pivot)) {
      lesser.push(arr[i])
    } else {
      greater.push(arr[i])
    }
  }
  const sortLesser = quickSort(lesser, compare)
  const sortGreater = quickSort(greater, compare)
  return sortLesser.concat(pivot, sortGreater)
}

console.log(quickSort(arr, (a, b) => a < b))
// [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]

堆排序

核心思路:先将无序数组构建成堆,然后遍历取堆的头部数据放到数组末尾。 时间复杂度:O(nlogn)

const arr = [2, 3, 12, 4, 2, 52, 23, 42, 10, 231, 13]

const heapDown = (arr, parentIndex, length, compare) => {
  let leftChildIndex = 2 * parentIndex + 1
  let rightChildIndex = 2 * parentIndex + 2

  if (leftChildIndex >= length) {
    // 左index超出界限
    return
  }
  // 找出左右节点的最大值
  // 注意这里的 compare是取反的,因为后续排序时,最大值放到最后面了
  let maxValueIndex = leftChildIndex
  if (rightChildIndex < length && compare(arr[maxValueIndex], arr[rightChildIndex])) {
    maxValueIndex = rightChildIndex
  }

  // 如果 parentValue 符合要求,则跳过。
  if (compare(arr[parentIndex], arr[maxValueIndex])) {
    // 交换位置,然后继续向下调整
    [arr[parentIndex], arr[maxValueIndex]] = [arr[maxValueIndex], arr[parentIndex]]
    heapDown(arr, maxValueIndex, length, compare)
  }
}

const heapSort = (arr, compare) => {
  const middleIndex = Math.floor(arr.length / 2 - 1)
  // 从 n/2-1 长度开始遍历,依次将父元素下层。
  for (let i = middleIndex;i >= 0;i--) {
    heapDown(arr, i, arr.length, compare)
  }

  // 遍历 arr,取出头部数据
  for (let i = arr.length - 1;i >= 0;i--) {
    // 每次都取第一个,然后与最后一个元素交换
    [arr[0], arr[i]] = [arr[i], arr[0]]
    // 交换完后进行调整
    heapDown(arr, 0, i, compare)
  }

  return arr
}

console.log(heapSort(arr, (a, b) => a < b))
// [2, 2, 3, 4, 10, 12, 13, 23, 42, 52, 231]