【问题标题】:What is the fastest way to sort a large(ish) array of numbers in JavaScript在 JavaScript 中对大量(ish)数字进行排序的最快方法是什么
【发布时间】:2016-11-21 13:48:53
【问题描述】:

在我的应用程序中,我需要对随机数的大型数组(介于 100,000 和 1,000,000 之间)进行排序。

我一直在使用内置的 array.sort(comparisonFunction),其中的 compareFunction 如下所示:

function comparisonFunction(a,b) {
    return a-b;
}

这很好用,但我读过(例如,Native JavaScript sort performing slower than implemented mergesort and quicksort)有更快的选择,特别是如果您的要求满足某些条件:

  1. 我只需要对数字进行排序(例如,不是对象或字母数字数据)
  2. 数据是随机的(不可能已经排序)
  3. 排序不需要稳定

那么 - 在这些情况下可用的最快(或足够接近)的排序算法是什么?

还有,是否有规范的(或至少相对理想的)JavaScript 实现?

[更新]

所以,快速澄清一下 - 在链接的问题中,OP 需要一个稳定的排序。因为我没有 - 我想知道这是否会改变答案(即,如果您事先知道您的数据不会被预先排序,那么也许有一个更快的排序选项可用 '不需要稳定的排序)。

也许答案是“不”,但这就是我要问的原因。

[更新 #2]

这是一个快速排序的实现,除非我犯了错误 - 轻松击败本机排序功能:

function comparisonFunction(a, b) {
  return a - b;
}

function quickSort(arr, leftPos, rightPos, arrLength) {
  let initialLeftPos = leftPos;
  let initialRightPos = rightPos;
  let direction = true;
  let pivot = rightPos;
  while ((leftPos - rightPos) < 0) {
    if (direction) {
      if (arr[pivot] < arr[leftPos]) {
        quickSort.swap(arr, pivot, leftPos);
        pivot = leftPos;
        rightPos--;
        direction = !direction;
      } else
        leftPos++;
    } else {
      if (arr[pivot] <= arr[rightPos]) {
        rightPos--;
      } else {
        quickSort.swap(arr, pivot, rightPos);
        leftPos++;
        pivot = rightPos;
        direction = !direction;
      }
    }
  }
  if (pivot - 1 > initialLeftPos) {
    quickSort(arr, initialLeftPos, pivot - 1, arrLength);
  }
  if (pivot + 1 < initialRightPos) {
    quickSort(arr, pivot + 1, initialRightPos, arrLength);
  }
}
quickSort.swap = (arr, el1, el2) => {
  let swapedElem = arr[el1];
  arr[el1] = arr[el2];
  arr[el2] = swapedElem;
}

var
  i,
  arr1, arr2,
  length;

length = 1000000;


arr1 = [];
arr2 = [];
for (i = 0; i < length; i++) {
  arr1.push(Math.random());
  arr2.push(Math.random());
}

console.time("nativeSort");
arr1.sort(comparisonFunction);
console.timeEnd("nativeSort");


console.time("quickSort");
quickSort(arr2, 0, length - 1, length);
console.timeEnd("quickSort");

【问题讨论】:

  • "...我听说有更快的选择..."在哪里?
  • 请注意,原生 JavaScript .sort() 不需要稳定。如果对于任何显着大小的数组,JavaScript 排序比本机排序更快,我会感到惊讶,尽管在某些输入约束下,基数排序可能值得一试。
  • @Liam:除了上面清楚地说明它不需要需要一个稳定的排序。 (请注意,如果您要对数字进行排序,稳定与不稳定是没有区别的区别......)
  • @mattstuehler - 我可以发布一个快速排序的 C++ 示例,随着重复次数的增加,它最终运行得更快。在进行类似 Hoare 的分区后,有两个 2 行循环用于排除所有中间值 == 枢轴值。在平均情况下几乎没有影响,如果所有值都相同,则运行时间为 O(n)。您可以轻松修改现有的 javascript 快速排序或 introsort 以遵循相同的逻辑。
  • 您的快速排序实现存在缺陷。如果输入数组中有太多重复项,例如用 1-100 之间的随机整数填充 1M 大小的输入数组,则会出现调用堆栈溢出(超出最大调用堆栈错误)。我建议您切换到 while 循环而不是递归调用。或者..我会说实现尾调用优化的递归函数,唉,截至今天,TCO 在 JS 引擎中是个笑话。它根本不起作用。

标签: javascript arrays sorting


【解决方案1】:

有一些排序实现一直优于股票 .sort(至少是 V8),node-timsort 就是其中之一。示例:

var SIZE = 1 << 20;

var a = [], b = [];

for(var i = 0; i < SIZE; i++) {
    var r = (Math.random() * 10000) >>> 0;
    a.push(r);
    b.push(r);
}

console.log(navigator.userAgent);

console.time("timsort");
timsort.sort(a, (x, y) => x - y);
console.timeEnd("timsort");

console.time("Array#sort");
b.sort((x, y) => x - y);
console.timeEnd("Array#sort");
&lt;script src="https://rawgithub.com/mziccard/node-timsort/master/build/timsort.js"&gt;&lt;/script&gt;

以下是我使用的不同浏览器的一些时间(Chakra 有人吗?):

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36
timsort: 256.120ms
Array#sort: 341.595ms

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14
timsort: 189.795ms
Array#sort: 245.725ms

Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:51.0) Gecko/20100101 Firefox/51.0
timsort: 402.230ms
Array#sort: 187.900ms

因此,FF 引擎与 Chrome/Safari 非常不同。

【讨论】:

  • 我正在使用 iceweasel,我得到 227.418ms 的 timsort 和 172.212ms 的 Array#Sort
  • @acontell:是的,强烈依赖于引擎,请参阅更新。
  • 我把这个 Fiddle (jsfiddle.net/uqq54ho8/2) 放在一起,它将本机排序与 timsort 以及我在上面发布的快速排序进行比较。对于 5,000,000 个随机数,结果为:timsort: 1050ms native: 2536ms quick: 754ms。 [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36]
  • Chrome 的内部排序实际上是非常错误和缓慢的,这是几年前报告的,没有修复。这来自一家以让候选人在面试中反刍排序算法而自豪的公司。
【解决方案2】:

不需要将此标记为答案,因为它不是 javascript,并且没有 introsort 的深度检查来切换到 heapsort。

示例 C++ 快速排序。它使用 3 的中值来选择枢轴值,Hoare 分区方案,然后排除中间值 == 枢轴(这可能会也可能不会缩短时间,因为值 == 枢轴可以在分区步骤期间的任何地方结束),并且仅在较小的分区,在较大的分区上循环以将堆栈复杂度限制为 O(log2(n)) 最坏情况。最坏情况的时间复杂度仍然是 O(n^2),但这需要 3 的中位数来重复选择小值或大值,这是一种不寻常的模式。排序或反向排序的数组不是问题。如果所有值都相同,则时间复杂度为 O(n)。添加深度检查以切换到堆排序(使其成为内部排序)会将时间复杂度限制为 O(n log(n)),但具有更高的常数因子,具体取决于使用了多少堆排序路径。

void QuickSort(uint32_t a[], size_t lo, size_t hi) {
    while(lo < hi){
        size_t i = lo, j = (lo+hi)/2, k = hi;
        uint32_t p;
        if (a[k] < a[i])            // median of 3
            std::swap(a[k], a[i]);
        if (a[j] < a[i])
            std::swap(a[j], a[i]);
        if (a[k] < a[j])
            std::swap(a[k], a[j]);
        p = a[j];
        i--;                        // Hoare partition
        k++;
        while (1) {
            while (a[++i] < p);
            while (a[--k] > p);
            if (i >= k)
                break;
            std::swap(a[i], a[k]);
        }
        i = k++;
        while(i > lo && a[i] == p)  // exclude middle values == pivot
            i--;
        while(k < hi && a[k] == p)
            k++;
        // recurse on smaller part, loop on larger part
        if((i - lo) <= (hi - k)){
            QuickSort(a, lo, i);
            lo = k;
        } else {
            QuickSort(a, k, hi);
            hi = i;
        }
    }
}

如果空间不是问题,那么这里的合并排序可能会更好:

Native JavaScript sort performing slower than implemented mergesort and quicksort

如果只是对数字进行排序,并且再次假设空间不是问题,则基数排序将是最快的。

【讨论】:

    【解决方案3】:

    对数字数组进行排序的最快方法是将其转换为TypedArray,然后调用其sort 函数。默认情况下,类型化数组的排序函数是数字的,比带有比较函数的Array.sort() 快​​得多。

    const a = Array.from({length: 1<<20}, Math.random)
    const b = Array.from(a)
    
    function comparisonFunction(a,b){ return a-b }
    
    console.time("nativeSort")
    a.sort(comparisonFunction)
    console.timeEnd("nativeSort")
    
    console.time("typedSort")
    let typedArray = Float32Array.from(b)
    typedArray.sort()
    console.timeEnd("typedSort")

    【讨论】:

      猜你喜欢
      • 2016-08-30
      • 1970-01-01
      • 2011-04-25
      • 2015-09-16
      • 2012-04-12
      • 2011-09-10
      • 1970-01-01
      • 2017-12-03
      • 1970-01-01
      相关资源
      最近更新 更多