it-swarm.dev

Warum wird Radix Sort nicht häufiger verwendet?

Es ist stabil und hat eine zeitliche Komplexität von O (n). Es sollte schneller sein als Algorithmen wie Quicksort und Mergesort, aber ich sehe es kaum jemals verwendet.

32
Queequeg

Im Gegensatz zur Radix-Sortierung ist die Quicksortierung universell, während die Radix-Sortierung nur für Ganzzahlschlüssel mit fester Länge nützlich ist.

Sie müssen auch verstehen, dass O(f(n)) wirklich in der Reihenfolge von K * f (n) bedeutet, wobei K eine beliebige Konstante ist. Für die Radix-Sortierung ist dieses K zufällig ziemlich groß (mindestens die Reihenfolge der Anzahl der Bits in den sortierten Ganzzahlen), andererseits hat Quicksort eines der niedrigsten K unter allen Sortieralgorithmen und eine durchschnittliche Komplexität von n * log (n). Somit wird Quicksort im realen Szenario sein sehr oft schneller als radix sort.

40
vartec

Die meisten Sortieralgorithmen sind universell einsetzbar. Bei einer Vergleichsfunktion funktionieren sie mit allem, und Algorithmen wie Quicksort und Heapsort sortieren mit O(1) zusätzlichem Speicher).

Die Radix-Sortierung ist spezialisierter. Sie benötigen einen bestimmten Schlüssel in lexikografischer Reihenfolge. Sie benötigen einen Bucket für jedes mögliche Symbol im Schlüssel, und die Buckets müssen viele Datensätze enthalten. (Alternativ benötigen Sie eine große Anzahl von Buckets, die jeden möglichen Schlüsselwert enthalten.) Sie benötigen wahrscheinlich viel mehr Speicher, um die Radix-Sortierung durchzuführen, und Sie werden ihn zufällig verwenden. Beides ist für moderne Computer nicht gut, da es wahrscheinlich zu Seitenfehlern wie Quicksort kommt, die zu Cache-Fehlern führen.

Schließlich schreiben die Leute im Allgemeinen keine eigenen Sortieralgorithmen mehr. Die meisten Sprachen verfügen über Bibliothekseinrichtungen zum Sortieren, und das Richtige ist normalerweise, sie zu verwenden. Da die Radix-Sortierung nicht universell einsetzbar ist, normalerweise auf die tatsächliche Verwendung zugeschnitten werden muss und viel zusätzlichen Speicher benötigt, ist es schwierig, sie in eine Bibliotheksfunktion oder -vorlage zu integrieren.

20
David Thornley

Es ist ziemlich selten, dass die Schlüssel, nach denen Sie sortieren, tatsächlich Ganzzahlen in einem bekannten, spärlichen Bereich sind. Normalerweise haben Sie alphabetische Felder, die aussehen so sind, als würden sie eine nicht vergleichende Sortierung unterstützen, aber da reale Zeichenfolgen nicht gleichmäßig über das Alphabet verteilt sind, funktioniert dies nicht so gut, wie es sollte Theorie.

In anderen Fällen wird das Kriterium nur operativ definiert (bei zwei Datensätzen können Sie entscheiden, welcher zuerst kommt, aber Sie können nicht beurteilen, wie weit ein isolierter Datensatz von der Skala entfernt ist). Daher ist die Methode oft nicht anwendbar, weniger anwendbar als Sie vielleicht glauben oder einfach nicht schneller als O (n * log (n)).

5
Kilian Foth

Ich benutze es die ganze Zeit, eigentlich mehr als vergleichsbasierte Sorten, aber ich bin zugegebenermaßen ein seltsamer Ball, der mehr mit Zahlen als mit irgendetwas anderem arbeitet (ich arbeite kaum jemals mit Strings, und sie sind im Allgemeinen interniert, wenn ja, an welchem ​​Punkt Radix Das Sortieren kann wieder nützlich sein, um Duplikate herauszufiltern und festgelegte Schnittpunkte zu berechnen (ich mache praktisch nie lexikografische Vergleiche).

Ein grundlegendes Beispiel sind Radix-Sortierpunkte nach einer bestimmten Dimension als Teil einer Suche oder einer Medianaufteilung oder eine schnelle Methode zum Erkennen von übereinstimmenden Punkten, Tiefensortierfragmenten oder Radix-Sortieren eines Arrays von Indizes, die in mehreren Schleifen verwendet werden, um einen cachefreundlicheren Zugriff zu ermöglichen Muster (nicht im Speicher hin und her gehen, nur um wieder zurück zu gehen und denselben Speicher in eine Cache-Zeile neu zu laden). Zumindest in meiner Domäne gibt es eine sehr breite Anwendung (Computergrafik), die nur zum Sortieren nach 32-Bit- und 64-Bit-Zifferntasten fester Größe geeignet ist.

Eine Sache, die ich einbringen und sagen wollte, ist, dass die Radix-Sortierung mit Gleitkommazahlen und Negativen funktionieren kann, obwohl es schwierig ist, eine FP -Version) zu schreiben, die so portabel wie möglich ist O (n * K), K muss nur die Anzahl der Bytes der Schlüsselgröße sein (Beispiel: Eine Million 32-Bit-Ganzzahlen würden im Allgemeinen 4 Byte-große Durchgänge benötigen, wenn sich 2 ^ 8 Einträge im Bucket befinden) Das Speicherzugriffsmuster ist in der Regel auch viel cachefreundlicher als Quicksorts, obwohl es normalerweise ein paralleles Array und ein kleines Bucket-Array benötigt (das zweite kann normalerweise gut auf den Stapel passen). QS führt möglicherweise 50 Millionen Swaps aus, um eine zu sortieren Array von einer Million Ganzzahlen mit sporadischen Direktzugriffsmustern. Die Radix-Sortierung kann dies in 4 linearen, cachefreundlichen Durchläufen über die Daten tun.

Das mangelnde Bewusstsein, dies mit einem kleinen K bei negativen Zahlen zusammen mit Gleitkommazahlen tun zu können, könnte jedoch sehr wohl erheblich zur mangelnden Popularität von Radix-Sorten beitragen.

Meine Meinung dazu, warum Benutzer es nicht häufiger verwenden, hat möglicherweise damit zu tun, dass viele Domains im Allgemeinen keine Nummern sortieren oder als Suchschlüssel verwenden müssen. Aufgrund meiner persönlichen Erfahrung haben viele meiner ehemaligen Kollegen es jedoch auch nicht in Fällen verwendet, in denen es perfekt geeignet war, und teilweise, weil sie nicht wussten, dass es für die Arbeit an FP und Negative. Abgesehen davon, dass es nur mit numerischen Typen funktioniert, wird es oft als gerade weniger allgemein anwendbar angesehen, als es tatsächlich ist. Ich hätte nicht annähernd so viel Verwendung dafür entweder wenn ich dachte, dass es bei Gleitkommazahlen und negativen ganzen Zahlen nicht funktioniert.

Einige Benchmarks:

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 ]

Und das ist nur mit meiner naiven Implementierung (mt_sort_int ist ebenfalls eine Radix-Sortierung, jedoch mit einem schnelleren Codezweig, da davon ausgegangen werden kann, dass der Schlüssel eine Ganzzahl ist. Stellen Sie sich vor, wie schnell eine von Experten geschriebene Standardimplementierung sein könnte.

Der einzige Fall, in dem ich fand, dass die Radix-Sortierung schlechter abschneidet als C++ 's wirklich schnelles vergleichendes std::sort war für eine wirklich kleine Anzahl von Elementen, sagen wir 32, an welchem ​​Punkt ich glaube std::sort beginnt mit der Verwendung von Sortierungen, die besser für die kleinste Anzahl von Elementen wie Heapsorts oder Einfügungssortierungen geeignet sind, obwohl meine Implementierung zu diesem Zeitpunkt nur std::sort.

4
user204677

Ein weiterer Grund: Heutzutage wird das Sortieren normalerweise mit einer vom Benutzer bereitgestellten Sortierroutine implementiert, die an die vom Compiler bereitgestellte Sortierlogik angehängt ist. Bei einer Radix-Sortierung wäre dies erheblich komplexer und würde noch schlimmer, wenn die Sortierroutine auf mehrere Schlüssel variabler Länge einwirkt. (Sagen Sie, Name und Geburtsdatum.)

In der realen Welt habe ich tatsächlich eine Radix-Sortierung implementiert einmal. Dies war in den alten Tagen, als der Speicher begrenzt war, ich konnte nicht alle meine Daten auf einmal in den Speicher bringen. Das bedeutete, dass die Anzahl der Zugriffe auf die Daten weitaus wichtiger war als O(n) vs O (n log n). Ich habe einen Durchgang über die Daten durchgeführt, wobei jeder Datensatz einem Bin zugewiesen wurde ( Anhand einer Liste, welche Datensätze sich in welchen Behältern befanden und nichts bewegten.) Für jeden nicht leeren Behälter (mein Sortierschlüssel war Text, es gab viele leere Behälter) überprüfte ich, ob ich die Daten tatsächlich in den Speicher bringen konnte --wenn ja, bringen Sie es ein und verwenden Sie Quicksort. Wenn nein, erstellen Sie eine temporäre Datei, die nur die Elemente im Bin enthält, und rufen Sie die Routine rekursiv auf. (In der Praxis würden nur wenige Bins überlaufen.) Dies verursachte zwei vollständige Lesevorgänge und einen vollständigen Schreibvorgang zum Netzwerkspeicher und ungefähr 10% davon zum lokalen Speicher. Wenn ich einfach die gesamte Datei schnell sortiere, würde dies meiner Meinung nach etwa 2 * n log n Lesevorgänge und etwa halb so viele Schreibvorgänge verursachen - erheblich langsamer.

Heutzutage sind solche Big-Data-Probleme weitaus schwieriger zu lösen. Ich werde wahrscheinlich nie wieder so etwas schreiben. (Wenn ich heutzutage mit denselben Daten konfrontiert wäre, würde ich einfach ein 64-Bit-Betriebssystem angeben und RAM, wenn Sie in diesem Editor einen Fehler machen) hinzufügen.)

1
Loren Pechtel