web-dev-qa-db-fra.com

Quel est le moyen le plus rapide de trier un grand tableau (ish) de nombres en JavaScript

Dans mon application, je dois trier de grands tableaux (entre 100 000 et 1 000 000) de nombres aléatoires.

Je me sers de la array.sort(comparisonFunction) intégrée où ComparaisonFonction ressemble à ceci:

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

Cela fonctionne très bien, mais j’ai lu (par exemple, le tri JavaScript natif s’exécutant plus lentement que le système mergesort et quicksort ) selon lequel il existe des options plus rapides, en particulier si vos exigences répondent à certaines conditions:

  1. Je n'ai besoin que de trier les nombres (par exemple, ni les objets ni les données alphanumériques)} _
  2. les données sont aléatoires (aucune chance qu’elles soient déjà commandées)} _
  3. le tri n'a pas besoin d'être stable

Alors, quel est l'algorithme de tri le plus rapide (ou assez proche) disponible dans ces circonstances?

Et existe-t-il une implémentation JavaScript canonique (ou du moins relativement idéale)?

[METTRE À JOUR]

Yikes ... deux votes négatifs dans les 30 secondes qui suivent la publication! Donc, une clarification rapide - dans la question liée, le PO nécessitait une sorte stable. Puisque je ne le fais pas, je me demande si cela changerait la réponse (par exemple, une option de tri plus rapide est peut-être disponiblesi vous savez à l'avance que vos données ne seront pas triées à l'avance, et pas besoin d'une sorte stable).

Peut-être que la réponse est "non", mais c'est pourquoi je demande.

[MISE À JOUR # 2]

Voici une implémentation de quicksort qui, sauf erreur de ma part, bat facilement la fonction de tri natif:

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");

5
mattstuehler

Il existe des implémentations de tri qui battent systématiquement le stock .sort (au moins V8), node-timsort étant l’un d’eux. Exemple:

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");
<script src="https://rawgithub.com/mziccard/node-timsort/master/build/timsort.js"></script>

Voici quelques timings de différents navigateurs que j'ai autour (Chakra quelqu'un?):

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

Le moteur FF est donc très différent de Chrome/Safari.

9
georg

Inutile de marquer ceci comme une réponse, car ce n'est pas du javascript et la vérification de la profondeur de l'introsort ne permet pas de passer à l'état dynamique.

Exemple de tri rapide C++. Il utilise la médiane de 3 pour choisir la valeur de pivot, la structure de partition Hoare, puis exclut les valeurs moyennes == pivot (au moins une de ces valeurs), et utilise uniquement la récursivité sur la partition la plus petite, effectuant une boucle sur la partition la plus grande pour limiter la complexité de la pile à O(log2(n)) dans le pire des cas. La complexité temporelle dans le pire des cas reste O (n ^ 2), mais cela nécessiterait une médiane de 3 pour choisir de manière répétée des valeurs petites ou grandes, un schéma inhabituel. Les tableaux triés ou inversés ne sont pas un problème. Si toutes les valeurs sont identiques, la complexité temporelle est alors O (n). L'ajout d'une vérification de profondeur pour passer à Heapsort (en faire un introsort) limiterait la complexité temporelle à O (n log (n)), mais avec un facteur constant plus élevé en fonction du chemin d'accès à l'hôte.

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;
        }
    }
}

Si l'espace n'est pas un problème, les tris de fusion ici peuvent être meilleurs:

Le tri JavaScript natif est plus lent que la fusion et le tri rapide

1
rcgldr