Javascript Shell Sort实现比Merge Sort更快

时间:2015-04-07 21:16:03

标签: javascript arrays algorithm

我有两个算法实现:一个是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是一个更改元素位置的函数。

4 个答案:

答案 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元素到结束。这需要花费大量时间,因为运行时将为这些数组分配然后重新分配足够的空间。

由于最终数组的长度事先是已知的(你只需要合并leftright你知道的长度),尝试将结果分配给它的最终大小(即result = new Array(left.length + right.length))并通过索引写入。