三大排序算法的对比与实现(归并,快排,堆排)

132 阅读2分钟

复习一下三大算法的对比与实现

image.png

三大算法对比:

时间空间稳定性
归并排序O(n*logn)O(n)
快速排序(随机)O(n*logn)O(logn)×
堆排序O(n*logn)O(1)×

它们各自的优势:

  • 归并:稳定
  • 快排:实际使用时间最快(实践证明原因在于常数项比较低)
  • 堆排:空间复杂度低

三大算法的实现:

归并排序

//归并排序
const mergeSort = (nums) => {
  if (!nums.length) return nums
  const process = (nums, l, r) => {
    if (l < r) {
      let m = l + ((r - l) >> 1)
      process(nums, l, m)
      process(nums, m + 1, r)
      merge(nums, l, m, r)
    }
  }
  const merge = (nums, l, m, r) => {
    let temp = []
    let p1 = l
    let p2 = m + 1

    while (p1 <= m && p2 <= r) {
      temp.push(nums[p1] < nums[p2] ? nums[p1++] : nums[p2++])
    }

    while (p1 <= m) {
      temp.push(nums[p1++])
    }

    while (p2 <= r) {
      temp.push(nums[p2++])
    }

    for (let i = 0, len = temp.length; i < len; i++) {
      nums[l + i] = temp[i]
    }
  }
  process(nums, 0, nums.length - 1)
  return nums
}

快速排序

定点取数的快排时间复杂度都能举出最差情况,所以时间复杂度为O(n²)
随机取数的快排从最差情况O(n²)到最优情况O(nlogn)等概率,计算数学期望时间复杂度为O(nlogn)

const quickSort = (nums) => {
  const swap = (nums, a, b) => {
    let temp = nums[a]
    nums[a] = nums[b]
    nums[b] = temp
  }

  const quickSort = (nums, l, r) => {
    if (l < r) {
      //随机选择一个数与最后一位交换
      swap(nums, l + ((Math.random() * (r - l + 1)) | 0), r)

      //p是返回回来的等于区
      let p = partition(nums, l, r)

      //把最低位到等于区前面一位进行quickSort
      quickSort(nums, l, p[0] - 1)

      //把等于区后面一位到最高位进行quickSort
      quickSort(nums, p[1] + 1, r)
    }
  }

  const partition = (nums, l, r) => {
    let less = l - 1
    let more = r
    while (l !== more) {
      if (nums[l] < nums[r]) {
        swap(nums, l++, ++less)
      } else if (nums[l] > nums[r]) {
        swap(nums, l, --more)
      } else {
        l++
      }
    }

    swap(nums, more, r)
    return [less + 1, more]
  }

  quickSort(nums, 0, nums.length - 1)
  return nums
}

堆排序

//堆排序
const heapSort = (nums) => {
  const swap = (nums, a, b) => {
    let t = nums[a]
    nums[a] = nums[b]
    nums[b] = t
  }

  const heapInsert = (nums, i) => {
    while (i > 0 && nums[i] > nums[(i - 1) >> 1]) {
      swap(nums, i, (i - 1) >> 1)
      i = (i - 1) >> 1
    }
  }

  const heapify = (nums, i, heapSize) => {
    while (i * 2 + 1 < heapSize) {
      let largest = nums[i * 2 + 1]
      let index = i * 2 + 1
      if (i * 2 + 2 < heapSize) {
        largest = nums[i * 2 + 1] > nums[i * 2 + 2] ? nums[i * 2 + 1] : nums[i * 2 + 2]
        index = nums[i * 2 + 1] > nums[i * 2 + 2] ? i * 2 + 1 : i * 2 + 2
      }
      if (nums[i] < largest) {
        swap(nums, i, index)
        i = index
      } else {
        break
      }
    }
  }

  let heapSize = 0
  for (let i = 0, len = nums.length; i < len; i++, heapSize++) {
    heapInsert(nums, i)
  }

  for (let i = 0, len = nums.length; i < len; i++) {
    swap(nums, 0, --heapSize)
    heapify(nums, 0, heapSize)
  }
  return nums
}

只用heapify可达到优化时间的效果,但时间复杂度不变
优化后:

//堆排序
const heapSort = (nums) => {
  const swap = (nums, a, b) => {
    let t = nums[a]
    nums[a] = nums[b]
    nums[b] = t
  }

  const heapInsert = (nums, i) => {
    while (i > 0 && nums[i] > nums[(i - 1) >> 1]) {
      swap(nums, i, (i - 1) >> 1)
      i = (i - 1) >> 1
    }
  }

  const heapify = (nums, i, heapSize) => {
    while (i * 2 + 1 < heapSize) {
      let largest = nums[i * 2 + 1]
      let index = i * 2 + 1
      if (i * 2 + 2 < heapSize) {
        largest = nums[i * 2 + 1] > nums[i * 2 + 2] ? nums[i * 2 + 1] : nums[i * 2 + 2]
        index = nums[i * 2 + 1] > nums[i * 2 + 2] ? i * 2 + 1 : i * 2 + 2
      }
      if (nums[i] < largest) {
        swap(nums, i, index)
        i = index
      } else {
        break
      }
    }
  }

  // let heapSize = 0
  // for (let i = 0, len = nums.length; i < len; i++, heapSize++) {
  //   heapInsert(nums, i)
  // }
  //优化后这部分的时间复杂度从O(NlogN)加速到O(N),但是下面那个循环的时间复杂度还是O(NlogN),
  //所以总体时间复杂度不变
  let heapSize = nums.length
  for (let i = nums.length - 1; i >= 0; i--) {
    heapify(nums, i, heapSize)
  }

  for (let i = 0, len = nums.length; i < len; i++) {
    swap(nums, 0, --heapSize)
    heapify(nums, 0, heapSize)
  }
  return nums
}