Perché Collections.sort usa Mergesort ma Arrays.sort no?


96

Sto usando JDK-8 (x64). Per Arrays.sort(primitive) ho trovato quanto segue nella documentazione Java:

L'algoritmo di ordinamento è un Dual-Pivot Quicksort di Vladimir Yaroslavskiy, Jon Bentley e Joshua Bloch.

Per Collections.sort(oggetti) ho trovato questo "Timsort":

Questa implementazione è un mergesort stabile, adattivo e iterativo ... Questa implementazione scarica l'elenco specificato in un array, ordina l'array e itera sull'elenco ripristinando ogni elemento dalla posizione corrispondente nell'array.

Se Collections.sortutilizza un array, perché non chiama Arrays.sorto utilizza QuickSort dual-pivot ? Perché usare Mergesort ?


8
Questo è il javadoc per gli array di primitive: gli array di oggetti vengono ordinati usando meregsort.
assylias

2
mergesort ti dà sempre u nlogn mentre quicksort a volte può dare nlogn2 geneally arrays la dimensione non è così grande ma le raccolte arrivano facilmente fino a milioni di voci quindi correre il rischio di nlogn2 non vale PS nlogn2 volevo dire sqaure di n
Kumar Saurabh

O (n ^ 2) per Quicksort è il caso peggiore estremo. In pratica è più veloce
James Wierzba

ma non puoi ignorare quei caese mentre crei un'api
Kumar Saurabh

2
Questo collegamento è molto correlato.
qartal

Risposte:


99

L'API garantisce uno smistamento stabile che Quicksort non offre. Tuttavia, quando si ordinano i valori primitivi in base al loro ordine naturale, non si noterà alcuna differenza poiché i valori primitivi non hanno identità. Pertanto, Quicksort può essere utilizzato per array primitivi e verrà utilizzato quando sarà considerato più efficiente¹.

Per gli oggetti che potresti notare, quando oggetti con identità diversa che sono ritenuti uguali in base alla loro equalsimplementazione o a quanto fornito Comparatorcambiano il loro ordine. Pertanto, Quicksort non è un'opzione. Quindi viene utilizzata una variante di MergeSort , le versioni Java correnti utilizzano TimSort . Questo vale per entrambi Arrays.sorte Collections.sort, sebbene con Java 8, lo Liststesso potrebbe sovrascrivere gli algoritmi di ordinamento.


¹ Il vantaggio in termini di efficienza di Quicksort è la necessità di una minore quantità di memoria quando viene eseguito sul posto. Ma ha prestazioni drammatiche nel caso peggiore e non può sfruttare esecuzioni di dati preordinati in un array, cosa che fa TimSort .

Pertanto, gli algoritmi di ordinamento sono stati rielaborati da versione a versione, pur rimanendo nella classe ora chiamata in modo fuorviante DualPivotQuicksort. Inoltre, la documentazione non ha recuperato, il che mostra che in generale è una cattiva idea nominare un algoritmo utilizzato internamente in una specifica, quando non è necessario.

La situazione attuale (inclusi Java 8 e Java 11) è la seguente:

  • In genere, i metodi di ordinamento per gli array primitivi utilizzeranno Quicksort solo in determinate circostanze. Per array più grandi, cercheranno di identificare prima le esecuzioni di dati preordinati, come fa TimSort , e le uniranno quando il numero di esecuzioni non supera una certa soglia. Altrimenti torneranno a Quicksort , ma con un'implementazione che ricadrà sull'ordinamento di inserimento per piccoli intervalli, che non riguarda solo i piccoli array, ma anche la ricorsione dell'ordinamento rapido.
  • sort(char[],…)e sort(short[],…)aggiungere un altro caso speciale, per utilizzare l' ordinamento conteggio per array la cui lunghezza supera una certa soglia
  • Allo stesso modo, sort(byte[],…)utilizzerà l' ordinamento conteggio , ma con una soglia molto più piccola, che crea il maggiore contrasto con la documentazione, poiché sort(byte[],…)non utilizza mai Quicksort. Utilizza solo l' ordinamento di inserimento per piccoli array e l' ordinamento conteggio in caso contrario.

1
Hmm, in modo interessante il Javadoc Collections.sort afferma: "Questo ordinamento è garantito per essere stabile", ma poiché delega a List.sort, che può essere sovrascritto dalle implementazioni di lista, l'ordinamento stabile non può essere realmente garantito da Collections.sort per tutta la lista implementazioni. O mi manca qualcosa? E List.sort non richiede che l'alogirthm di ordinamento sia stabile.
Puce

11
@ Puce: ciò significa semplicemente che la responsabilità di quella garanzia ora è nelle mani di coloro che implementano il List.sortmetodo prioritario . Collections.sortnon potrebbe mai garantire il corretto funzionamento per ogni Listimplementazione in quanto non può garantire, ad esempio, che il Listnon cambia falsamente il suo contenuto. Tutto si riduce al fatto che la garanzia di Collections.sortsi applica solo alle Listimplementazioni corrette (e corrette Comparatoro equalsimplementazioni).
Holger

1
@ Puce: Ma hai ragione, Javadoc non è ugualmente esplicito su questo vincolo in entrambi i metodi, ma almeno la documentazione più recente afferma che Collections.sortdelegherà a List.sort.
Holger

@ Puce: ci sono tantissimi esempi di questo, dove proprietà importanti non fanno parte del tipo ma sono solo menzionate nella documentazione (e quindi non controllate dal compilatore). Il sistema di tipi di Java è semplicemente troppo debole per esprimere proprietà interessanti. (Non è molto diverso da un linguaggio tipizzato dinamicamente in questo senso, anche lì le proprietà sono definite nella documentazione e spetta al programmatore assicurarsi che non vengano violate.) Va anche oltre, in realtà: hai notato che Collections.sortnon menziona nemmeno nella sua firma del tipo che l'output è ordinato?
Jörg W Mittag

1
In un linguaggio con un sistema di tipi più espressivo, il tipo restituito Collections.sortsarebbe qualcosa come "una raccolta dello stesso tipo e lunghezza dell'input con le proprietà che 1) ogni elemento presente nell'input è presente anche nell'output, 2 ) per ogni coppia di elementi dall'output, quello di sinistra non è maggiore di quello di destra, 3) per ogni coppia di elementi uguali dall'output, l'indice di sinistra nell'ingresso è più piccolo di quello di destra "o qualcosa di simile quello.
Jörg W Mittag

20

Non conosco la documentazione, ma l'implementazione di java.util.Collections#sortin Java 8 (HotSpot) funziona così:

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

E List#sortha questa implementazione:

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

Quindi, alla fine, Collections#sortusa Arrays#sort(degli elementi oggetto) dietro le quinte. Questa implementazione usa merge sort o tim sort.


16

Secondo Javadoc, solo gli array primitivi vengono ordinati utilizzando Quicksort. Anche gli array di oggetti vengono ordinati con un Mergesort.

Quindi Collections.sort sembra utilizzare lo stesso algoritmo di ordinamento di Arrays.sort per gli oggetti.

Un'altra domanda sarebbe perché per gli array primitivi viene utilizzato un algoritmo di ordinamento diverso rispetto agli array di oggetti?


2

Come affermato in molte delle risposte.

Il Quicksort viene utilizzato da Arrays.sort per ordinare le raccolte primitive perché la stabilità non è richiesta (non saprai né ti interesserà se due int identici sono stati scambiati nell'ordinamento)

MergeSort o più specificamente Timsort viene utilizzato da Arrays.sort per ordinare le raccolte di oggetti. È richiesta stabilità. Quicksort non fornisce stabilità, Timsort sì.

Collections.sort delega ad Arrays.sort, motivo per cui vedi javadoc che fa riferimento a MergeSort.


1

L'ordinamento rapido ha due principali svantaggi quando si tratta di unire l'ordinamento:

  • Non è stabile mentre si tratta di non primitivo.
  • Non garantisce n log n prestazioni.

La stabilità non è un problema per i tipi primitivi, poiché non esiste una nozione di identità distinta dall'uguaglianza (di valore).

La stabilità è un grosso problema quando si ordinano oggetti arbitrari. È un bel vantaggio collaterale che Merge Sort garantisce n log n (tempo) prestazioni indipendentemente dall'input. Ecco perché viene selezionato l'ordinamento di unione per fornire un ordinamento stabile (ordinamento di unione) per ordinare i riferimenti agli oggetti.


1
Cosa intendi con "Non stabile"?
Arun Gowda
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.