常用的四种排序算法

642 阅读4分钟

背景: 前段时间, 某前端校招二面上来就让写快排, 当时就懵了, 想着很久以前看过的, 忘了, 血亏. 最后是挂了, 可能第一印象就不行. 现在记录一下, 也避免大家遇到这种情况

插入排序

思想

二分查找并插入元素

  1. 对于有序数组, 插入一个值使得数组仍然有序
  2. 选择有序数组中间元素, 将有序数组分成前后两部分
  3. 比较待插入元素与中间元素
  4. 待插入元素大于中间元素则插入后半部分, 否则插入前半部分
  5. 插入前/后半部分的方式重复步骤2, 3, 4

排序

  1. 有一个待排序数组, 和一个作为容器的空数组
  2. 从待排序数组中选择一个元素插入到容器数组, 并保持其有序

代码

const numbers = [2, 4, 6, 12, 43, 12, 56, 1]

function insertionSort(arr) {
  function binInsert(val, arr) {
    if (arr.length === 0) {
      return [val]
    } else if (arr.length === 1) {
      return val > arr[0] ? [arr[0], val] : [val, arr[0]]
    } else {
      const middle = Math.floor(arr.length / 2)
      return val > arr[middle] ?
        [...arr.slice(0, middle), ...binInsert(val, arr.slice(middle))] :
        [...binInsert(val, arr.slice(0, middle)), ...arr.slice(middle)]
    }
  }
  return arr.reduce((buf, val) => binInsert(val, buf), [])
}

console.log(insertionSort(numbers))

归并排序

思想

先分解, 后归并

归并

  1. 有两个有序数组(从小到大), 和一个作为容器的空数组
  2. 各从两数组头部弹出一个元素
  3. 比较两个弹出元素大小, 较小的元素从容器尾部推入, 较大元素放回原数组
  4. 重复步骤2, 3, 直至两个有序数组之一为空
  5. 当两个有序数组之一为空时, 将非空数组的头部与容器数组的尾部连接成一个新数组
  6. 新的数组即为排序好的数组

分解

  1. 将待排序数组分为两个数组
  2. 将分出来的两个数组再各自分成两个数组
  3. 重复步骤2, 直至分成的数组只有两个元素或更少
  4. 对于只有两个元素的数组, 通过比较即可排序
  5. 归并各组分解出来的数组

代码

const numbers = [2, 4, 6, 12, 43, 12, 56, 1]

function mergeSort(arr) {
  if (arr.length < 2) {
    return arr
  } else if (arr.length === 2) {
    return arr[0] > arr[1] ?
      [arr[1], arr[0]] :
      [arr[0], arr[1]]
  }
  else {
    const buf = []
    const middle = Math.floor(arr.length / 2)
    const part0 = mergeSort(arr.slice(0, middle))
    const part1 = mergeSort(arr.slice(middle))
    for (let i = 0, j = 0; i < part0.length || j < part1.length;) {
      const val0 = part0[i]
      const val1 = part1[j]
      if (val0 && val1) {
        if (val0 < val1) {
          buf.push(val0)
          i++
        } else {
          buf.push(val1)
          j++
        }
      } else if (val0) {
        buf.push(val0)
        i++
      } else if (val1) {
        buf.push(val1)
        j++
      }
    }
    return buf
  }
}

console.log(mergeSort(numbers))

冒泡排序

思想

  1. 有一个待排序数组, 一个空的数组作为容器
  2. 对于待排序数组, 从头至尾, 两两比较元素, 交换位置(小的在前)
  3. 步骤2使得待排序数组中最大元素位于最后
  4. 最大元素从容器数组头部推入
  5. 最大元素之前的所有元素构成一个新的待排序数组
  6. 重复步骤2, 3, 4, 5, 直至新的待排序数组只含一个元素
  7. 将这个新的待排序数组的唯一元素从容器数组头部推入

代码

const numbers = [2, 4, 6, 12, 43, 12, 56, 1]

function bubbleSort(arr) {
  if (arr.length < 2) {
    return arr
  } else {
    for (let i = 1; i < arr.length; i++) {
      if (arr[i] < arr[i - 1])
        [arr[i - 1], arr[i]] = [arr[i], arr[i - 1]]
    }
    return [...bubbleSort(arr.slice(0, -1)), ...arr.slice(-1)]
  }
}

console.log(bubbleSort(numbers))

快速排序

思想

  1. 有一个待排序数组
  2. 从待排序数组中选择一个元素作为基准值
  3. 遍历待排序数组, 小于基准值的元素构成一个数组(less), 大于基准值的元素也构成一个数组(greater)
  4. 如果lessgreater都已经排序好了, 连接less, 基准值, greater即得到最终排序好的数组
  5. 对待排序数组分解出来的lessgreater分别应用步骤2, 3, 4

代码

const numbers = [2, 4, 6, 12, 43, 12, 56, 1]

function quickSort(arr) {
  if (arr.length < 2) {
    return arr
  } else {
    const pivot = arr[0], less = [], greater = []
    for (let i = 1; i < arr.length; i++) {
      const num = arr[i];
      num > pivot ? greater.push(num) : less.push(num)
    }
    return [...quickSort(less), pivot, ...quickSort(greater)]
  }
}

console.log(quickSort(numbers))

睡眠排序

四大xxxx有五个显然是常识

思想

  1. 有一个待排序数组, 一个空的数组作为容器
  2. 对于待排序数组每个元素, 使用元素的值作为时间, 设置一个相应的定时器
  3. 定时器归零时, 将对应元素从容器数组尾部推入

代码

const numbers = [2, 4, 6, 12, 43, 12, 56, 1]

async function sleepSort(arr) {
  const sorted = []
  await Promise.all(numbers.map(t => new Promise((res) => {
    const timer = setTimeout(() => {
      clearTimeout(timer)
      sorted.push(t)
      res()
    }, t)
  })))
  return sorted
}

sleepSort(numbers).then(sorted => console.log(sorted))