Entwickler-Ecke
Algorithmen, Optimierung und Assembler - schnellstes Sortierverfahren für Strings
Heiko - Mi 05.10.05 15:21
Titel: schnellstes Sortierverfahren für Strings
Kann jemand vlt. mir sagen, welches Sortierverfahren am schnellsten ist für Strings, denn QuickSort ist ja bekanntlich nicht die schnellste Variante. Die Anzahl der Strings beläuft sich da von 0-10 000, wobei es meistens im Bereich von 500-2000 ist. ;)
PS: Am liebsten wäre es mit gleich mit einem Link zu der Erklärung des Verfahrens und am besten gleich mit Quelltext, der aber nicht zwingend dabei sein muss ;).
uall@ogc - Mi 05.10.05 15:40
Wofür brauchst du was schnelleres als Qicksort? Das wird doch wohl für deine Zwecke reichen. Qucksort hat schon nen laufzeitverhalten von n*log(n) was schnell ist, gibt noch heapsort und mergesort die gleich schnell sind. aber glaub für die paar elemente ist das egal. ich kenn nur einen alg mit dem laufzeitverhalten n, aber den kannste auf strings nicht anwenden.
Gausi - Mi 05.10.05 15:45
Wenn es sehr viele relativ kurze Strings sind, würde sich
RadixSort [
http://de.wikipedia.org/wiki/Radixsort] empfehlen. den müsste man evtl. nur anpassen, da deine Elemente (Strings) nicht alle gleich lang sind.
Da du aber nur ziemlich wenige Strings sortieren musst, und du für jedes Zeichen einen Durchlauf machen musst (z.B wenn du das Wort "Sortierverfahren" in deiner Stringliste hast, musst du 16 Durchläufe machen), dürfte der Aufwand von O(N LogN) im Mittel bei Quicksort in deinem Fall fast besser sein als der lineare Aufwand bei RadixSort.
Hinzu kommt ein erhöhter Speicherbedarf für RadixSort.
Überschlagsrechnung:
Quelltext
1: 2: 3:
| 16 * N ?<? logN * N 16 ?<? logN 2^16 ?<? N |
Fazit: Wenn du so in etwa mehr als 2^16 (bzw. 2^x, wobei x die Länge deines längsten Wortes ist) Strings sortieren musst, dann würde ich von Quicksort abraten, ansonsten bleib besser dabei.
Anmerkung: Ich weiss, dass in der Laufzeit von Quicksort auch noch Konstanten drin sind, und man deshalb eigentlich nicht so rechnen kann, aber das Prinzip sollte klar sein...
Grishnak - Mi 05.10.05 15:47
@Heiko: Wichtig zu wissen: Sind diese Strings a) aufsteigend vorsortiert, b) absteigend vorsortiert oder c) unsortiert? Bei aufsteigend vorsortierten Feldern ist Quicksort mWn nicht so effektiv und wird von "primitiveren" Algorithmen geschlagen. Ansonsten kenne ich Quicksort als schnellste Möglichkeit.
Gausi - Mi 05.10.05 15:55
Grishnak hat folgendes geschrieben: |
Bei aufsteigend vorsortierten Feldern ist Quicksort mWn nicht so effektiv und wird von "primitiveren" Algorithmen geschlagen. |
Das kommt sehr darauf an, wie man bei Quicksort das Pivotelement wählt. Die Methode "Sort" bei Delphi verwendet einen Quicksort mit Pivot-Element = mittleres Element(*), sodass bei auf-/ absteigend sortierten Listen eine optimale Aufteilung in zwei Teilfolgen erreicht wird. Daher zweifele ich deine Aussage zumindest teilweise an.
Wenn man als Pivotelement jedoch das erste/letzte Element wählt, bekommt man bei solchen Listen natürlich eine quadratische Laufzeit. Da Bubblesort die Vorsortierung in dem Fall besser ausnutzt, liegt dann Quicksort weit hinten.
(*) hab ich herausgefunden, als ich Sort aufgerufen habe mit einer Liste von 10.000 Elementen, die dadurch entstanden ist, dass ich 2 identische 5000er Listen aneinandergehägt habe. Der Sortiervorgang hat solange gedauert, dass ich das Prog mit Gewalt beenden musste. Hab ich diese Liste vorher "randomisiert", oder eine sortierte Liste mit 10.000 Elementen sortiert, war er in nullkommanix fertig.
Udontknow - Mi 05.10.05 16:03
Ein allgemeine Praxishinweis (vielleicht ein wenig Offtopic, aber bei 5000-10000 Strings und trotdem langer Wartezeit liegt es vielleicht sogar daran):
Variablen immer mit const oder var an Subroutinen übergeben, ansonsten kann sich die benötigte Zeit dramatisch erhöhen, da das Programm bei jedem Aufruf von Subroutinen erst den Variableninhalt kopieren muß. Bei Pointern, Integerwerten etc. ist das egal, aber alle Variablen, die größer als ein Pointer (also 4 Byte) sind, sollten so an Subroutinen übergeben werden (es sei denn, das spezielle Verhalten, daß Variablen innehrhalb der Routine änderbar sind, aber die Änderung nicht an die aufgerufene "Ebene" zurückgegeben werden, ist erwünscht).
Cu,
Udontknow
Grishnak - Mi 05.10.05 16:54
@Gausi:
Ich hab jetzt mal 50.000 Zufalls-Strings der Länge 8 in einer StringList gespeichert und mittels Sort-Methode sortieren lassen. Das ganze wurde anschließend nochmals sortiert.
Dabei hat das erneute Sortierten der bereits sortierten Liste ca. 66% der Zeit gedauert, die das Sortieren einer unsortierten Liste dauerte. Das deutet mMn nicht auf die Wahl des mittleren Elementes als Pivot-Element hin!
Gausi - Mi 05.10.05 17:05
Grishnak hat folgendes geschrieben: |
@Gausi:
Dabei hat das erneute Sortierten der bereits sortierten Liste ca. 66% der Zeit gedauert, die das Sortieren einer unsortierten Liste dauerte. Das deutet mMn nicht auf die Wahl des mittleren Elementes als Pivot-Element hin! |
Hast du dich hier vertippt, oder bestätigst du damit nicht meine Aussage? Wenn das sortieren der sortierten Liste nur 66% der Zeit zum sortieren der unsortieren Liste benötigt, dann ist das weniger ;-)
Wie ich sagte, basiert meine Vermutung auf einem Test, den ich gemacht habe. Und es erscheint mir auch sinnvoll, nicht das letzt Element zu nehmen, da es doch öfter vorkommt, dass man eine fast sortierte Liste novhmal schnell "drübersortiert".
Und Schwankung um den Faktor 2 sind durchaus noch im "Messfehlerbereich" ;-)
Heiko - Mi 05.10.05 17:12
So bin jetzt wieder vom Einkaufen zurück und kann auf ein paar Nachfragen jetzt Antwort geben :).
@Länge der Strings: Sie ist ganz verschieden. Wie sich bei dieser Typendeklaration sicherlich denken kann.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
| type TMusikInfo=record DateiPfad: String; Titel: String; Interpret: String; Album: String; Genre: String; Jahr: Integer; TitelNr: Integer; Kommentar: String; Komponist: String; Encoder: String; Copyright: String; Sprache: String; Link: String; Format: String; end; |
Ich will also immer nach einem dieser Elemente sortieren (dynamisches Array), wodurch ich vorher nicht sagen kann, wie lang ein String ist, da es ja je nach dem Kriterium verschieden ist und ich nicht für jedes Element ein extra Verfahren haben will.
@Vorsortiert: Jein, die sind nach einem Kriterium sortiert, welches keine Rolle für das Suchverfahren besitzten wird. Sprich: Es kann sein das die Liste nach dem titel sortiert ist und jetzt nach dem Album sortiert werden soll ;).
Udontknow - Mi 05.10.05 17:43
Noch einmal offtopic:
Weitere Geschwindigkeitsvorteile gewinnst du, indem du in deinem Array nur Pointer auf per Getmem erstellte Records speicherst, sodaß beim Sortieren nicht der gesamte Record, sondern nur der Pointer auf einen Record ausgetauscht wird.
Cu,
Udontknow
Heiko - Mi 05.10.05 17:45
Mhm, ich kenne mich nicht so gut mit Pointern aus. Das mit GetMem und so bekomme ich hin, aber wie ich genau auf die einzelnen Elemente zeigen kann nicht. Muss ich mir mal angucken ;).
//Edit: Wie macht man es am besten, wenn in man die Größe eines Strings nicht kennt, den man einlesen will?
Udontknow - Mi 05.10.05 18:19
Hier mal ein objektorientierter Ansatz. Dadurch, daß nun der Record als Objekt (sprich Pointer) definiert ist, sollte das auch schon einen Effekt auf die Geschwindigkeit haben.
Ich hätte es wohl von Anfang an als DB-Projekt konzipiert, aber naja...
Du musst ein TMusikinfos-Objekt erstellen. Bei diesem kannst du dann sooft du eben Daten hast Add aufrufen und den neuen Eintrag mit Daten bestücken. Sortiert wird diese Liste dann über die angegebenen zwei Methoden (ist leider unflexibel, da man die einzelnen Felder des Records nicht dynamisch über einen Index ansprechen kann, wie man es bei Feldern eines Datasets machen könnte).
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146:
| unit U_Musikinfo;
interface
uses SysUtils, Classes;
type TMusikInfo=Class DateiPfad: String; Titel: String; Interpret: String; Album: String; Genre: String; Jahr: Integer; TitelNr: Integer; Kommentar: String; Komponist: String; Encoder: String; Copyright: String; Sprache: String; Link: String; Format: String; end;
type TMusikInfos=class private FItems:TList; function GetItems(Index: Integer): TMusikInfo; procedure SetItems(Index: Integer; const Value: TMusikInfo); function GetCount: Integer; public function Add:TMusikInfo; procedure Delete(Index:Integer); procedure Clear; property Items[Index:Integer]:TMusikInfo read GetItems write SetItems; property Count:Integer read GetCount;
procedure SortierenNachDateiPfad; procedure SortierenNachTitel;
constructor Create; virtual; destructor Destroy; override; end;
implementation
{$R *.dfm}
function Vergleich_Titel(Item1, Item2: Pointer): Integer; var I1,I2:TMusikInfo; begin I1:=TMusikInfo(Item1); I2:=TMusikInfo(Item2);
Result:=0; if I1.Titel<I2.Titel then Result:=-1 else if I1.Titel>I2.Titel then Result:=1 end;
function Vergleich_DateiPfad(Item1, Item2: Pointer): Integer; var I1,I2:TMusikInfo; begin I1:=TMusikInfo(Item1); I2:=TMusikInfo(Item2);
Result:=0; if I1.DateiPfad<I2.DateiPfad then Result:=-1 else if I1.DateiPfad>I2.DateiPfad then Result:=1 end;
function TMusikInfos.GetItems(Index: Integer): TMusikInfo; begin Result:=TMusikInfo(FItems[Index]); end;
procedure TMusikInfos.SetItems(Index: Integer; const Value: TMusikInfo); begin FItems[Index]:=Value; end;
function TMusikInfos.Add: TMusikInfo; begin Result:=TMusikInfo.Create; FItems.Add(Result); end;
procedure TMusikInfos.Delete(Index: Integer); begin TMusikInfo(FItems[Index]).Free; FItems.Delete(Index); end;
procedure TMusikInfos.Clear; begin While FItems.Count>0 do Delete(0); end;
function TMusikInfos.GetCount: Integer; begin Result:=FItems.Count; end;
constructor TMusikInfos.Create; begin FItems:=TList.Create; end;
destructor TMusikInfos.Destroy; begin Clear; FItems.Free; inherited; end;
procedure TMusikInfos.SortierenNachTitel; begin FItems.Sort(Vergleich_Titel); end;
procedure TMusikInfos.SortierenNachDateiPfad; begin FItems.Sort(Vergleich_DateiPfad); end;
end. |
Cu,
Udontknow
Gausi - Mi 05.10.05 18:31
Also, wenn deine Strings SO aussehen ;-), dann kann ich dir aus eigener Erfahrung sagen: Nimm ruhig die Standardvariante mit dem Quicksort. Das ist schnell genug. Und insbesondere beim Sortieren nach Pfad dürfte Quicksort wesentlich schneller sein.
Ich hab auch ziemlich viele solche "Strings", und wenn ich auf den Header in meinem VirtualTreeView klicke, dann gibt es ne Verzögerung von ca. 0,5 Sekunden, bis "1910 Fruitgum Company - Simon Says" ganz oben und "ZZ Top - What Would You Do" ganz unten ist ;-) Und bei mir ist die Anzahl noch etwas über deinem Maximalbereich (0-10.000)
Grishnak - Mi 05.10.05 19:03
@Gausi: "Schneller" ist nicht das gleiche wie "schnell"! Wenn ein Sortiert-Algorithmus ca. 2/3 der Zeit für das sortieren eines bereits sortierten Feldes braucht, dann ist er dafür weniger geeignet - genau das wollte ich andeuten! Aber: back-to-topic!
alzaimar - Mi 05.10.05 19:21
Soweit ich weiss, soll Mergesort etwas schneller sein, aber ich denke, es lohnt den Aufwand nicht. Kopier dier das Quicksort von TStringlist und experimentier damit rum.
Wenn Du noch schneller werden willst, dann nimm ein iteratives Quicksort, das ist nochmal 10% schneller. Grundsätzlich sollte auf Rekursion verzichtet werden (so schon, wie sie ist), wenn es schnell gehen soll.
Hier, QnD (Quick'n Dirty):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
| procedure QuickSortIt (a : TIntegerArray; L,R : Integer); var sp, I, J,p,t: Integer; s : Array [0..100,0..1] Of Integer; begin fillchar (s,sizeOf(s),0); sp := 1; s[0,0] := L; s[0,1] := R; While sp>0 Do Begin dec (sp); l := s[sp,0]; r := s[sp,1];
repeat I := L; J := R; P := a[(L + R) shr 1]; repeat while a[i]<p do inc (i); while a[j]>p do dec (j); if I <= J then begin T := a[I];a[I] := a[J];a[J] := T; Inc(I); Dec(J); end; until I > J; if L < J then begin s[sp,0] := l; s[sp,1] := j; inc (sp); End; L := I; until I >= R; End; end; |
Gausi - Mi 05.10.05 19:25
Ist ja eigentlich nicht OT, da es ja um geeignete (=schnelle) Sortierverfahren geht. Ich hatte dich da falsch verstanden. Ich dachte, du vergleichst beide Verfahren, aber die Angaben waren wohl für Quicksort bei 2 Listen.
Das widerspricht aber nicht meiner Vermutung. Sie bestätigt nur die Güte von Quicksort und die Tatsache, dass "im Mittel" die Liste gut aufgeteilt wird. Nur daher kommt ja die schnelle Laufzeitabschätzung.
D.h. eine unsortierte Liste wird nur um 30% langsamer sortiert, als wenn man eine optimale Aufteilung hat (ich gehe weiter von meiner Vermutung aus).
Es ist richtig, dass Quicksort keine Vorsortierung ausnutzen kann. Deshalb ist Quicksort nie dann optimal, wenn man sortierte Listen sortiert. Aber Qicksort ist in vielen Fällen sehr "quick", daher ja auch der Name. In der Praxis hat der sich ganz einfach bewährt.
Und da du hier keinen "Spezialfall" hast, wo du ganz spezielle Voraussetzungen an deine Liste stellst, würde ich einfach dabei bleiben. Einen spürbar schnelleren Algo wirst du kaum finden. (und das ist wieder voll On-Topic ;-))
MSCH - Mi 05.10.05 19:33
ich auch mal; bei einer solchen Menge, die wie ich vermute, als File of Record (?) irgentwo auf der Platte dümpelt, ist da nicht eine kleine Datenbank besser? Es reicht ja dazu Access und Ado. Und du hast deine Probleme mehr oder weniger gelöst, wenn du dort die richtigen Indizes setzt. Dann ist das Laden und Umsortieren nur noch abhängig von der Hardware.
grez
msch
alzaimar - Mi 05.10.05 19:40
Kurze Frage am Rande:
Wozu sortieren? Zum schnellen finden? Dann wäre eine Dictionary (Hashliste) ideal. Einfügen ist O(1), Suchen ist O(1), Löschen auch. Nur mit der sortierten Ausgabe haperts.
Eine weitere Möglichkeit könnten die Skiplist sein. Das hat ein nahezu lineares Laufzeitverhalten.
Heiko - Mi 05.10.05 20:25
Ne, die ich erListe sortiere ich nicht zum schnellen finden. Das sortieren soll für den mp3-Tag-Editor von [url=
http://www.killprocess.de.vu]KillProcess[/url] sein. Dabei suche ich nach allen Musikdateien und will dem User die Daten dann nach z.B. dem Titel sortiert präsentieren.
alzaimar - Mi 05.10.05 20:33
Ach so. Na dann Quicksort. Oder einfach TStringlist.Sort einfach gehts doch nicht. Und ob der anwender 100 oder 120ms wartet ist doch peng.
Hier: Teste selbst
Heiko - Do 06.10.05 10:51
alzaimar hat folgendes geschrieben: |
Ach so. Na dann Quicksort. Oder einfach TStringlist.Sort einfach gehts doch nicht. Und ob der anwender 100 oder 120ms wartet ist doch peng. |
Mit StringListen zu arbeiten schlägt ja völlig gegen Performancevorteile und es ich kann da ja nicht die Zusammengehörigkeit behalten, oder nur sehr schwer ;)
alzaimar hat folgendes geschrieben: |
Hier: Teste selbst |
:gruebel: Was willst du damit aussagen?
alzaimar - Do 06.10.05 11:46
Heiko hat folgendes geschrieben: |
Mit StringListen zu arbeiten schlägt ja völlig gegen Performancevorteile... |
Ich fülle ein Stringarray (dynamisch) mit 10000 Elementen in 1,8ms. Eine Stringlist (Capacity vorher gesetzt) in 3,5ms. einerseits ist das die doppelte Zeit, andereseits: Drauf gesch*****. Das Sortieren dauert mit der Stringlist per s.sort 60ms. Das ist in jedem Fall unterhalb der Warnehmungsschwelle. Du schreibst doch keine zeitkritische RT-Anwendung.
Ich würde keine Sekunde mehr daran verschwenden, was denn nun schneller ist. Es *ist* schnell genug, und das zählt. Eine Stringliste ist doch völlig ok für weniger als so 10-20k Elemente. Darüber wirds dann natürlich sehr laangsaam.
Heiko hat folgendes geschrieben: |
und es ich kann da ja nicht die Zusammengehörigkeit behalten, oder nur sehr schwer ;) |
Kapier ich nicht
alzaimar hat folgendes geschrieben: |
Hier: Teste selbst |
:gruebel: Was willst du damit aussagen?[/quote]
Na, das Du es selbst testen sollst. das 'Hier:' bezog sich auf die (auch topologische) Nähe zum Attachment 'TestSorting.Zip', welches, wie der Name impliziert, Sortierungen testet.
Heiko - Do 06.10.05 13:27
alzaimar hat folgendes geschrieben: |
Heiko hat folgendes geschrieben: | Mit StringListen zu arbeiten schlägt ja völlig gegen Performancevorteile... |
Ich fülle ein Stringarray (dynamisch) mit 10000 Elementen in 1,8ms. Eine Stringlist (Capacity vorher gesetzt) in 3,5ms. einerseits ist das die doppelte Zeit, andereseits: Drauf gesch*****. Das Sortieren dauert mit der Stringlist per s.sort 60ms. Das ist in jedem Fall unterhalb der Warnehmungsschwelle. Du schreibst doch keine zeitkritische RT-Anwendung.
Ich würde keine Sekunde mehr daran verschwenden, was denn nun schneller ist. Es *ist* schnell genug, und das zählt. Eine Stringliste ist doch völlig ok für weniger als so 10-20k Elemente. Darüber wirds dann natürlich sehr laangsaam.
Heiko hat folgendes geschrieben: | und es ich kann da ja nicht die Zusammengehörigkeit behalten, oder nur sehr schwer ;) |
Kapier ich nicht |
Naja ich habe doch einen Record und jetzt möchte ich nach einem dieser Strings aus dem Record sortieren. Dafür muss ich ja die StringList mit diesesn Strings füllen. Aber woher weiß ich dann noch, welcher Titel z.B. zu den neu sortierten Alben gehört?
alzaimar hat folgendes geschrieben: |
Heiko hat folgendes geschrieben: | alzaimar hat folgendes geschrieben: | Hier: Teste selbst |
:gruebel: Was willst du damit aussagen? |
Na, das Du es selbst testen sollst. das 'Hier:' bezog sich auf die (auch topologische) Nähe zum Attachment 'TestSorting.Zip', welches, wie der Name impliziert, Sortierungen testet. |
Ich hatte den Anhang mal wieder nicht gesehen ;). Wo du es jetzt sagtest, das dort ein Anhang habe ich einmal die Seite aktualisiert so das ich ihn jetzt sehe ;).
alzaimar - Do 06.10.05 13:49
Sei A : Array Of TMyRecord und TMyRecord.Caption der String, nachdem Du sortieren willst:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| Var sl : TStringList;
Begin sl := TStringlist.creatE; sl.capacity := Length (A); For i:=0 To High (A) Do sl.addObject (a[i].Caption, @A[i]); sl.sort; |
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!