几种排序算法速度对比,顺便对比一下 ES6 内置的 sort 方法

文章对比了冒泡排序、选择排序、插入排序、快速排序、归并排序以及ES6内置的sort方法,详细阐述了各种算法的时间复杂度、空间复杂度和稳定性,并通过测试显示在大数据量下快速排序和sort方法的表现最佳。测试结果显示,快速排序平均速度较快,而ES6的sort经过优化,性能更优。
摘要由CSDN通过智能技术生成

几种排序算法速度对比,顺便对比一下 ES6 内置的 sort 方法

一、先上代码

1. 冒泡排序

比较相邻的元素,如果第一个比第二个大,就交换它们两个,直到没有需要交换为止。即排序完成。

  • 时间复杂度:平均O(n²)  最好O(n)  最坏O(n²)
  • 空间复杂度: O(1)
  • 稳定性:稳定
/** 冒泡排序 */
function bubbleSort(arr) {
  const len = arr.length
  if (len >= 1) {
    for (let i = 0; i < len - 1; i++) {
      for (let j = 0; j < len - 1 - i; j++) {
        if (arr[j] > arr[j + 1]) {
          let temp = arr[j + 1]
          arr[j + 1] = arr[j]
          arr[j] = temp
        }
      }
    }
  }
  return arr
}

2. 选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

  • 时间复杂度:平均O(n²)   最好O(n²)  最坏O(n²)
  • 空间复杂度: O(1)
  • 稳定性:不稳定
/** 选择排序 */
function selectionSort(arr) {
  const len = arr.length
  let minIndex, temp
  for (let i = 0; i < len - 1; i++) {
    minIndex = i
    for (let j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        // 寻找最小的数
        minIndex = j
        // 将最小数的索引保存
      }
    }
    temp = arr[i]
    arr[i] = arr[minIndex]
    arr[minIndex] = temp
  }
  return arr
}

3. 插入排序

工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  • 时间复杂度:平均O(n²)   最好O(n)  最坏O(n²)
  • 空间复杂度: O(1)
  • 稳定性:稳定
/** 插入排序 */
function insertSort(arr) {
  const len = arr.length
  let preIndex, current
  for (let i = 1; i < len; i++) {
    preIndex = i - 1
    current = arr[i]
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex]
      preIndex--
    }
    arr[preIndex + 1] = current
  }
  return arr
}

4. 快速排序

使用分治的思想,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
具体算法描述如下:
(1) 选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot)
选择基准的方式 1.固定位置  2.随机选取基准  3.三数取中(左中右)
(2) 分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大。
(3) 递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。

  • 时间复杂度:平均O(nlogn)  最好O(nlogn)  最坏O(n²)
  • 空间复杂度: O(nlogn)
  • 稳定性:不稳定
/** 快速排序 */
function quickSort(arr, left, right) {
  let partitionIndex,
    lt = typeof left != "number" ? 0 : left,
    rt = typeof right != "number" ? arr.length - 1 : right
  if (lt < rt) {
    partitionIndex = partition(arr, lt, rt)
    quickSort(arr, lt, partitionIndex - 1)
    quickSort(arr, partitionIndex + 1, rt)
  }
  return arr
}

function partition(arr, left, right) {
  // 分区操作
  let index = left + 1
  for (let i = index; i <= right; i++) {
    if (arr[i] < arr[left]) {
      swap(arr, i, index)
      index++
    }
  }
  swap(arr, left, index - 1)
  return index - 1
}

function swap(arr, i, j) {
  let temp = arr[i]
  arr[i] = arr[j]
  arr[j] = temp
}

5. 归并排序

归并排序(Merge Sort): 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
具体算法描述如下:
1.把长度为n的输入序列分成两个长度为n/2的子序列;
2.对这两个子序列分别采用归并排序;
3.将两个排序好的子序列合并成一个最终的排序序列。

  • 时间复杂度:平均O(nlogn)  最好O(nlogn)  最坏O(nlogn)
  • 空间复杂度: O(n)
  • 稳定性:稳定
/** 归并排序 */
function mergeSort(arr) {
  // 采用自上而下的递归方法
  const len = arr.length
  if (len < 2) {
    return arr
  }
  let middle = Math.floor(len / 2),
    left = arr.slice(0, middle),
    right = arr.slice(middle)
  return merge(mergeSort(left), mergeSort(right))
}
function merge(left, right) {
  let result = []
  while (left.length > 0 && right.length > 0) {
    if (left[0] <= right[0]) {
      result.push(left.shift())
    } else {
      result.push(right.shift())
    }
  }
  while (left.length) {
    result.push(left.shift())
  }
  while (right.length) {
    result.push(right.shift())
  }
  return result
}

6. ES6 内置 sort 方法

NodeJS 使用的 v8 引擎 对 sort 做了特殊处理,对于长度 <= 10 的数组使用插入排序,长度 >10 的数组使用快速排序,并进行了很多优化,比我们常见的快速排序要复杂得多,不过核心算法还是快速排序。

/** ES6内置排序 */
function sort(arr) {
  return arr.sort((a, b) => a - b)
}

二、算法分析

排序算法平均时间最坏情况稳定度额外空间备注
冒泡O(n^2)O(n^2)稳定O(1)n 小的时候比较好
选择O(n^2)O(n^2)不稳定O(1)n 小的时候比较好
插入O(n^2)O(n^2)稳定O(1)大部分已经排序时比较好
快速O(nlogn)O(n^2)不稳定O(nlogn)n 大时候较好
归并O(nlogn)O(nlogn)稳定O(1)n 大时候较好

三、测试

环境:NodeJS

1. 测试函数代码

const isArray = Array.isArray
const isObject = (val) => val !== null && typeof val === "object"

/**
 * @param {Function} fn 测试函数
 * @param {Array} args 测试函数的参数数组
 * @returns
 */
const testLog = (fn, args) => {
  if (isArray(args)) {
    // 克隆参数,防止参数被函数修改而影响后续测试
    args = args.map((item) => (isObject(item) ? JSON.parse(JSON.stringify(item)) : item))
  } else {
    return "参数格式错误"
  }

  const start_time = process.hrtime()

  const result = fn.apply(this, args) // 执行函数

  const end_time = process.hrtime(start_time)
  const delta_time = Math.floor(end_time[0] * 1000 * 1000 + end_time[1] / 1000)

  console.log(result, "------", fn.name, "------", delta_time, "ms")
  return { fn: fn.name, time_ms: Math.floor(delta_time / 1000), time_μs: delta_time }
}

/**
 * @param {Function} fnArray 由多个测试函数组成的数组
 * @param {Array} args 测试函数的参数数组
 * @returns
 */
const testQueue = (fnArray, args) => {
  let results = []
  fnArray.forEach((fn) => results.push(testLog(fn, args)))
  console.table(results.sort((a, b) => a.time_μs - b.time_μs))
}

module.exports = { testLog, testQueue }

2. 测试执行代码

/** 引入测试函数 */
const { testQueue } = require("../testLog.js")
const randomArray = require("../randomArray.js")
const rndArr = randomArray(1, 100, 100000)
testQueue(
  [bubbleSort, selectionSort, insertSort, quickSort, mergeSort, sort],
  [rndArr]
)

3. 测试结果

数组长度 100000 测试结果如下:

排序方法函数名耗时 (ms)
sort 方法sort16
快速排序quickSort36
归并排序mergeSort544
插入排序insertSort1619
选择排序selectionSort3662
冒泡排序bubbleSort11619

排序算法性能测试输出截图
备注:截图为执行测试代码输出的结果,图中 selectionSort2 为选择排序优化算法,quickSort2 为快速排序极简算法,本文省略。

四、总结

以上几种排序算法最快的是快速排序。当数组足够大时,差距非常明显。
当然,得益于强大的性能优化,ES6 的 sort 方法速度明显更快。
以上测试在 NodeJS 环境中。NodeJS 使用的 v8 引擎 对 sort 做了特殊处理,对于长度 <= 10 的数组使用插入排序,长度 >10 的数组使用快速排序,并进行了很多优化,比我们常见的快速排序要复杂得多,不过核心算法还是快速排序。想要深入了解可以看看 v8 源码

本文参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值