深入浅出 JavaScript 的Array.prototype.sort 排序算法

reddog 发布于3年前

本文要解决的问题

1、找出 Array.prototype.sort 使用的什么排序算法

2、用一种直观的方式展示 Array.prototype.sort 的时间复杂度,看看它有多快?

3、实际开发中要注意的问题

Array.prototype.sort 各浏览器的算法实现

ECMAScript 5.1

ECMAScript 6.0

ECMAScript 草案

看完上面三个规范中 Array.prototype.sort 部分,我们会发现 ECMAScript 不同版本规范对 Array.prototype.sort 的定义中没有要求用什么样的排序方式实现 sort() 方法,也没有要求是否要采用稳定算法(下文会解释什么是稳定算法)。

因此各浏览器都给出自己的实现方式:

表格内容部分来自于维基百科

浏览器 使用的 JavaScript 引擎 排序算法 源码地址
Google Chrome V8 插入排序和快速排序 sort 源码实现
Mozilla Firefox SpiderMonkey 归并排序 sort 源码实现
Safari Nitro(JavaScriptCore ) 归并排序和桶排序 sort 源码实现
Microsoft Edge 和 IE(9+) Chakra 快速排序 sort 源码实现

源码分析

V8引擎的一段注释

// In-place QuickSort algorithm.
// For short (length <= 10) arrays, insertion sort is used for efficiency.

Google Chromesort 做了特殊处理,对于长度 <= 10 的数组使用的是插入排序(稳定排序算法) ,>10 的数组使用的是快速排序。快速排序是不稳定的排序算法。

但是很明显比我们常见的快速排序要复杂得多,不过核心算法还是快速排序。算法复杂的原因在于v8出于性能考虑进行了很多优化。

再看 safari Nitro 引擎的一段代码

if (typeof comparator == "function")
  comparatorSort(array, length, comparator);
else if (comparator === null || comparator === @undefined)
  stringSort(array, length);

  省略....

function stringSort(array, length)
{
  var valueCount = compact(array, length);

  var strings = @newArrayWithSize(valueCount);
  for (var i = 0; i < valueCount; ++i)
      strings[i] = { string: @toString(array[i]), value: array[i] };

  bucketSort(array, 0, strings, 0);
}

  省略....

function comparatorSort(array, length, comparator)
{
  var valueCount = compact(array, length);
  mergeSort(array, valueCount, comparator);
}

默认使用的桶排序,如果 sort 传入的自定义函数作为参数,就是用归并排序(稳定排序算法)

Firefox 源码就不贴了,上面的表格有源码地址,使用的稳定排序算法 — 归并算法。
Microsoft EdgeIE(9+) 使用的不稳定排序算法 - 快速排序。
但是 IE(6、7、8)使用的稳定算法。

各种算法的对比

排序类型 平均情况 最好情况 最坏情况 辅助空间 稳定性
快速排序 O(nlogn) O(nlogn) O(n²) O(nlogn) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
插入排序 O(n²) O(n) O(n²) O(1) 稳定
桶排序 O(n+k) O(n+k) O(n²) O(n+k) (不)稳定

注: 桶排序的稳定性取决于桶内排序的稳定性, 因此其稳定性不确定。

算法时间复杂度

在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,
进而分析T(n)随着n的变化情况并确定T(n)的数量级。
算法的时间复杂度,也就是算法的时间度量,记作:T(n)=O(f(n))。
它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,
称作算法的时间复杂度,简称为时间复杂度。
其中f(n)是问题规模n的某个函数。

常用的时间复杂度所耗费的时间从小到大依次是

O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^n) < O(n!) < O(n^n)

图片来源

算法的时间复杂度与运行时间有一些常见的比例关系 查看图表来源

复杂度 10 20 50 100 1,000 10,000 100,000
O(1) < 1s < 1s < 1s < 1s < 1s < 1s < 1s
O(log(n)) < 1s < 1s < 1s < 1s < 1s < 1s < 1s
O(n) < 1s < 1s < 1s < 1s < 1s < 1s < 1s
O(n*log(n)) < 1s < 1s < 1s < 1s < 1s < 1s < 1s
O(n²) < 1s < 1s < 1s < 1s < 1s 2 s 3-4 min
O(n³) < 1s < 1s < 1s < 1s 20 s 5 hours 231 days
O(2^n) < 1s < 1s 260 days hangs hangs hangs hangs
O(n!) < 1s hangs hangs hangs hangs hangs hangs
O(n^n) 3-4 min hangs hangs hangs hangs hangs hangs

维基百科关于算法稳定性的解释

当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。

(4, 1)  (3, 1)  (3, 7)(5, 6)

在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:

(3, 1)  (3, 7)  (4, 1)  (5, 6)  (维持次序)
(3, 7)  (3, 1)  (4, 1)  (5, 6)  (次序被改变)

想看自己浏览器排序算法的稳定性? 点我

有多快哪

我们先通过这个在线网站大体测试一下

浏览器的实现不同有什么影响

排序算法不稳定有什么影响

举个例子:

某市的机动车牌照拍卖系统,最终中标的规则为:

1、按价格进行倒排序;

2、相同价格则按照竞标顺位(即价格提交时间)进行正排序。

排序若是在前端进行,那么采用快速排序的浏览器中显示的中标者很可能是不符合预期的。

解决办法

Array.prototype.sort 在不同浏览器中的差异和解决办法

大体的思路就是,自己写一个稳定的排序函数,以保持各浏览器的一致性。

扩展阅读

快速排序(Quicksort)的Javascript实现

JS中可能用得到的全部的排序算法

7 种常用的排序算法-可视化

深入了解javascript的sort方法

JavaScript 排序算法汇总

JS中可能用得到的全部的排序算法

参考文档

聊聊前端排序的那些事

排序算法

JavaScript排序算法汇总

查看原文: 深入浅出 JavaScript 的Array.prototype.sort 排序算法

  • whitefish
  • silverleopard
  • beautifulostrich
  • yellowduck
  • heavyrabbit
  • yellowgorilla
  • purplepanda