JS中sort函数的语法
Array.sort(compareFunctuion)
Array.sort(compareFunctuion)中的参数
Array.sort(compareFunctuion)中的参数是一个匿名函数,该参数可传可不传。
一 . JS中sort函数的基本用法
注意:sort函数不会复刻出一个数组,而是在原来的数组上进行排序。
1.sort()不传参
无参数时,调用Array.sort()底层的compareFunctuion的默认值,默认为升序排序,排序方式是根据字符串Unicode码点。
当给字符串进行排序时,sort函数可以运行出正确的结果,但当进行数值排序时,会出现问题。代码如下:
let arr = [455,56,9,90,1,4];
console.log(arr.sort());
// 运行结果为[1, 4, 455, 56, 9, 90]
之所以出现这样的情况是因为,如果不传参数,sort函数底层会运行默认的compareFunctuion函数,其会将数组中的元素全转化为字符串形式,以字符串中每个字符的Unicode编码逐一进行比较,若相等则比较下一个字符,直到出现不相等或没有字符可比较时,才结束。然后按照从小到大的顺序排放。
2.sort()传参
传入的匿名函数可接收两个参数x,y。x,y代表了数组中要比较的元素的值,该匿名函数的返回值是整数,即,根据返回值不同,来决定排序的方式。有以下三种返回值:
注意:只有当传入函数的返回值大于0时,才交换x,y的位置 ,函数的返回值小于等于0不做任何操作
1.当x-y>0,返回一个正整数时,交换 x 与 y 的位置,按升序方式,从小到大排列
let arr = [455,56,9,90,1,4];
console.log(
arr.sort(function(x,y){
return x-y
})
);
// 结果[1,4,9,56,90,455]
2.当x-y=0时,x 和 y 的相对位置不变。
3.当x-y<0 ,看成是降序排列,从大到小排序 ,由上面的注意可知,此时return后面要写成y - x。
let arr = [455,56,9,90,1,4];
console.log(
arr.sort(function(x,y){
return y-x
})
);
// 结果[455,90,56,9,4,1]
3.数组对象排序
最重要的还是这个对象属性排序,当后台给我们前端很多数据并且没有排序时,我们一般都是要重新进行排序,而后台给的数据往往是好几层,不会像前面那种简单的就一个数组,这个时候就要用sort中对象属性排序了
// 3.对象属性排序
var obj = [
{name:"lucy", num:400},
{name:"nancy", num:110},
{name:"maria", num:200}
];
obj.sort(compare("num"));
console.log(obj);
//数组对象属性值排序
function compare(property){
return function(a,b){
//value1 - value2升序
//value2 - value1降序
var value1 = a[property];
var value2 = b[property];
return value1 - value2;//升序
}
}
运行结果:
[
{name:"nancy", num:110},
{name:"maria", num:200},
{name:"lucy", num:400}
]
注意: compare()中参数必须是这个对象的属性名称,而你要比较的这些对象里面,一定要有这个属性名称,否则会出错
二 . 底层原理
在V8 引擎 sort 函数使用了两种排序算法,分别是,插入排序InsertionSort 和 快速排序QuickSort。
- 当 n<=10 时,采用
插入排序;- 当 n>10 时,采用
三路快速排序;- 10<n <=1000,采用中位数作为哨兵元素;
- n>1000,每隔 200~215 个元素挑出一个元素,放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数。
- 根据数组中元素的个数采用不同的算法,这与两种算法的时间复杂度 O(n)有关。想知道这方面知识,可自行查找
为何要选择哨兵元素
因为快速排序的性能瓶颈在于递归的深度,最坏的情况是每次的哨兵都是最小元素或者最大元素,那么进行 partition(一边是小于哨兵的元素,另一边是大于哨兵的元素)时,就会有一边是空的。如果这么排下去,递归的层数就达到了n, 而每一层的复杂度是 O(n),因此快排这时候会退化成O(n^2)级别。
这种情况是要尽力避免的,那么如何来避免?就是让哨兵元素尽可能地处于数组的中间位置,让最大或者最小的情况尽可能少
1.插入排序算法:
将 n 个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含一个元素, 无序表中包含有 n-1 个元素, 排序过程中每次从无序表中取出第一个元素, 把它的值依次与有序表元素的值进行比较, 将它插入到有序表中的适当位置, 使之成为新的有序表。
插入排序图解
注意:橙色是已排列好的元素,蓝色是未排列的元素,红色的是正在进行排列的元素
2.快速排序算法:
(1).首先在数组中选择一个元素作为一个分界值。即,选定一个元素作为Pivot中心轴。
(2).将大于Pivot的数字放在Pivot的右边
(3).将小于Pivot的数字放在Pivot的左边
(4).重复上述三步操作,直至Pivot中心轴左右的元素个数各为一,即快速排序进行完成。
可以看出,其本质是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
B站上一个挺好的的快速排序算法的讲解视频www.bilibili.com/video/BV1at…
快速排序图解
>>(右移运算符)
其实来说,右移运算符就是/2的n次方,然后四舍五入。
如,以下换算 m >> n 等价于 m/2^n,例如,500>>1 = 500 / 2^1 = 250
JS中sort函数底层的实现代码
function InnerArraySort(array, length, comparefn) {
// 判断函数是否作为参数传入
if (!IS_CALLABLE(comparefn)) {
comparefn = function (x, y) { //sort函数调用默认的ompareFunctuion
if (x === y) return 0;
// %表示的是当前调用的方法不是JS里面的方法,而是V8引擎中C++定义的方法。
if (%_IsSmi(x) && %_IsSmi(y)) {
//词典比较
return %SmiLexicographicCompare(x, y);
}
x = TO_STRING(x);
y = TO_STRING(y);
if (x == y) return 0;
else return x < y ? -1 : 1;
};
}
function InsertionSort(a, from, to) {
// 插入排序 如,
for (var i = from + 1; i < to; i++) { //对[7,8,3,4,6,1]排序
var element = a[i]; //当i=2时,将数组下表为2的元素取出放入element中
for (var j = i - 1; j >= from; j--) { //element从数组为下标为1的元素依次向前比较
var tmp = a[j]; //内层循环整个数组的变化
var order = comparefn(tmp, element);//[7,8,3,4,6,1]->
if (order > 0) { //[7,8,8,4,6,1]->
a[j + 1] = tmp; //[7,7,8,4,6,1]
} else {
break;
}
} //内层循环结束后,此时j=-1
a[j + 1] = element; //[7,7,8,4,6,1]->[3,7,8,4,6,1]
//然后按以上步骤将数组的下标2以后的元素依次与前面排列好的元素进行比较
}
}
function GetThirdIndex(a, from, to) { // 元素个数大于1000时,寻找基准点
// new Array()出来的数组向用户公开,而 new InternalArray()出来的数组仅供内部使用。
var t_array = new InternalArray();
var increment = 200 + ((to - from) & 15);
var j = 0;
from += 1;
to -= 1;
// n>1000,每隔 200~215 个元素挑出一个元素,放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数。
for (var i = from; i < to; i += increment) {
t_array[j] = [i, a[i]]; // 记录数组元素的位置和对应的值
j++;
}
t_array.sort(function(a, b) {
return comparefn(a[1], b[1]);
});
var third_index = t_array[t_array.length >> 1][0]; // 记录中位数对应的元素下标
return third_index;
}
function QuickSort(a, from, to) { // 快速排序实现
//哨兵位置
var third_index = 0;
while (true) {
if (to - from <= 10) {
InsertionSort(a, from, to); // 数据量小,使用插入排序,速度较快
return;
}
if (to - from > 1000) {
third_index = GetThirdIndex(a, from, to); // 设置哨兵的位置
} else {
// (to - from) >> 1所表示的意思可看上面的向右运算符语法,即,小于1000 直接取中位数对应的元素
third_index = from + ((to - from) >> 1);
}
// 下面开始快排
var v0 = a[from];
var v1 = a[to - 1];
var v2 = a[third_index];
var c01 = comparefn(v0, v1);
if (c01 > 0) {
var tmp = v0;
v0 = v1;
v1 = tmp;
}
var c02 = comparefn(v0, v2);
if (c02 >= 0) {
var tmp = v0;
v0 = v2;
v2 = v1;
v1 = tmp;
} else {
var c12 = comparefn(v1, v2);
if (c12 > 0) {
var tmp = v1;
v1 = v2;
v2 = tmp;
}
}
a[from] = v0;
a[to - 1] = v2;
var pivot = v1;
var low_end = from + 1;
var high_start = to - 1;
a[third_index] = a[low_end];
a[low_end] = pivot;
// 接下来进行分隔,将小于pivot的数放在其左边,大于pivot的数放在其右边
// 左右分别开弓
partition: for (var i = low_end + 1; i < high_start; i++) {
var element = a[i];
var order = comparefn(element, pivot);
当pivot右边的数小于pivot时,交换两个值的位置,然后继续向数组后移动一位在进行比较,直至有数比pivot大时,左边的不动,右边的开弓
if (order < 0) {
a[i] = a[low_end];
a[low_end] = element;
low_end++;
} else if (order > 0) {
// 右边开弓,pivot右边的数比pivot大时,do-while继续循环,下标减一,继续比较,直至有比pivot小的值出现,退出循环。
do {
high_start--;
if (high_start == i) break partition;
var top_elem = a[high_start];
order = comparefn(top_elem, pivot);
} while (order > 0);
// 将比pivot小的数放在pivot的左边,然后继续左开弓,重复以上步骤。循环全部结束时,pivot左边的数都是比它小的,右边的数都是比他大的。
a[i] = a[high_start];
a[high_start] = element;
if (order < 0) {
element = a[i];
a[i] = a[low_end];
a[low_end] = element;
low_end++;
}
}
}
// 快排的核心思路,递归调用快速排序方法
if (to - high_start < low_end - from) {
QuickSort(a, high_start, to);
to = low_end;
} else {
QuickSort(a, from, low_end);
from = high_start;
}
}
}
参考文章作者:
Z_Maple
链接:juejin.cn/post/697798… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:鼓玄
链接:juejin.cn/post/684490… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
js位移运算符(<<、>>) 请参考下列文章
js中sort总结 - 10年码农 - 博客园
www.cnblogs.com/10yearsmano…
JS - sort方法的使用场景