Skip to content

排序算法汇总

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 希尔排序
  • 归并排序
  • 快速排序
  • 堆排序

冒泡排序

冒泡排序

核心思路:比较“大”的元素向后冒泡。

时间复杂度:O(n2)

缺点:频繁交换元素,更多的内存读写操作,影响执行效率。

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

const bubbleSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)
  // 优化1:如果过程中不存在交换的状况,说明已经排序好了
  let isSortCompleted = true
  // 优化2:记录最后一次交换的index
  let lastSwapIndex = arr.length - 1
  // 遍历的边界
  let sortBorder = arr.length - 1
  for (let i = 0;i < arr.length;i++) {
    for (let j = 0;j < sortBorder;j++) {
      if (!compare(arr[j], arr[j + 1])) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
        isSortCompleted = false
        lastSwapIndex = j
      }
    }
    sortBorder = lastSwapIndex
    if (isSortCompleted) break
  }
  return arr
}

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

const bubbleSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)
  // 优化1:如果过程中不存在交换的状况,说明已经排序好了
  let isSortCompleted = true
  // 优化2:记录最后一次交换的index
  let lastSwapIndex = arr.length - 1
  // 遍历的边界
  let sortBorder = arr.length - 1
  for (let i = 0;i < arr.length;i++) {
    for (let j = 0;j < sortBorder;j++) {
      if (!compare(arr[j], arr[j + 1])) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
        isSortCompleted = false
        lastSwapIndex = j
      }
    }
    sortBorder = lastSwapIndex
    if (isSortCompleted) break
  }
  return arr
}

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

选择排序

选择排序

核心思路:依次选择比较“大”的元素,挨个往前面放。

时间复杂度:O(n2)

缺点:不稳定排序,值相同的元素排序后可能顺序发生了变化。

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

const selectSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)

  for (let i = 0;i < arr.length;i++) {
    for (let j = i + 1;j < arr.length;j++) {
      if (!compare(arr[i], arr[j])) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
  return arr
}

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

const selectSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)

  for (let i = 0;i < arr.length;i++) {
    for (let j = i + 1;j < arr.length;j++) {
      if (!compare(arr[i], arr[j])) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
  return arr
}

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

插入排序

插入排序

核心思路:对比当前元素和前面的元素,如果不符合情况则将前面元素依次后移一位,直到找到正确的位置进行插入。

时间复杂度:最坏情况下O(n2)

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

const insertSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)

  for (let i = 1;i < arr.length;i++) {
    // 需要插入的元素
    const insertValue = arr[i]
    let j = i - 1
    // 与前面元素进行判断,不满足的话依次将元素后移
    while (j >= 0 && !compare(arr[j], insertValue)) {
      arr[j + 1] = arr[j]
      j--
    }
    arr[j + 1] = insertValue
  }
  return arr
}

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

const insertSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)

  for (let i = 1;i < arr.length;i++) {
    // 需要插入的元素
    const insertValue = arr[i]
    let j = i - 1
    // 与前面元素进行判断,不满足的话依次将元素后移
    while (j >= 0 && !compare(arr[j], insertValue)) {
      arr[j + 1] = arr[j]
      j--
    }
    arr[j + 1] = insertValue
  }
  return arr
}

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

希尔排序

希尔排序

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

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

const shellSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)

  let hill = 0
  // 计算希尔间间隔序列,主要目的是使得计算量尽可能的小
  while (hill < arr.length / 3) {
    hill = hill * 3 + 1
  }

  while (hill >= 1) {
    for (let i = hill;i < arr.length;i++) {
      // 需要插入的元素
      const insertValue = arr[i]
      // 与前面元素进行判断,不满足的话依次将元素后移
      let j = i - hill
      for (;j >= 0 && !compare(arr[j], insertValue);j = j - hill) {
        arr[j + hill] = arr[j]
      }
      arr[j + hill] = insertValue
    }

    hill = (hill - 1) / 3
  }


  return arr
}

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

const shellSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)

  let hill = 0
  // 计算希尔间间隔序列,主要目的是使得计算量尽可能的小
  while (hill < arr.length / 3) {
    hill = hill * 3 + 1
  }

  while (hill >= 1) {
    for (let i = hill;i < arr.length;i++) {
      // 需要插入的元素
      const insertValue = arr[i]
      // 与前面元素进行判断,不满足的话依次将元素后移
      let j = i - hill
      for (;j >= 0 && !compare(arr[j], insertValue);j = j - hill) {
        arr[j + hill] = arr[j]
      }
      arr[j + hill] = insertValue
    }

    hill = (hill - 1) / 3
  }


  return arr
}

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

归并排序

归并排序

核心思想:将数组递归平分,先将最小的数组排序,然后将这些数组进行合并。由于小数组已经存在顺序了,所以合并的时候只需要遍历一遍即可。

时间复杂度:递归过程为logn,每一层的排序为n,因此复杂度为O(nlogn)

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

const merge = (arr, start, end, compare) => {
  // 如果只有一个元素,返回
  if (end === start) return
  // 如果只有两个元素,进行交换
  if (end - start === 1) {
    if (!compare(arr[start], arr[end])) {
      [arr[start], arr[end]] = [arr[end], arr[start]]
    }
    return
  }
  const middle = Math.floor((end + start) / 2)
  merge(arr, start, middle, compare)
  merge(arr, middle + 1, end, compare)

  let leftIndex = 0
  let rightIndex = 0
  let tempArray = []
  // 遍历这一整段数组
  for (let i = start;i <= end;i++) {
    // 左侧数组的值
    const leftValue = arr[start + leftIndex]
    // 右侧数组的值
    const rightValue = arr[middle + 1 + rightIndex]
    if (leftIndex <= middle - start && compare(leftValue, rightValue)) {
      tempArray.push(leftValue)
      leftIndex += 1
    } else {
      tempArray.push(rightValue)
      rightIndex += 1
    }
  }
  // 更改数组的原始顺序
  for (let i = 0;i < tempArray.length;i++) {
    arr[start + i] = tempArray[i]
  }
}

const mergeSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)
  merge(arr, 0, arr.length - 1, compare)
  return arr
}

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

const merge = (arr, start, end, compare) => {
  // 如果只有一个元素,返回
  if (end === start) return
  // 如果只有两个元素,进行交换
  if (end - start === 1) {
    if (!compare(arr[start], arr[end])) {
      [arr[start], arr[end]] = [arr[end], arr[start]]
    }
    return
  }
  const middle = Math.floor((end + start) / 2)
  merge(arr, start, middle, compare)
  merge(arr, middle + 1, end, compare)

  let leftIndex = 0
  let rightIndex = 0
  let tempArray = []
  // 遍历这一整段数组
  for (let i = start;i <= end;i++) {
    // 左侧数组的值
    const leftValue = arr[start + leftIndex]
    // 右侧数组的值
    const rightValue = arr[middle + 1 + rightIndex]
    if (leftIndex <= middle - start && compare(leftValue, rightValue)) {
      tempArray.push(leftValue)
      leftIndex += 1
    } else {
      tempArray.push(rightValue)
      rightIndex += 1
    }
  }
  // 更改数组的原始顺序
  for (let i = 0;i < tempArray.length;i++) {
    arr[start + i] = tempArray[i]
  }
}

const mergeSort = (arr, compare) => {
  compare = compare || ((a, b) => a < b)
  merge(arr, 0, arr.length - 1, compare)
  return arr
}

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

快速排序

快速排序

核心思路:选取基准值,遍历数组,小于基准值的放入左侧,大于基准值的放入右侧,递归执行,直到最小的数组均完成排序。

时间复杂度:最坏情况下O(n2),最好情况下O(nlogn)

javascript
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]
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)

javascript
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]
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]