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

本文要解决的问题

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) (次序被改变)

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

各种排序算法实现有多快?

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

对一个有 10000 个元素的数组,快速排序 > 归并排序 >>> 插入排序
而且插入排序大于 1s 了。

对于一个只有 10 个元素的数组,插入排序 > 快速排序
这也说明了为什么 chrome 在小于等于 10 个元素的小数组使用插入排序的原因了。

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

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

举个例子:

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

1、按价格进行倒排序;

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

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

解决办法

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

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

工具

1、在线排序算法对比网站
2、排序算法视觉图

扩展阅读

1、快速排序(Quicksort)的Javascript实现
2、JS中可能用得到的全部的排序算法
3、7 种常用的排序算法-可视化
4、深入了解javascript的sort方法
5、JavaScript 排序算法汇总

参考文档

聊聊前端排序的那些事
排序算法
JavaScript排序算法汇总


小猿大圣的技术之路
小猿变大圣,小工到专家,记录技术点滴。

喵星人铲屎官、『海贼王海米答题』小程序开发者

1.3k 声望
49 粉丝
0 条评论
推荐阅读
哎呀,服务器登录不进去了瞎折腾
副标题:记录一次服务器重启应用恢复过程梳理服务器错误操作的整个过程,算是自己的一次复盘 ✍️本文可以了解到:ssh 免密登录服务器简单配置sshd 端口查看和修改Elixir + Phoenix 服务线上操作如何查看 Linux 的...

小猿大圣阅读 1.7k

封面图
从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木140阅读 11.8k评论 10

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木60阅读 5.9k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.1k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木39阅读 7k评论 6

【关于Javascript】--- 正则表达式篇
基础知识一、元字符 {代码...} 二、量词 {代码...} 三、集合 字符类 {代码...} 四、分支 {代码...} 五、边界 开始结束 {代码...} 六、修饰符 {代码...} 七、贪婪模式和非贪婪模式js默认贪婪模式即最大可能的匹配...

Jerry35阅读 2.9k

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木32阅读 6k评论 9

喵星人铲屎官、『海贼王海米答题』小程序开发者

1.3k 声望
49 粉丝
宣传栏