【问题标题】:What is Array.prototype.sort() time complexity?什么是 Array.prototype.sort() 时间复杂度?
【发布时间】:2019-09-02 22:28:23
【问题描述】:

根据 Mozilla 文档:

排序的时间和空间复杂度无法保证,因为它 取决于实现。

假设它不是O(n^2) 至少是安全的吗?有没有关于它是如何实施的更详细的数据?谢谢。

【问题讨论】:

  • V8 使用Timsort
  • @Pointy 谢谢。所以在大多数情况下它不应该是 O(n^2)。
  • @Ashwani 哇,这太酷了。这也是犯错很有趣的场合之一:)

标签: javascript


【解决方案1】:

Firefox 使用merge sort。从 70 版开始,Chrome 使用合并排序和插入排序的混合体,称为 Timsort

归并排序的时间复杂度为O(n log n)。虽然规范没有指定要使用的排序算法,但在任何严重的环境中,您可能会期望对更大的数组进行排序不会比O(n log n) 花费更长的时间(因为如果这样做了,那么很容易更改为更快的算法比如合并排序,或者其他一些对数线性的方法)。

虽然comparison sorts 与合并排序类似,其下限为O(n log n)(即它们至少需要这么长时间才能完成),但 Timsort 则利用了已排序数据的“运行”,因此下限为@987654327 @。

【讨论】:

  • 很高兴知道这一点。当内存稀缺并且交换通常比比较更昂贵时,快速排序很流行。我猜这些都不是真的了
【解决方案2】:

理论与实践:理论上没有理论区别 和实践,但实际上是有的。

  • 理论:一切都很清楚,但没有任何作用;
  • 练习:一切正常,但没有什么是清楚的;
  • 有时理论与实践相结合:没有什么是有效的,也没有什么是清楚的。

Big O 表示法非常适合评估算法的可扩展性,但不提供直接比较算法实现之间性能的方法...

典型的例子是 Array.sort() 在浏览器中的实现。尽管 Timsort 的 Big O 配置文件比 Merge 排序更好(请参阅 https://www.bigocheatsheet.com/),但经验测试表明,在 Chrome 的 V8 引擎中实现 Timsort 的平均性能明显优于在 Firefox 中实现 Merge 排序。

下面的图表分别显示了两组数据点:

  • 蓝色数据是 Array.sort() 对随机填充整数的随机长度数组(从 100 到 500,000 个元素)的 500 个测试用例的性能。曲线以 N * Log( N ) 的形式显示数据的 中值,而虚线则以 N * Log( N ) 的形式显示 95% 的界限。也就是说,95% 的数据点位于这些曲线之间。
  • 橙色数据显示了 Array.sort() 对大多数排序数组的性能。 具体来说,数组中约 2% 的值被重新随机化,然后再次应用 Array.sort()。在这种情况下,实线和虚线是性能的线性度量,而不是对数。

此外,Big O 表示法提供了一般的经验法则,可以预期算法的可扩展性,但不解决可变性问题。 Timsort 算法的 Chrome V8 实现在执行上比 Firefox Merge 排序具有更大的可变性,尽管 Timsort 的 Big O 配置文件更好,但即使 Timsort 的最佳时间也不比 Merge 排序的最差时间好。冒着引发宗教战争的风险,这并不意味着 Timsort 比 Merge 排序更差,因为这可能只是 Firefox 的 JavaScript 实现的整体性能更好的一个例子。

上面图表的数据是根据我的 Acer Aspire E5-5775G 签名版的以下代码生成的,该版本具有 Intel Core i5-7200U CPU @2.50GHz 和 8GB RAM。然后将数据导入 Excel,分析 95% 的边界范围,然后绘制图表。图表上的坐标轴刻度已标准化,以便于视觉比较。

  function generateDataPoints( qtyOfTests, arrayRange, valueRange, nearlySortedChange ) {
  
    let loadingTheArray = [];
    let randomSortMetrics = [];
    let nearlySortedMetrics = [];
    
    for ( let testNo = 0; testNo < qtyOfTests; testNo++ ) {
      if ( testNo % 10 === 0 ) console.log( testNo );
      
      // Random determine the size of the array given the range, and then
      // randomly fill the array with values.
      let testArray = [];      
      let testArrayLen = Math.round( Math.random() * ( arrayRange.hi - arrayRange.lo ) ) + arrayRange.lo;
      
      start = performance.now();
      for ( let v = 0; v < testArrayLen; v++ ) {
        testArray[ v ] = Math.round( Math.random() * ( valueRange.hi - valueRange.lo ) ) + valueRange.lo;
      }
      end = performance.now();
      
      loadingTheArray[ testNo ] = { x: testArrayLen, y: Math.floor( end - start ) };
      
      // Perform the sort and capture the result.
      start = performance.now();
        testArray.sort( (a, b ) => a - b );
      end = performance.now();
      
      randomSortMetrics[ testNo ] = { x: testArrayLen, y: Math.floor( end - start ) };
      
      // Now, let's change a portion of the sorted values and sort again.
      let qtyOfValuesToChange = testArrayLen * nearlySortedChange;
      for ( let i = 0; i < qtyOfValuesToChange; i++ ) {
        let v = Math.round( Math.random() * testArrayLen );
        testArray[ v ] = Math.round( Math.random() * ( valueRange.hi - valueRange.lo ) ) + valueRange.lo;
      }
      
      start = performance.now();
        testArray.sort( (a, b ) => a - b );
      end = performance.now();
      
      nearlySortedMetrics[ testNo ] = { x: testArrayLen, y: Math.floor( end - start ) };

    }
    
    return  [ loadingTheArray, randomSortMetrics, nearlySortedMetrics ];
    
  }
  
  // Let's start running tests!
  let arraySizeRange = { lo: 100, hi: 500000 };
  let valueRange = { lo: 0, hi: 2 ** 32 - 1 };
  let results = generateDataPoints( 500, arraySizeRange, valueRange, 0.02 );
  
  
  let tabFormat = 'No Of Elements\tTime to Load Array\tFull Sort\tMostly Sorted\n';
  for ( let i = 0; i < results[0].length; i++ ) {
    tabFormat += `${results[0][i].x}\t${results[0][i].y}\t${results[1][i].y}\t${results[2][i].y}\n`;
  }
  console.log( tabFormat );

要点是,一个算法的性能,表面上基于大 O 表示法更好,有许多因素推动其整体性能,更好的大 O 不一定转化为更好的性能......

【讨论】:

    【解决方案3】:

    这取决于浏览器/引擎。

    自 V8 v7.0 和 Chrome 70 使用 Timsort 算法。
    对于较小的数组,它的时间复杂度为 O(n),空间复杂度为 0(1)。 而对于较大的数组,它的时间复杂度为 O(nlog(n)),空间复杂度为 O(n)。

    旧版本的 V8 使用快速排序,它可以检查小数组(最多 10 个元素)。 所以对于较小的数组,时间复杂度是O(n^2),空间复杂度是O(1)。
    对于较大的数组,时间复杂度为Θ(n log(n))(平均情况),空间复杂度为O(log(n))

    【讨论】:

    • 没有基于比较的排序具有线性平均时间复杂度。如果我们限制输入大小(“for small arrays”),那么“复杂性”毫无意义,我们不妨直接说它是O(1)
    • 不完全符合en.wikipedia.org/wiki/Timsort
    • 那篇文章说 timsort 具有平均时间复杂度 O(n log(n)) - O(n) 只有在输入已经排序的情况下才能实现。
    猜你喜欢
    • 2019-11-09
    • 2017-09-01
    • 2016-03-20
    • 2019-07-10
    • 2018-06-16
    • 2020-09-11
    • 2020-12-16
    • 1970-01-01
    • 2023-02-10
    相关资源
    最近更新 更多