我们都知道js数组排序的方法为array.sort,但你知道里面深埋的秘密吗?
array.sort的使用
在MDN的文档中,关于array.sort的参数compareFunction
是这样描述的:
如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
比如:
var foo = [3, 4, 1, 8, 3, 9, 0, 2, 5, 4, 0];
foo.sort(a.sort(function(a, b){return a - b;}));
//返回结果 [0, 0, 1, 2, 3, 3, 4, 4, 5, 8, 9]
大家可以自己的chrome的console里试验下。我使用的是chrome最新的59版本。或者safari里面也是同样的结果。
惊人的发现
事情可能不像上面的那么简单。在实际项目中,我们的数组里也许是并非是一个简单类型的数据,例如:
var foo = [
{txt: 'A', value: 5},
{txt: 'B', value: 5},
{txt: 'C', value: 3},
{txt: 'D', value: 5},
{txt: 'E', value: 1},
{txt: 'F', value: 4},
{txt: 'G', value: 7},
{txt: 'H', value: 3},
{txt: 'I', value: 0},
{txt: 'J', value: 1},
{txt: 'L', value: 2},
{txt: 'M', value: 10},
{txt: 'N', value: 6}
];
//我们可能会期望在按照value从小到大排序并且保持txt顺序不变
foo.sort(function(a, b){ return a.value - b.value; });
//我们可以把排完序的结果按txt跟value更清晰地打印出来
var txtArr = [];
for(var i = 0; i < foo.length; i++) { txtArr.push(foo[i].txt); }
var valueArr = [];
for(var i = 0; i < foo.length; i++) { valueArr.push(foo[i].value); }
/*
* chrome打印出来txtArr ["I", "E", "J", "L", "C", "H", "F", "A", "D", "B", "N", "G", "M"]
* chrome打印出来valueArr [0, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 7, 10]
* safari打印出来txtArr [ "I", "E", "J", "L", "C", "H", "F", "A", "B", "D", "N", "G", "M"]
* safari打印出来valueArr [0, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 7, 10]
*/
从上面的结果中我们可以看到,虽然chrome是按照value进行了排序,但结果中的txt确跟原数组里的先后顺序不一样了。但在safari中,却得到了我们想要的结果。
奥妙在这里
其实啊,chrome跟safari因为js引擎差异,使用了不同的排序算法。我们称为不稳定排序法
跟稳定排序法
。
比较常见的快速排序算法就是不稳定排序法,
归并排序就是稳定排序法。
稳定排序相比于不稳定排序的优势就在于会保持原数组中的先后顺序。但效率上可能没有不稳定排序高。
关于具体的排序算法,大家可以查阅相关资料,去比较各种排序方法优劣,本文不作深入分析。
对于js引擎来说,往往不会简单地使用一种排序方法,而且多种排序方法结合,以达到效率最大化。通常以数组长度为标准,达到某一范围使用不同的排序算法。
其实像我们上面的例子中,chrome(v8)跟safari(javascriptcore)就分别使用了快速排序和归并排序。
那我们要怎么处理这个问题呢
就让我们手动来实现一个归并排序法吧:
function mergeSortArray(list, comparefunc) {
function _merge(left, right, comparefunc) {
var result = [];
var il = 0;
var ir = 0;
while(il < left.length && ir < right.length) {
if(typeof comparefunc === 'function') {
if(comparefunc(left[il], right[ir]) <= 0) {
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
} else {
if(left[il] <= right[ir]) {
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
}
}
return result.concat(left.slice(il)).concat(right.slice(ir));
}
function _mergeSort(items, comparefunc) {
if (items.length < 2) {
return items;
}
var middle = Math.floor(items.length / 2);
var left = items.slice(0, middle);
var right = items.slice(middle);
return _merge(_mergeSort(left, comparefunc), _mergeSort(right, comparefunc), comparefunc);
}
return _mergeSort(list, comparefunc);
}
具体实现思路可参考归并排序的定义。
这些代码在本人的工具库jUtils中可以找到。jUtils也可以作为npm模块去使用。
如有错误,还望不吝赐教。内容均为原创,需要转载,请与本人联系。