Perché Radix Sort non viene utilizzato più spesso?


31

È stabile e presenta una complessità temporale di O (n). Dovrebbe essere più veloce di algoritmi come Quicksort e Mergesort, ma non lo vedo quasi mai usato.


2
Vedi qui: en.wikipedia.org/wiki/Radix_sort#Efficiency L'efficienza è O (kn) e potrebbe non essere migliore di O (n * log (n)).
FrustratedWithFormsDesigner,

2
L'ordinamento Radix viene spesso utilizzato nei sistemi soft in tempo reale come i giochi. Il fatto che un algoritmo superi o meno un altro dipende, come al solito, da tutti i parametri del problema, non solo dal limite di complessità
awdz9nld

@FrustratedWithFormsDesigner Forse il wiki è cambiato? Non vedo più il riferimento a `n log (n) , FWIW ...
rogerdpack

Boost ne ha una (sul posto): boost.org/doc/libs/1_62_0/libs/sort/doc/html/sort/sort_hpp.html ma sì, penso che la gente non sappia che esiste ... o quello o tutti usano semplicemente l'algoritmo di ordinamento "standard" che, per qualsiasi motivo, i creatori di framework tendono ancora a riutilizzare i tipi "generici" che non sono così efficienti ... forse non sono concentrati sugli ordinamenti in genere, dal momento che è un caso d'uso più raro?
rogerdpack,

Risposte:


38

A differenza dell'ordinamento radix, quicksort è universale, mentre l'ordinamento radix è utile solo per i numeri interi a lunghezza fissa.

Inoltre devi capire che O (f (n)) significa veramente in ordine di K * f (n), dove K è una costante arbitraria. Per l'ordinamento radix questo K sembra essere abbastanza grande (almeno l'ordine del numero di bit negli interi ordinati), d'altra parte quicksort ha uno dei K più bassi tra tutti gli algoritmi di ordinamento e la complessità media di n * log (n). Quindi, nello scenario di vita reale, quicksort sarà molto più veloce dell'ordinamento radix.


Nota sulla complessità dichiarata: sebbene l'ordinamento Radix (LSD) abbia una complessità di O (n * K), questa costante è generalmente piccola, tipicamente scelta in modo tale che (2 ^ (W / K)) * C si adatti a L1, dove C è la dimensione in byte del contatore, W la dimensione della chiave ordinata. La maggior parte delle implementazioni sceglie K = [3,4] per parole a 32 bit su x86. K può anche essere adattato per sfruttare la coerenza temporale (quasi-ordinamento), poiché ogni radice viene ordinata individualmente.
awdz9nld,

11
Nota sull'universalità: l'ordinamento Radix è pienamente in grado di operare su chiavi a virgola mobile, nonché su numeri interi a lunghezza variabile
awdz9nld

20

La maggior parte degli algoritmi di ordinamento sono di uso generale. Data una funzione di confronto, funzionano su qualsiasi cosa e algoritmi come Quicksort e Heapsort ordineranno con O (1) memoria aggiuntiva.

L'ordinamento Radix è più specializzato. È necessaria una chiave specifica in ordine lessicografico. È necessario un bucket per ogni possibile simbolo nella chiave e i bucket devono contenere molti record. (In alternativa, hai bisogno di una vasta gamma di bucket che conterrà ogni possibile valore chiave.) Probabilmente avrai bisogno di molta più memoria per eseguire l'ordinamento radix e la utilizzerai in modo casuale. Niente di tutto ciò è positivo per i computer moderni, poiché è probabile che si verifichino errori di pagina come Quicksort otterrà mancati riscontri nella cache.

Infine, in genere le persone non scrivono più i propri algoritmi di ordinamento. La maggior parte delle lingue dispone di strutture di libreria da ordinare e la cosa giusta da fare è normalmente usarle. Poiché l'ordinamento radix non è universalmente applicabile, in genere deve essere adattato all'uso effettivo e utilizza molta memoria aggiuntiva, è difficile inserirlo in una funzione di libreria o modello.


In realtà, quicksort richiede O(n^2)memoria nel peggiore dei casi a causa di nchiamate ricorsive sulle partizioni sinistra e destra. Se l'implementazione utilizza l'ottimizzazione della ricorsione della coda, è possibile ridurla solo O(n)perché le chiamate alla partizione corretta non richiedono spazio aggiuntivo. ( en.wikipedia.org/wiki/Quicksort#Space_complexity )
Scheggia del caos

È necessario solo S(n) \in O(n)spazio per l'ordinamento con radix, vale a dire come per l'heap o l'ordinamento rapido.
Velda,

@SplinterofChaos è forse cambiata la wiki? Non sembra più parlare n^2di quicksort, ma O(log n)...
rogerdpack il

Non penso che sia "molto" più memoria, forse 2 * n (OK, è molto di più ma forse non impossibile)? E i bucket sono così piccoli (supponendo che tu stia dividendo in byte e ricorrendo) che potrebbero adattarsi bene alla cache?
rogerdpack,

5

È abbastanza raro che le chiavi ordinate siano in realtà numeri interi in un intervallo noto e scarso. Di solito hai campi alfabetici, che sembrano supportare un ordinamento non comparativo, ma poiché le stringhe del mondo reale non sono distribuite uniformemente in tutto l'alfabeto, questo non funziona come dovrebbe in teoria.

Altre volte, il criterio è definito solo a livello operativo (dati due record, è possibile decidere quale viene prima, ma non è possibile valutare quanto un record isolato sia "lontano" rispetto alla scala). Quindi il metodo spesso non è applicabile, meno applicabile di quanto si possa pensare o semplicemente non più veloce di O (n * log (n)).


L'ordinamento Radix può gestire numeri interi (o stringhe) in qualsiasi intervallo ordinandoli in modo ricorsivo "un byte alla volta" in modo che non debbano trovarsi in un intervallo sparso FWIW ...
rogerdpack

4

Lo uso sempre, in realtà più di una sorta di confronto, ma devo ammettere che è una strana palla che funziona più con i numeri che con qualsiasi altra cosa (a malapena lavoro con le stringhe, e in genere sono internati se sì, a quel punto radix l'ordinamento può essere di nuovo utile per filtrare i duplicati e calcolare le intersezioni impostate; praticamente non faccio mai confronti lessicografici).

Un esempio di base sono i punti di ordinamento radix per una data dimensione come parte di una ricerca o divisione mediana o un modo rapido per rilevare punti coincidenti, frammenti di ordinamento di profondità o ordinamento radicale di una matrice di indici utilizzati in più loop per fornire un accesso più intuitivo alla cache pattern (non andare avanti e indietro in memoria solo per tornare indietro e ricaricare la stessa memoria in una riga della cache). Esiste un'applicazione molto ampia almeno nel mio dominio (computer grafica) solo per l'ordinamento su chiavi numeriche a 32 e 64 bit di dimensioni fisse.

Una cosa che volevo presentare e dire è che l'ordinamento radix può funzionare su numeri e negativi in ​​virgola mobile, anche se è difficile scrivere una versione FP il più portatile possibile. Inoltre, mentre è O (n * K), K deve essere solo il numero di byte della dimensione della chiave (es: un milione di numeri interi a 32 bit richiederebbe generalmente 4 byte di dimensione se ci sono 2 ^ 8 voci nel bucket ). Il modello di accesso alla memoria tende inoltre ad essere molto più compatibile con la cache rispetto agli quicksorts anche se in genere ha bisogno di un array parallelo e di un piccolo array bucket (il secondo può in genere adattarsi perfettamente allo stack). QS potrebbe eseguire 50 milioni di swap per ordinare un array di un milione di numeri interi con schemi di accesso casuale sporadici. L'ordinamento radix può farlo in 4 passaggi lineari e compatibili con la cache sui dati.

Tuttavia, la mancanza di consapevolezza di poterlo fare con una piccola K, su numeri negativi insieme a virgola mobile, potrebbe benissimo contribuire significativamente alla mancanza di popolarità dei tipi di radix.

Per quanto riguarda la mia opinione sul perché le persone non lo usano più spesso, potrebbe avere a che fare con molti domini che generalmente non hanno la necessità di ordinare i numeri o usarli come chiavi di ricerca. Tuttavia, basandomi solo sulla mia esperienza personale, molti dei miei ex colleghi non l'hanno utilizzata nemmeno nei casi in cui era perfettamente adatto, e in parte perché non erano consapevoli che potesse essere fatto funzionare su FP e negativi. Quindi, a parte il fatto che funziona solo su tipi numerici, si pensa spesso che sia persino meno generalmente applicabile di quanto non sia in realtà. Non lo userei quasi nemmeno se pensassi che non funzionasse su numeri in virgola mobile e numeri interi negativi.

Alcuni parametri di riferimento:

Sorting 10000000 elements 3 times...

mt_sort_int: {0.135 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

mt_radix_sort: {0.228 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

std::sort: {1.697 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

qsort: {2.610 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

E questo è solo con la mia ingenua implementazione ( mt_sort_intè anche l'ordinamento radix ma con un ramo di codice più veloce dato che può assumere che la chiave sia un numero intero). Immagina quanto potrebbe essere veloce un'implementazione standard scritta da esperti.

L'unico caso in cui ho trovato che l'ordinamento radix è peggiore di quello basato sul confronto veramente veloce di C ++ è std::sortstato per un numero molto piccolo di elementi, diciamo 32, a quel punto credo che std::sortinizi a usare tipi più adatti al numero più piccolo di elementi come heapsorts o specie di inserzione, anche se a quel punto la mia implementazione usa solo std::sort.


1
Sempre bello ascoltare le opinioni delle persone con esperienza nella zona.
Frank Hileman,

Sembra che mt_ siano implementazioni multi-thread: softwareengineering.stackexchange.com/a/362097/65606
rogerdpack

1

Un motivo in più: oggigiorno l'ordinamento è di solito implementato con una routine di ordinamento fornita dall'utente collegata alla logica di ordinamento fornita dal compilatore. Con un ordinamento radix questo sarebbe notevolmente più complesso e peggiorerebbe ulteriormente quando la routine di ordinamento agisce su più chiavi di lunghezza variabile. (Dire, nome e data di nascita.)

Nel mondo reale ho effettivamente implementato una sorta di radix una volta. Questo era ai vecchi tempi quando la memoria era limitata, non riuscivo a portare tutti i miei dati in memoria contemporaneamente. Ciò significava che il numero di accessi ai dati era molto più importante di O (n) vs O (n log n). Ho fatto un passaggio tra i dati assegnando ogni record a un cestino (da un elenco di quali record si trovavano in quali scomparti, in realtà non spostavano nulla). Per ogni cestino non vuoto (la mia chiave di ordinamento era il testo, ci sarebbero stati molti scomparti vuoti) Ho verificato se potevo effettivamente portare i dati in memoria - in caso affermativo, inseriscili e usa quicksort. In caso contrario, crea un file temporaneo contenente solo gli elementi nel cestino e chiama la routine in modo ricorsivo. (In pratica, pochi bin traboccerebbero.) Ciò causava due letture complete e una scrittura completa nella memoria di rete e qualcosa come il 10% di questa nella memoria locale.

In questi giorni problemi di big data sono molto più difficili da affrontare, probabilmente non scriverò mai più niente del genere. (Se dovessi affrontare gli stessi dati in questi giorni, vorrei semplicemente specificare un sistema operativo a 64 bit, aggiungere RAM se si verifica un thrashing in quell'editor.)


Affascinante considerando uno degli svantaggi menzionati per radix sort a volte menzionato è "occupa più spazio".
Sto

1
@rogerdpack Non è che il mio approccio abbia usato meno spazio, è che ha usato meno accesso ai dati. Stavo ordinando un file che era attorno a un gigabyte mentre gestivo un limite del compilatore (questa era la modalità protetta DOS, non Windows) di un po 'meno di 16 MB di utilizzo della memoria totale incluso codice e un limite di struttura di 64kb.
Loren Pechtel,

-1

Se tutti i tuoi parametri sono tutti numeri interi e se hai oltre 1024 parametri di input, l'ordinamento radix è sempre più veloce.

Perché?

Complexity of radix sort = max number of digits x number of input parameters.

Complexity of quick sort = log(number of input parameters) x   number of input parameters

Quindi l'ordinamento radix è più veloce quando

log(n)> max num of digits

Il numero intero massimo in Java è 2147483647. Che è lungo 10 cifre

Quindi il radix sort è sempre più veloce quando

log(n)> 10

Pertanto l' ordinamento radix è sempre più veloce quando n>1024


Ci sono costanti nascoste nei dettagli dell'implementazione, ma fondamentalmente stai dicendo "per un più grande input radix l'ordinamento è più veloce" che ... dovrebbe essere il caso! È solo difficile trovare casi d'uso per questo, ma quando puoi ...
rogerdpack
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.