web-dev-qa-db-fra.com

Pourquoi Collections.sort utilise-t-il Mergesort mais pas Arrays.sort?

J'utilise JDK-8 (x64). Pour Arrays.sort (primitives) J'ai trouvé ce qui suit dans la documentation Java:

L'algorithme de tri est un double pivot Quicksort de Vladimir Yaroslavskiy, Jon Bentley et Joshua Bloch. "

Pour Collections.sort (objets) J'ai trouvé ce "Timsort":

Cette implémentation est stable, adaptative, itérative mergesort ... Cette implémentation vide la liste spécifiée dans un tableau, trie le tablea, et effectue une itération sur la liste, réinitialisant chaque élément à partir de la position correspondante dans le tableau.

Si Collections.sort utilise un tableau, pourquoi n’appelle-t-il pas simplement Arrays.sort ou utilisez le double pivot QuickSort? Pourquoi utiliser Mergesort?

80
Quest Monger

L’API garantit un tri stable que Quicksort ne propose pas. Cependant, lors du tri des valeurs primitives selon leur ordre naturel, vous ne remarquerez pas de différence, car les valeurs primitives n’ont pas d’identité. Par conséquent, Quicksort peut être utilisé pour les tableaux primitifs et sera utilisé s’il est considéré comme plus efficace¹.

Pour les objets que vous pouvez remarquer, lorsque des objets avec une identité différente qui sont considérés comme égaux en fonction de leur implémentation equals ou du Comparator fourni changent leur ordre. Par conséquent, Quicksort n'est pas une option. Donc, une variante de MergeSort est utilisée, la version actuelle Java) TimSort. Ceci s'applique à la fois à Arrays.sort Et à Collections.sort, Bien qu'avec Java 8, le List lui-même puisse remplacer les algorithmes de tri.


¹ L'avantage en termes d'efficacité de Quicksort nécessite moins de mémoire lorsqu'il est installé sur place. Mais il a des performances dramatiques dans le pire des cas et ne peut pas exploiter les suites de données triées au préalable dans un tableau, ce que TimSort fait.

Par conséquent, les algorithmes de tri ont été retravaillés de version en version, tout en restant dans la classe nommée de manière trompeuse DualPivotQuicksort. De plus, la documentation n’a pas rattrapé son retard, ce qui montre que c’est une mauvaise idée en général de nommer un algorithme utilisé en interne dans une spécification, s’il n’est pas nécessaire.

La situation actuelle (y compris Java 8 à Java 11) est la suivante:

  • En règle générale, les méthodes de tri des tableaux primitifs n'utiliseront Quicksort que dans certaines circonstances. Pour les baies plus grandes, ils essaieront d’abord d’identifier les exécutions de données prédéfinies, comme TimSort , et les fusionneront lorsque le nombre d’exécutions ne dépassera pas un certain seuil. Sinon, ils retomberont sur Quicksort , mais avec une implémentation qui retombera sur tri par insertion pour les petites plages, ce qui affecte non seulement les petits tableaux, mais également la récursivité de tri rapide. .
  • sort(char[],…) et sort(short[],…) ajoutent un autre cas spécial, à utiliser sorte de comptage pour les tableaux dont la longueur dépasse un certain seuil
  • De même, sort(byte[],…) utilisera tri par comptage , mais avec un seuil beaucoup plus petit, ce qui crée le plus grand contraste par rapport à la documentation, car sort(byte[],…) n'utilise jamais Quicksort. Il utilise uniquement tri par insertion pour les petits tableaux et tri par comptage sinon.
85
Holger

Je ne connais pas la documentation, mais l'implémentation de Java.util.Collections#sort in Java 8 (HotSpot) va comme ceci:

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

Et List#sort a cette implémentation:

@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

Donc, à la fin, Collections#sort les usages Arrays#sort (des éléments d'objet) en coulisse. Cette implémentation utilise un tri par fusion ou un tri par tim.

19
Luiggi Mendoza

Selon la Javadoc, seuls les tableaux primitifs sont triés à l'aide de Quicksort. Les tableaux d'objets sont également triés avec un Mergesort.

Donc Collections.sort semble utiliser le même algorithme de tri que Arrays.sort pour les objets.

Une autre question serait de savoir pourquoi un algorithme de tri différent est utilisé pour les tableaux primitifs par rapport aux tableaux d'objets.

15
Puce

Comme indiqué dans de nombreuses réponses.

Le fichier Quicksort est utilisé par Arrays.sort pour trier les collections de primitives car la stabilité n’est pas nécessaire (vous ne saurez pas si deux entiers identiques ont été intervertis dans le tri).

MergeSort ou plus spécifiquement Timsort est utilisé par Arrays.sort pour trier des collections d'objets. La stabilité est requise. Quicksort ne fournit pas de stabilité, Timsort en assure.

Collections.sort délègue à Arrays.sort, raison pour laquelle vous voyez le javadoc faisant référence au MergeSort.

2
cogitoboy

Le tri rapide présente deux inconvénients majeurs lorsqu'il s'agit de fusionner le tri:

  • Ce n'est pas stable quand il s'agit de non primitif.
  • Cela ne garantit pas la performance de n log n.

La stabilité n'est pas un problème pour les types primitifs, car il n'y a pas de notion d'identité distincte de l'égalité (de valeur).

La stabilité est un gros problème lors du tri d'objets arbitraires. L’avantage de Nice est que Merge Sort garantit des performances n log n (time), quelle que soit l’entrée. C'est pourquoi le tri par fusion est sélectionné pour fournir un tri stable (Tri par fusion) pour trier les références d'objet.

1
Krutik