总结前端排序中遇到的问题

前一轮似乎一直是一个误区:前端不能用于算法知识,长期以来,人们可能受到这种说法的影响。

前端分类

前端排序场景

前端将排序条件作为请求参数传递给后端,后端将排序结果作为对前端的请求响应返回,这是一种常见的设计。

想象一个场景:如果你使用一个美食应用程序,你经常切换排序方式,然后按价格排序,然后按得分排序。

在实际生产中,受服务器成本等因素的制约。当单个数据查询成为整体性能的瓶颈时,它也将考虑通过在前端进行排序来优化性能。

排序算法

我觉得这是不必要的。作为计算机科学的基本算法,描述是维基百科的直接拷贝。

这一节的存在纯粹是为了(子)(子)(舒)。

javascript的排序

当涉及到前端分类,这是自然的第一个想到的Javascript的原生界面array.prototype.sort。

这个界面存在因为ECMAscript第一版。让我们来看看最新的规范,它描述的是什么。

Array.prototype.sort规范

Array.prototype.sort(comparefn)

复制代码代码如下所示:

这个数组的元素被排序。排序是不稳定的(即元素相等不一定要是comparefn是保持原来的顺序)。不是未定义的,它应该是一个接受两个参数x和y的函数,如果x y返回一个负值。



很明显,规范并不限制排序算法内部实现的排序,甚至接口的实现不需要稳定地排序,这是一个关键点,它将涉及很多次。

在这种情况下,前端排序实际上依赖于各种浏览器的具体实现。那么,当今主流浏览器如何分类呢接下来,我们简单地比较Chrome,Firefox和微软边缘,分别。

实现在Chrome

Chrome的Javascript引擎是V8,因为它是开源的,您可以直接查看源代码。

整个array.js在Javascript语言实现。排序的方法显然是更复杂的比快速排序,已经看到,但很显然,核心算法仍然是一个快速排序的算法复杂度的原因是V8引擎性能的考虑,做了很多的优化。(下开放的展览)

实现在Firefox

目前还不可能确定Firefox Javascript引擎的数组排序算法目前将使用什么。{ 3 }

根据现有的信息,spidermoney内部归并排序的实现。

微软边缘的实现

对微软引擎,Javascript引擎的核心部分脉轮,已在2016年初GitHub开源。

通过对源代码的研究发现,查克拉的数组排序算法也是一种快速排序,与V8相比,它只实现了一种纯快速排序,没有任何性能优化的痕迹。

javascript数组排序问题

我们都知道,快速排序是一个不稳定的排序算法,和归并排序是稳定的排序算法。由于在不同的搜索引擎算法选择的差异,通过前端Javascript代码取决于array.prototype.sort接口,和分类的效果不一致的浏览器。

等级稳定性的不同要求特定的场景触发有问题;在许多情况下,这种不稳定性不会影响它。

如果在实际的项目开发中没有对数组的稳定性要求,我们可以看出,到目前为止,浏览器之间的区别并不那么重要。

但是,如果项目要求排序必须是稳定的,那么这些差异的存在就不能满足需要,我们需要为此做一些额外的工作。

例如 uff1a

一个城市的汽车牌照拍卖系统,投标的最终规则如下:

1。按价格分类;

2。相同的价格是按照投标顺序(即提交价格的时间)订购的。

如果排序是在前端,中标人在快速排序浏览器很可能是不符合预期。



探索差异的背后

在我们找到解决办法之前,有必要先探究问题的原因。

为什么Chrome使用快速排序

事实上,这种情况从一开始就存在。

Chrome测试版于2008年9月2日发布。然而,其发布后不久,有90人提交# bug反馈给开发组开发的铬。

这个bug问题讨论的时间跨度很大,直到2015年11月10日,仍有人对V8的数组排序实现问题发表评论。

同时,我们也注意到,这个问题已经关闭。然而,开发组的成员在2013年6月重开为ECMAscript讨论下规范参考。

ES的最终结论是

复制代码代码如下所示:

它不会改变。稳定是不稳定的一个子集。和,子集,是,是,和其他因素。

/安德烈亚斯



本条前款规定,描述最终的ECMAscript 2015规范。

时代特征

在对清规戒律,Chrome发布之初,据报道,这个问题可能有其特殊性。

如上所述,Chrome的第一个版本于2008年9月发布,根据StatCounter的统计,两个浏览器与当时市场占有率最高的是IE(当时只有IE6和IE7和Firefox),和市场占有率分别为67.16%和25.77%。也就是说,两个浏览器添加超过90%的市场份额。

根据另一个浏览器排序算法稳定性的统计数据,这两个超过90%的市场份额的浏览器采用了稳定的数组排序,因此在Chrome发行版开始时,开发者会受到质疑。

符合规范

在讨论bug问题的过程中,可以理解开发组成员用于快速排序引擎实现的一些考虑因素。

有一点,他们认为发动机必须符合ECMAscript规范。因为不规范要求的稳定排序的描述,他们认为V8的实施是完全符合。

性能方面的考虑

此外,他们认为V8引擎设计中最重要的考虑因素之一是发动机的性能。

比归并排序更快,整体性能更好:

在实际的计算机执行环境中,快速排序比同一时间复杂度的排序算法(当最坏的组合遇到最坏的组合)快得多。

较低的空间开销,前者仅为O(n)空间复杂度,与运行时内存消耗较少的O(n)空间复杂度相比

V8阵列排序算法的性能优化

既然V8非常了解引擎的性能,那么它在数组排序中做什么呢

通过阅读源代码,一些皮毛得到了学习。

混合插入排序

快速排序是划分和管理、分解大型数组和逐层递归的思想,但是如果递归太深,递归调用堆栈的内存资源消耗将非常大,优化不好甚至可能导致堆栈溢出。

V8的当前实现是设置一个阈值,使用插入排序对小于10的最小数组进行排序。

根据代码注释,以及在维基百科的描述,而插入排序的平均时间复杂度(nsup2;O)为O(n)的快速排序,但在运行环境、小阵列使用插入排序比快速排序效率更高,而不再是展开。

V8引擎的代码示例


无功快速排序=函数QuickSort(,,到){
......
当(真){
短数组中插入排序更快。
如果(从< = 10){
InsertionSort(,,等);
返回;
}
......
}
......
};


带他们



被称为快速排序的阿基里斯跟最坏的情况下,组合阵列算法退化。

快速排序算法的选择参考内核(支点),并分解为交换阵列为后续递归的两个数据的数据。如果一个已排序的数组,总是选择每个参考元素的第一个或最后一个元素,所以每次都会有一些地方是空的,递推数将达到N,导致算法的时间复杂度降低到O(nsup2;)。因此,支点的选择是非常重要的。

V8用于三(中位数为三)优化:除了两个附加元素,并在基准竞争元素中选择一个元素。

这第三个元素的选择策略大致如下:

当数组长度小于或等于1000时,元素的位置作为二进制元素的目标选择。

当数组的长度大于1000,每200-215(非固定,随着长度的数组元素)选择确定一批候选元素第一。然后一个候选元素的排序,和中值作为目标元素

最后,以三个元素的中值作为支点。

V8引擎的代码示例


无功getthirdindex =函数(,,到){
无功t_array =新internalarray();
使用both'from'and'to/确定候选人的支点。
var增量= 200 +((从)15);
var j=0;
从= 1;
到1;
对于(var i =;i <到;i =增量){
t_array { } = {我J,一个{我} };
++;
}
T_array.sort (function (a, b) {
返回comparefn(B一{ 1 },{ 1 });
});
无功third_index = t_array { t_array.length > > 1 } { 0 };
third_index返回;
};

无功快速排序=函数QuickSort(,,到){
......
当(真){
......
如果(从- > 1000){
third_index = getthirdindex(,,等);
{人}
third_index = +((-从)> > 1);
}
}
......
};


原地排序



当我回顾快速排序算法时,我在因特网上看到了许多使用Javascript的例子。

但是,发现大部分代码是按照以下方式实现的


无功快速排序=功能(ARR){
如果(arr.length <= 1){ return ARR;}
无功pivotindex = math.floor(arr.length / 2);
VaR支点= arr.splice(pivotindex,1){ 0 };
var左};
var;
对于(var i = 0;i < arr.length;i++){
如果(ARR {我} <支点){
Left.push(ARR {我});
其他{ }
Right.push(ARR {我});
}
}
返回快速排序(左),Concat({转},快速排序(右));
};


上述代码的主要问题是存储递归子阵在左、右两个数,因此,它需要额外的存储空间为O(n),空间复杂度理论平均(O N)相比差距较大。

额外的空间开销也影响到实际操作的整体速度,这也是其他排序算法的原因之一,这些排序算法可以在实际运行时快速地对性能进行排序,而不是在同一时间复杂度级别上进行排序。

V8源代码中的实现是对原始数组元素的交换。

为什么Firefox使用合并排序

它背后有一个故事。

事实上,Firefox在开始发布时不使用稳定的排序算法进行数组排序。这是一个有效的测试。

数组排序算法通过Firefox的原始版本的实现(火鸟)是一堆排序,这也是一个不稳定的排序算法。所以,后来有人提交一个bug。

Mozilla开发小组对这个问题进行了一系列的讨论。

我们可以从讨论的过程中得出几点意见。

1。同时,Mozilla的竞争对手是IE6。从上面的统计数据,它是已知的,IE6是一个稳定的排序。



布兰登·艾奇的父亲,2.javascript,感觉稳定性OD



3、在使用堆排序之前,Firefox是一种快速排序。



在此基础上,开发组的成员往往会达到一个稳定的排序算法的前提下,Firefox3是一种新的数组排序的实现。

解决排序稳定性的差异

到目前为止,我想讨论一下排序实现中浏览器之间的区别以及它们存在的原因。

但在这里阅读,读者可能还有一个问题:如果我的项目是依靠一个稳定的排序

解决方案

事实上,解决这个问题的想法比较简单。

浏览器为不同的考虑选择不同的排序算法,有些可能偏向于追求极端性能,有些则偏向于提供良好的开发经验,但有一些规则需要遵循。

从目前已知的情况下,所有的主流浏览器(包括IE6,7, 8)基本上都是枚举数组的排序算法的实现:

1。归并排序 / timsort



2。快速排序



所以,我们将快速排序,通过自定义转换,成为一个稳定的排序,不是吗

一般来说,对数组对象使用不稳定排序会影响结果。使用稳定排序或不稳定排序的其他类型数组的结果是相等的:

对经过排序的数组进行预处理,以将自然顺序属性添加到要排序的每个对象,它不与对象的其他属性相冲突。

自定义分类比较的方法,comparefn,总是以自然秩序为二判断维度的预断是相等的。



在归并排序方面,由于算法本身是稳定的,所以该算法是稳定的。附加自然序比较不改变排序结果,因此方案相容性较好。

但它需要修改要排序的数组,还需要为存储自然顺序属性创建额外的空间。可以想象,V8引擎不应该采用类似的方法,但是,开发人员自定义排序方案是可行的。

程序代码实例


严格使用;

const指数=符号('index);

功能getcomparer(比较){
返回函数(左,右){
让结果比较(左,右);

返回结果左{ } -右} { index }:结果= 0;
};
}

函数排序(数组,比较){
阵列= Array.map(
(项目,索引){ {
如果(typeof项目= 'object){
项目{索引} =索引;
}

返回的项目;
}
);

返回array.sort(getcomparer(比较));
}


以上只是一个满足稳定排序的算法的简单示例。

原因很简单,因为数据结构作为一个杂数组输入实际的生产环境,需要根据实际情况确定需要在排序前进行更多种类的检测。

标注

1。前端是一个比较宽泛的概念,本文的前端主要指的是使用浏览器作为载体的环境和Javascript作为编程语言的环境。



2。本文不打算涉及整个算法,我们使用普通排序算法作为切入点。



三.这类的错误spidermoney相关搜索当算法被证实是通过Firefox的数组排序的实现。这是讨论建议使用timsort算法在极端条件下的过程中,更好的性能取代归并,但Mozilla的工程师说,timsort算法具有许可证授权的问题,没有办法在Mozilla软件直接使用算法,随后等待答复

总结

以上是对前端排序中遇到的问题的总结和解决方法。近年来,越来越多的项目转变为富客户端应用的方向,并在项目前端的比例更大。未来随着浏览器的计算能力的进一步增强,它允许一个更复杂的计算。有责任感的变化,有可能是在前面最终形成的一些重大变化。行走江湖,总是有一个技能。