【问题标题】:Javascript Shell Sort implementation is quicker then Merge SortJavascript Shell Sort 实现比 Merge Sort 更快
【发布时间】:2015-04-07 21:16:03
【问题描述】:

我得到了算法的两种实现:一种是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 是一个改变元素位置的函数。

【问题讨论】:

  • 当在 V8 等现代内核上运行时,您根本无法指望 CS101 为编译代码做出的性能估计,因为它有自己的一组优化和瓶颈。简而言之,可能是对象访问或 [].slice() 与 [].push() 的性能比实际算法的时间更长;谁知道...无论如何,您应该使用内置的 [].sort() 以获得最佳性能。
  • 我虽然这样 :) 但真的很感兴趣是什么让这项工作如此缓慢:)
  • 您可以将计时器插入代码本身以跟踪性能,尽管您需要逐步执行更小的数组来执行此操作...查看代码,您的合并排序似乎需要一堆新数组,每个数组都在自己的范围内,而第一个数组则将它们全部保存在一个范围内,这样可以节省大量拆解并提供更多优化机会。
  • “所以基本上合并排序应该更快”只有当你选择 n 足够大时,显然你没有

标签: javascript arrays algorithm


【解决方案1】:

在高层次上,您的 shell 排序实现主要依赖于对 swap() 的调用,而归并排序涉及许多数组访问和操作。很简单,内置函数处理的逻辑与您的脚本的比率在 shell 排序中要高得多,而在解释型语言中,这通常会导致更快的执行。

在您的特定情况下,合并排序将在每次调用 merge() 时创建一个新数组,在该数组上多次调用 .push(),并最终在合并时丢弃该数组。 shell 排序做所有事情,从不需要创建或销毁数组。因此,合并排序相对于 shell 排序的性能将在很大程度上受到浏览器使用的垃圾收集特性的影响。

如果我没记错的话,合并排序的传统分析假设创建、扩展和销毁数组都是大致恒定的时间操作。 Javascript 中可能不是这种情况。

【讨论】:

    【解决方案2】:

    你可以看到 Sedgewick 的合并排序实现:http://algs4.cs.princeton.edu/22mergesort/Merge.java.html 他使用输入数组的副本来维护 merge_sort 函数中的小数组,因此他在创建数组并将元素推送到它时没有任何开销。

    【讨论】:

      【解决方案3】:

      如果有人感兴趣,请查找没有这么多数组的算法来源:

      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->5 行。顺便说一句,我使用长度为 1000 万的数组将 Merge Sort 与 Shell Sort 以及本机 Array.prototype.sort() 进行了比较。 Shell Sort 需要 1.5 * Merge Sort Time,有点慢,而原生 Array.prototype.sort() 需要 2.5 * Merge Sort Time。但在大 O 表示法中,像这样的常量并不重要。
      【解决方案4】:

      正如@dandavis 所说,mergeSort 不仅需要一堆新数组,而且它们还会自动增长——即您将它们定义为空 (result = []),然后在末尾重复使用 push 元素。这需要很长时间,因为运行时会为这些数组分配然后重新分配足够的空间。

      由于最终数组的长度是预先知道的(您只是合并 leftright,它们的长度都知道),请尝试将结果分配给一开始的最终大小(即 @987654325 @) 并改为按索引写入。

      【讨论】:

        猜你喜欢
        • 2017-05-17
        • 1970-01-01
        • 2016-10-14
        • 2011-06-29
        • 2016-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多