sort函数作用:
(1)直接修改原数组。
(2)默认按照数组元素第一位的ASCII码从小到大排列。
(3)可以设置下列的比较器函数来控制升序,降序或者打乱。
在使用sort()函数时,函数参数如果不设置的话,会以默认方式进行排序,就是以字母顺序进行排序,准确的讲就是按照字符编码的顺序进行排序。
var arr = [3,2,3,34,12,23,234,84,9];
arr.sort();
// 结果: 12,2,23,234,3,3,34,84,9
sort函数基本信息
语法:
arr.sort([compareFunction])
参数:
compareFunction [可选]
用于数组排序规则的比较函数。如果不含有该参数,数组元素按照转换字符串的各个字符的Unicode编码顺序进行排序。
compareFunction 参数:
firstElement 用于比较的第一个元素
secondElement 用于比较的第二个元素``
返回值:
排序后的数组,返回的是当前数组。
默认情况下按升序排列数组,但是是根据字符串UniCode码进行比较。sort()方法进行排序时会在每一项上调用String()转型函数,然后通过字符串UniCode码来决定顺序,即使数组的元素都是number类型,也会把它转换成字符串再比较。
在V8 引擎 sort 函数使用了两种排序算法,分别是,插入排序InsertionSort 和 快速排序QuickSort。
- 当 n<=10 时,采用
插入排序;- 当 n>10 时,采用
三路快速排序;- 10<n <=1000,采用中位数作为哨兵元素;
- n>1000,每隔 200~215 个元素挑出一个元素,放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数。
在v8引擎中,对sort方法提供了2种排序算法:插入排序及快排序。
作者:dxylilac
链接:zhuanlan.zhihu.com/p/33626637\
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\
sort使用方法:
var arr=[];
arr.sort();//默认排序
arr.sort(comparefn(a,b));//自定义排序比较方法
当没有参数传入的时候,其排序顺序默认为,将待排序数据转换为字符串,并按照Unicode序列排序;当然,比较函数可以自定义,自定义排序函数需要返回值,其返回值为-1,0,1,分别表示a<b, a=b, a>b.
当数组长度小于等于10的时候,采用插入排序,大于10的时候,采用快排。
插入排序:
v8源码
function InsertionSort(a, from, to) {
for (var i = from + 1; i < to; i++) {
var element = a[i];
for (var j = i - 1; j >= from; j--) {
var tmp = a[j];
var order = comparefn(tmp, element);
if (order > 0) {
a[j + 1] = tmp;
} else {
break;
}
}
a[j + 1] = element;
}
};
a表示数组,from表示数组开始排序的位置下标,to表示数组结束位置下标。根据上述代码,var arr=[7,3,5,2,4], 那么arr.sort()排序过程如下:
7 3 5 2 4
3 7 5 2 4
3 5 7 2 4
2 3 5 7 4
2 3 4 5 7
虽然插入排序的复杂度是n^2,但是由于数据量很小,因此是常量的复杂度,效率很高,而且插入排序是一个稳定的排序算法。
快速排序:
v8源码
function GetThirdIndex(a, from, to) {//获取关键值
var t_array = new InternalArray();
// Use both 'from' and 'to' to determine the pivot candidates.
var increment = 200 + ((to - from) & 15);
var j = 0;
from += 1;
to -= 1;
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) {
// Insertion sort is faster for short arrays.
if (to - from <= 10) {//数组长度小于等于10的时候,插入排序
InsertionSort(a, from, to);
return;
}
if (to - from > 1000) {//数组长度大于1000的时候,获取关键值
third_index = GetThirdIndex(a, from, to);
} else {//长度大于10小于等于1000的时候,取数组中间的元素作为关键值
third_index = from + ((to - from) >> 1);
}
// Find a pivot as the median of first, last and middle element.
var v0 = a[from];
var v1 = a[to - 1];
var v2 = a[third_index];
var c01 = comparefn(v0, v1);
if (c01 > 0) {
// v1 < v0, so swap them.
var tmp = v0;
v0 = v1;
v1 = tmp;
} // v0 <= v1.
var c02 = comparefn(v0, v2);
if (c02 >= 0) {
// v2 <= v0 <= v1.
var tmp = v0;
v0 = v2;
v2 = v1;
v1 = tmp;
} else {
// v0 <= v1 && v0 < v2
var c12 = comparefn(v1, v2);
if (c12 > 0) {
// v0 <= v2 < v1
var tmp = v1;
v1 = v2;
v2 = tmp;
}
}
// v0 <= v1 <= v2
a[from] = v0;
a[to - 1] = v2;
var pivot = v1;
var low_end = from + 1; // Upper bound of elements lower than pivot.
var high_start = to - 1; // Lower bound of elements greater than pivot.
a[third_index] = a[low_end];
a[low_end] = pivot;
// From low_end to i are elements equal to pivot.
// From i to high_start are elements that haven't been compared yet.
partition: for (var i = low_end + 1; i < high_start; i++) {
var element = a[i];
var order = comparefn(element, pivot);
if (order < 0) {
a[i] = a[low_end];
a[low_end] = element;
low_end++;
} else if (order > 0) {
do {
high_start--;
if (high_start == i) break partition;
var top_elem = a[high_start];
order = comparefn(top_elem, pivot);
} while (order > 0);
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;
}
}
};
可见,v8的sort对于长度大于1000的数组,采用的是快排与插入排序混合的方式进行排序的,因为,当数据量很小的时候,插入排序效率优于快排。
快排需要选择一个关键值,并且以关键值作为基准开始排序,比关键值的值大的放在关键值右边,小的放在关键值左边,由此完成一趟排序;然后再对关键值左侧的数据以及右侧的数据分别执行快排。如果每次关键字选择都是一组数据的最小值或者最大值,那么快排的复杂度将会达到n^2,就跟冒泡没什么区别了。在快排算法中,最优的关键值,是这组数据最中间位置的值,这样才能可以使得排序算法复杂度达到nlogn. 因此,关键值的选择尤为重要。
v8快排关键值的获取:
- 获取临时关键值tmp:
-
- 对于大于10小于等于1000的数据量,tmp为这组数据中间位置的值
- 对于大于1000的数据,根据一定步长从待排序的数组里面获取一组临时数据,对临时数据排序,再获得临时数据中最中间位置的值,作为待排序数组的tmp。步长的计算跟数组的长度有关系,其计算方法如下:
步长 = 200 + 数组长度&15; - 将数组的长度转换为二进制后,与1111按位与,其结果与200相加,作为步长。
- 计算关键值key:
获取数组第一个数a0, 最后一个数an;
比较a0, tmp, an, 赋值给v0, v1, v2, 保证v0<=v1<=v2;
关键值 = v1.
通过对关键值的选取,能最大程度保证快排的复杂度趋近于平均复杂度,即nlogn.
例1: var arr=[3,6,2,7,9,23,5,13,12,6,73,34,55,22,34],arr.sort()排序顺序如下
- 第一趟排序:
-
- 数组长度为15,获取临时关键值tmp=(15-0)>>1+0=7;
- a0=arr[0]=3, an=arr[14]=34, tmp=arr[7]=13, 因此v0=3, v1=13, v2=34, 因此key=13;
- 排序结果:3,2,7,9,6,12,5,6,13,23,73,34,55,22,34,小于13的都在其左侧,大于13的都在其右侧;
- 第二趟排序:
分别对13左侧的数据,及13右侧的数据调用快排方法;
由于13左侧及右侧数据量都小于10,因此会调用插入排序,因此接下来排序结果如下: -
- 13左侧:
2,3,7,9,6,12,5,6
2,3,7,9,6,12,5,6
2,3,7,9,6,12,5,6
2,3,6,7,9,12,5,6
2,3,6,7,9,12,5,6
2,3,5,6,7,9,12,6
2,3,5,6,6,7,9,12 - 13右侧:
23,73,34,55,22,34
23,34,73,55,22,34
23,34,55,73,22,34
22,23,34,55,73,34
22,23,34,34,55,73 - 最终结果2,3,5,6,6,7,9,12,13,22,23,34,34,55,73
- 13左侧:
快排的平均时间复杂度是nlogn,在排序算法中属于效率最高的。快排是一种不稳定的排序算法,但是一般情况下稳定或者不稳定对我们没有特别大的影响,但是对稳定性要求高的排序,就不能使用快排了。
源码中的sort的基本结构
CHECK_OBJECT_COERCIBLE(this,"Array.prototype.sort");
var array = TO_OBJECT(this);
var length = TO_LENGTH(array.length);
return InnerArraySort(array, length, comparefn);
}
function InnerArraySort(array, length, comparefn) {
// 比较函数未传入
if (!IS_CALLABLE(comparefn)) {
comparefn = function (x, y) {
if (x === y) return 0;
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++) {
var element = a[i];
for (var j = i - 1; j >= from; j--) {
var tmp = a[j];
var order = comparefn(tmp, element);
if (order > 0) {
a[j + 1] = tmp;
} else {
break;
}
}
a[j + 1] = element;
}
}
function GetThirdIndex(a, from, to) { // 元素个数大于1000时寻找哨兵元素
var t_array = new InternalArray();
var increment = 200 + ((to - from) & 15);
var j = 0;
from += 1;
to -= 1;
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 {
// 小于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;
partition: for (var i = low_end + 1; i < high_start; i++) {
var element = a[i];
var order = comparefn(element, pivot);
if (order < 0) {
a[i] = a[low_end];
a[low_end] = element;
low_end++;
} else if (order > 0) {
do {
high_start--;
if (high_start == i) break partition;
var top_elem = a[high_start];
order = comparefn(top_elem, pivot);
} while (order > 0);
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…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
标题: JSsort函数底层原理 - 知乎 网址:www.zhihu.com/search?type…
标题:javascript中sort()函数的理解 - 知乎 网址:zhuanlan.zhihu.com/p/22931269