Schnellstes Sortierverfahren für 1 Million Integer

hallo!

Ich hatte heute einen Einfall zu einem Sortierverfahren.

In Python wird eine Liste mit 1 Million Zufallintegern erstellt:
anzahl=1000000
for i in range(0,anzahl):
zufall.append(randint(0,anzahl))

Für die Sortierung dieser Liste braucht mein Verfahren ca. 1 Sekunde auf meinem Rechner.

Als Vergleich habe ich mal eine Pythonimplementierung von Quicksort ausm Internet kopiert:
def qsortr(list):
qsortr([x for x in list[1:] if x = list[0]])
http://en.literateprograms.org/Quicksort_%28Python%29

Mit dieser Quicksortimplementierung brauche ich für die gleiche Liste ca. 15 Sekunden.

Außerdem habe ich noch die in Python eingebaute Sortierfunktion .sort() getestet, ich habe gelesen, dass es sich dabei um eine Version von mergesort handeln soll.

bei 1 Millionen Zufallsintegern:
meine: 1 Sekunde
.sort(): 1,3 Sekunden
Quicksort: 15 Sekunden

bei 2 Millionen Zufallsintegern:
meine: 1,5 Sekunden
.sort(): 2,5 Sekunden
Quicksort: 27 Sekunden

bei 3 Millionen Zufallsintegern:
meine: 2,5 Sekunden
.sort(): 4 Sekunden
Quicksort: 45 Sekunden

Die Zeiten sind nicht wirklich statisitsch gemittelt und schwanken von Versuch zu Versuch, doch sie treffen in etwa zu und mein Verfahren ist immer schneller als .sort() und Quicksort.

Bei wikipedia gibt es ja eine ganze Liste von Sortierverfahren.
http://de.wikipedia.org/wiki/Sortierverfahren

Wenn es sein muss, programmiere ich auch alle 20 Verfahren, aber ich wollte vorher mal fragen, ob ich mir das sparen kann. Wenn mein Algorithmus schneller als der in Python eingebaute Algorithmus ist, gibt es dennoch einen schnelleren Algorithmus?
Welches ist der schnellste zum Sortieren von 1-Million Zufallsintegern?

Gruß
Paul

Hi,

In Python wird eine Liste mit 1 Million Zufallintegern
erstellt:
anzahl=1000000
for i in range(0,anzahl):
zufall.append(randint(0,anzahl))

ich kann zwar kein Python, aber ich lese das so, dass der größte auftretende Integer maximal eine Million ist. In diesem Fall würde sich der Algorithmus CountingSort anbieten:
http://de.wikipedia.org/wiki/Countingsort

Dessen Aufwand wächst asymptotisch für viele Elemente nur linear. Allerdings beansprucht er umso mehr Speicher, je größer das Intervall ist, aus dem die Elemente stammen. Daher kann er für deine eine Million Zahlen recht gut sein - wenn du allerdings Zufallszahlen aus dem ganzen möglichen Bereichs des Integers nimmst, kannste das vergessen.

Viele Grüße

Manny

ich kann zwar kein Python, aber ich lese das so, dass der
größte auftretende Integer maximal eine Million ist.

richtig
Es werden 1 Million Zufallszahlen erzeugt, jede dieser Zufallszahlen kann einen Wert zwischen 0 und 1.000.000 annehmen.

In diesem
Fall würde sich der Algorithmus CountingSort anbieten:
http://de.wikipedia.org/wiki/Countingsort

alles klar, das werde ich nachher ausprobieren und dann mal die Ergebnisse hier posten

Jetzt muss ich mich erstmal mit den äußeren Zwängen meiner irdischen Umwelt auseinandersetzen.

Gruß
Paul

Hallo,

so recht werde ich aus dem Zweck der Frage nicht schlau. Möchtest du wissen, ob du ein geniales Sortierverfahren entwickelt hast, das alles Bekannte in den Schatten stellt? Unwahrscheinlich. Knuth, Art of Computer Programming, Band 3 beschäftigt sich auf vielen hundert Seiten nur mit solchen Verfahren und da spielen viele Faktoren hinein, u. a. der Grad an Sortiertheit vorher, ob das Sortierverfahren stabil sein soll, wie viel zusätzlicher Speicher verwendet werden darf.
Ohne Abschätzung der Laufzeitkomplexität deines Algorithmus anhand der Anzahl der Vergleichsoperationen und Vertauschungen kann man da wenig argumentieren.
Die angegebene Routine für Quicksort ist zwar kurz und zeigt ein paar Python-Spezialitäten, ist performance-technisch aber ziemlich schlecht: unter anderem rumpelt sie zwei Mal durch die Ausgangsliste um einmal die kleineren und einmal die größeren Werte herauszufiltern und jeweils in eine neue Liste zu stecken (für richtig große Listen ist das ohnehin nicht praktikabel). Ich schätze, dass man damit im Wesentlichen die Zeit zum Listenaufbau messen kann. Oder, wie du richtig gemessen hast: eine schlechte Implementierung kann den besten Algorithmus ruinieren.
Ich teile die vorher geäußerte Einschätzung, dass in diesem speziellen Szenario (im Schnitt kommt jede Zahl einmal in der Liste vor) ein Zählverfahren am schnellsten ist.

Grüße, guidot

Äpfel und Birnen
Hallo Paul,

ich befürchte, Du vergleichst Äpfel und Birnen.

Die Laufzeit von Programmen hängt natürlich nicht nur vom implementierten Algorithmus ab, sondern viel stärker von den Rahmenbedingungen, die durch die Laufzeitumgebung vorgegeben sind.
Insbesondere wenn Du dabei zum Vergleich Programmierumgebungen verwendest, die eigentlich nicht für diese Sorte von Problemen gedacht sind.
Entsprechend passiert es schnell, dass nicht der beste Algorithmus, sondern die für die Problemklasse schnellste Laufzeitumgebung gewinnt.

Möchtest Du wirklich die „Güte“ eines Sortieralgorithmus bewerten, geht das furchtbar einfach: Messe nicht die Zeit, sondern zähle die Anzahl der notwendigen Prozessschritte: Anzahl Vergleichoperationen, Anzahl der Vertauschungsoperation.

Wenn Du Laufzeitoptimierung betreiben willst, bist Du immer gezwungen, bis tief hinab in die technische Abbildung zu schauen. Selbst die Optimierung auf bestimmte Schwächen und Stärken eines Prozessors oder eines Caches hat erheblichen Einfluss auf die Geschwindigkeit eines solch maschinennahen Problems wie Sortierung von Integerarrays. Für die Praxis ist das meist irrelevant, weil die meisten Sortierungen nicht nur Integerwerte in Reihe bringen müssen, sondern andere Tabellen indizieren.

Ciao, Allesquatsch

Hossa :smile:

Sortierverfahren, die auf einem paarweisen Vergleich von Elementen beruhen, sind alle relativ langsam. Bei n Elementen gibt es n! (=Fakultät) Möglichkeiten, diese Elemente anzuordnen. Wenn ich diese n! Kombinationen in einen Entscheidungsbaum packe, dann hat dieser Baum die Höhe log2(n!). Also brauche ich mindestens so viele Vergleiche, um die richtige Kobmination zu finden. Die Mindestanzahl der Vergleiche V(n) beträgt daher:

V(n)=\log_2(n!)

Mit der Stirling-Formel:

n!\approx\sqrt{2\pi,n}\left(\frac{n}{e}\right)^n

Kann man die Anzahl der Vergleiche abschätzen:

V(n)\approx\log_2\left(\sqrt{2\pi,n}\left(\frac{n}{e}\right)^n\right)

V(n)\approx\log_2\left(\sqrt{2\pi,n}\right)+\log_2\left(\left(\frac{n}{e}\right)^n\right)

Den ersten Logarithmus kann man wegen seiner Kleinheit im Vergleich zum zweiten vernachlässigen und erhält dann:

V(n)\approx n\cdot\log_2\left(\frac{n}{e}\right)

Mit dieser Quicksortimplementierung brauche ich für die
gleiche Liste ca. 15 Sekunden.

Quicksort ist wohl das langsamste aller gängigen Sortierverfahren! Im schlimmsten Fall benötigt es sogar n² Vergleiche. Das ist deutlich schlechter als das gerade berechnete theoretische Minimum.

Außerdem habe ich noch die in Python eingebaute
Sortierfunktion .sort() getestet, ich habe gelesen, dass es
sich dabei um eine Version von mergesort handeln soll.

Merge-Sort ist in dem Sinne schneller als Quicksort als dass es auch im schlimmsten Fall n*log(n) Vergleiche benötigt. Aber die Rumkopiererei ist sehr zeitaufwändig.

Wenn mein Algorithmus schneller als der in Python eingebaute
Algorithmus ist, gibt es dennoch einen schnelleren
Algorithmus?

Das schnellste Sortierverfahren, das auf paarweisen Vergleichen von Elementen beruht ist Bottom-up-Heapsort.

Schneller kann man nur noch sortieren, wenn man auf den paarweisen Vergleich der Elemente verzichtet. Es gibt Sortierverfahren, bei denen die Anzahl der benötigten Vergleiche nur linear mit der Anzahl der Elemente ansteigt. Das bekannteste davon ist Bucket-Sort. Es sollen z.B. folgende Zahlen sortiert werden:

167, 157, 121, 942, 237, 558, 333, 721, 49, 888

Beim Bucket-Sort werden 10 Töpfe bereit gestellt, einer für jede der Ziffern 0 bis 9. Beim Sortieren werden im ersten Schritt nur die letzten Ziffern der zu sortierenden Zahlen betrachtet und anhand dieses Kriteriums in die Töpfe geworfen:

Topf 0: —
Topf 1: 121, 721
Topf 2: 942
Topf 3: 333
Topf 4: —
Topf 5: —
Topf 6: —
Topf 7: 167, 157, 237
Topf 8: 558, 888
Topf 9: 49

Danach werden die Zahlen der Reihe nach (die Reihenfolge muss erhalten bleiben) wieder aus den Töpfen genommen. Das ergibt die Reihenfolge:

121, 721, 942, 333, 167, 157, 237, 558, 888, 49

Nun wird der gleiche Vorgang mit der 2-ten Ziffer von rechts durchlaufen:

Topf 0: —
Topf 1: —
Topf 2: 121, 721
Topf 3: 333, 237
Topf 4: 942, 49
Topf 5: 157, 558
Topf 6: 167
Topf 7: —
Topf 8: 888
Topf 9: —

Wieder der Reihe nach rausnehmen:

121, 721, 333, 237, 942, 49, 157, 558, 167, 888

Und nun der letzte Schritt mit der 3-ten Ziffer von rechts, wobei bei der 49 vorne eine „0“ (Null) aufgefüllt wird:

Topf 0: 049
Topf 1: 121, 157, 167
Topf 2: 237
Topf 3: 333
Topf 4: —
Topf 5: 558
Topf 6: —
Topf 7: 721
Topf 8: 888
Topf 9: 942

Rausnehmen und fertig ist die Sortierung:

049, 121, 157, 167, 237, 333, 558, 721, 888, 942

Bucket-Sort beruht nicht auf paarweisen Vergleichen der Elemente und kann daher die Grenze der oben berechneten Mindestanzahl von Vergleichen unterschreiten. Hier werden 3*n Vergleiche benötigt (n Ziffern mit maximal 3 Stellen).

Viele Grüße

Hasenfuß