JS-数组排序 Array.sort()
Array.sort() 基本使用
语法
1 |
|
参数 sortby:
- 可选
- 必须是函数
不传参数时,默认将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码(Unicode编码)的顺序进行排序
所以,若数组是数字,想按大小排序,不传参是无法实现的(内部比较的是字符编码的顺序)
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值
然后返回一个用于说明这两个值的相对顺序的数字
比较函数应该具有两个参数 a 和 b,其返回值如下:
- 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值
- 若 a 等于 b,则返回 0
- 若 a 大于 b,则返回一个大于 0 的值
示例
1 |
|
返回值:
- 返回对数组的引用
- 请注意,数组在原数组上进行排序,不生成副本
关于localeCompare
语法
1 |
|
以本地特定的顺序,对字符串进行比较
同样的,返回值也是三种
- stringObject 小于 target,则 localeCompare() 返回小于 0 的数
- 两个字符串相等,或根据本地排序规则没有区别,该方法返回 0
- 如果 stringObject 大于 target,则该方法返回大于 0 的数
(这里的大小,是指位置顺序大小)
使用场景:
通常用于姓名的排序上
示例
1 |
|
注意:
必须是字符串才能调用此方法
Array.sort() 实现原理
接下来了解一下 sort() 方法的内部实现,有助于更好地运用此方法
不同浏览器,对于 sort() 方法的实现有所不同
Mozilla/Firefox:
- 归并排序(稳定排序)
Webkit:
- 底层实现用了 C++ 库中的 qsort() 方法
chrome浏览器的话,V8 v7.0以前:
- 数组长度小于等于 10 => InsertionSort 插入排序(稳定排序)
- 数组长度大于 10 => QuickSort 快速排序(不稳定排序)
- 源码(第710行开始):
https://github.com/v8/v8/blob/ad82a40509c5b5b4680d4299c8f08d6c6d31af3c/src/js/array.js
V8 v7.0之后:
- 改进了排序算法
- 换成了使用Torque实现的Timsort算法
- 从V8 v7.0和Chrome 70开始可用
- 改进之后的排序,是稳定的
v8博客-排序篇:
https://v8.dev/blog/array-sort
查资料参考:
https://zhuanlan.zhihu.com/p/81204554
小小科普:
- 在Chrome中,只有Html的渲染采用了WebKit
- JavaScript解析使用V8引擎
还有一个关于chrome浏览器排序会出现的小问题
当排序的是对象数组时,我们会传入自定义的比较函数,用对象的某一属性来作比较
若某个对象的该属性值为undefined,排序结果可能会与预期不符
看个示例
1 |
|
会发现,2个undefined上下,并没有排序,原因也很简单
我们的排序函数是年龄相减,而 undefined 与任何数 相减都是 NaN
而 NaN 参与运算,都是 false
所以根据比较结果,会直接判断undefined属于该位置
所以,undefined前后的值,会出现排序断层
若我们修改一下
1 |
|
可以发现,我们用基本数据类型的数组排序的话,undefined是会放到最后,并且非undefined的数据都会排序
这是因为chrome浏览器对数组的值做了处理,若是undefined的话,会移到最后,并且不进行排序
关于排序算法的稳定性
2个相等的数,排序前后,位置顺序不变的,就是稳定排序
(这里的位置顺序,指的是前后位置顺序)例如:
Ai = Aj
排序前,Ai 在 Aj 前面
排序之后,Ai 也要在 Aj 前面了解算法的稳定性后,我们才能知道,什么时候要用稳定排序,什么时候不需要用稳定排序
比如,我们要排序的是一个对象数组,我们仅针对对象的年龄属性排序
若用不稳定排序,当年龄相同时,该对象的相对位置却是不固定的
若需要列表展示,姓名 + 年龄
就会出现姓名列不断变动的情况,这是要避免的情况
此时就需要用稳定排序了
排序算法实现思路
插入排序(InsertionSort)
时间复杂度:O(N^(1-2))
最优的情况:
当待排序数组是有序时,只需当前数跟前一个数比较一下就可以了
这时一共需要比较N- 1次,时间复杂度为 O(N)
最坏的情况:
待排序数组是逆序的,此时需要比较次数最多
总次数记为:1+2+3+…+N-1
所以,插入排序最坏情况下的时间复杂度为 O(N^2)
空间复杂度:O(1)
思想:
将数据分两个区间,已排序区间和未排序区间,初始已排序区间只有一个元素,就是数组的第一个元素
在未排序区间中依次取出元素并插入到已排序区间的合适位置
重复这个步骤直到未排序区间元素为空步骤
- 将第一个元素定为已排序数据,后面的都是未排序数据
- 取出下一个元素(从第二个元素开始),在已排序数据中,从右往左,依次比较
- 被取出元素与已排序数据比较,若满足排序条件(自定义),则被取元素插入到该已排序数据前
- 继续往左比较,重复步骤3,直到不满足排序条件,则被取元素位置确定
- 重复步骤2,3,4
实现(javaScript)
1 |
|
实现的时候,有一个要注意的点就是:
- 在用未排序数据,对已排序数据进行扫描时(从第二个元素开始,依次往前扫描)
- 要确保元素交换后,进行比较的还是原先的那个未排序数据的值
- 可以先把该未排序元素保存起来,用保存的副本,与已排序数据进行比较(第一种写法)
- 也可以每次比较,都用交换后的值与下一个未排序值比较(第二种写法)
建议亲自尝试写一下,就不会觉得乱了~
(未完待续…)
(持续更新…)