我有两个算法实现:一个是shell排序,另一个是合并排序。 Shell排序复杂度接近n ^ 1.5并且合并排序是n * logn,因此基本上合并排序应该更快。但是,在我的测试中,我看到了不同的结果:shell排序比合并排序快得多。我相信我做错了,但却看不到这一点。
Shell排序实现:
var shell_sort = function(array){
var length = array.length;
var h = 1;
while( h < length / 3){
h = 3 * h + 1;
}
while( h > 0 ){
for ( var i = h; i < length; i++){
for ( var j = i; j > 0 && array[j] < array[j-h]; j-=h){
array.swap(j, j-h);
}
}
//decreasing h
h = --h / 3
}
return array;
};
并且合并排序:
var merge_sort = function(array){
function merge(left, right){
var result = [];
var il = 0;
var ir = 0;
while (il < left.length && ir < right.length){
if (left[il] < right[ir]){
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
}
if ( il < left.length){
result.push.apply(result,left.slice(il));
}
if (ir < right.length){
result.push.apply(result,right.slice(ir));
}
return result;
}
function merge_sort(items){
//well it is only 1 element
if (items.length < 2){
return items;
}
var middle = Math.floor(items.length / 2);
//create two arrays
var left = items.slice(0, middle);
var right = items.slice(middle);
return merge(merge_sort(left), merge_sort(right));
}
return merge_sort(array);
};
接下来,基本上会产生1000万个元素的数组:
Shell排序:12725ms
合并分类:34338ms
测试非常简单:
//sorting 100000 elements
array.generate_numbers(10000000);
console.time('10000000elements');
sort_algs(array);
console.timeEnd('10000000elements');
其中generate_numbers是简单的辅助函数,它生成具有配置大小的数字数组,以及swap是一个更改元素位置的函数。
答案 0 :(得分:2)
在较高级别,shell排序实现主要依赖于对swap()的调用,而合并排序涉及许多数组访问和操作。很简单,内置函数处理的逻辑与脚本的比例在shell排序中要高得多,而在解释语言中,这通常会导致执行速度更快。
在您的特定情况下,合并排序将在每次调用merge()时创建一个新数组,在该数组上多次调用.push(),并最终在合并时丢弃该数组。 shell排序可以完成所有事情,并且永远不需要创建或销毁数组。因此,合并排序相对于shell排序的性能将受到浏览器使用的垃圾收集特性的严重影响。
如果我没记错的话,传统的合并排序分析假设创建,扩展和销毁数组都是大致恒定的时间操作。这可能不是Javascript中的情况。
答案 1 :(得分:1)
你可以看到Sedgewick的合并排序实现:http://algs4.cs.princeton.edu/22mergesort/Merge.java.html 他使用输入数组的副本来维护merge_sort函数中的小数组,因此他没有任何数组创建和推送元素的开销。
答案 2 :(得分:1)
如果有人感兴趣,可以找到这么多阵列的算法来源:
var merge_sort = function(array){
function merge(a, aux, lo, mid, hi ){
for (var k = lo; k <= hi; k++){
aux[k] = a[k];
}
debugger;
var i = lo;
var j = mid + 1;
for (var k = lo; k <= hi; k++){
if ( i > mid) a[k] = aux[j++];
else if ( j > hi ) a[k] = aux[i++];
else if ( aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
function sort(array, aux, lo, hi){
if (hi <= lo) return;
var mid = Math.floor(lo + (hi - lo) / 2);
sort(array, aux, lo, mid);
sort(array, aux, mid + 1, hi);
merge(array, aux, lo, mid, hi);
}
function merge_sort(array){
var aux = array.slice(0);
sort(array, aux, 0, array.length - 1);
return array;
}
return merge_sort(array);
};
答案 3 :(得分:0)
不仅像@dandavis所说的那样,mergeSort不仅需要一堆新数组,而且它们也会自动增长 - 即你将它们定义为空(result = []
)然后重复push
元素到结束。这需要花费大量时间,因为运行时将为这些数组分配然后重新分配足够的空间。
由于最终数组的长度事先是已知的(你只需要合并left
和right
你知道的长度),尝试将结果分配给它的最终大小(即result = new Array(left.length + right.length)
)并通过索引写入。