Kapitel 4: Sortieren
1. Sortierprobleme
1.1 Motivation des Sortierens
Sortieralgorithmen spielen eine zentrale Rolle in der Informatik und im täglichen Leben. Viele Anwendungen erfordern eine geordnete Datenstruktur, um effizient arbeiten zu können. Beispiele für sortierwürdige Daten sind:
- Telefonbücher – Sortierung nach Nachnamen für schnelle Suche.
- Patientendateien – Sortierung nach Name oder Dringlichkeit.
- Preis- und Produktlisten – Sortierung nach Preis, Kategorie oder Beliebtheit.
- Suchergebnisse – Ordnung nach Relevanz, Datum oder Nutzerinteressen.
Ziele des Sortierens
Das Sortieren von Daten hat mehrere zentrale Ziele:
- Effiziente Suche: Sortierte Daten ermöglichen schnellere Suchvorgänge, insbesondere mit Algorithmen wie der binären Suche.
- Datenkonsistenz und Synchronisation: Sortierung hilft bei der konsistenten Bearbeitung großer Datenmengen.
- Optimierung der Rechenzeit: Studien zeigen, dass mehr als 25 % der kommerziellen Rechenzeit für Sortiervorgänge verwendet werden ([Ottmann und Widmayer, 2012]).
Eine wichtige Beobachtung ist, dass nicht immer alle Attribute eines Objekts für die Sortierung relevant sind. Beispielsweise können Produktlisten nach Preis geordnet sein, enthalten aber viele weitere Merkmale wie Hersteller oder Bewertungen, die für die Reihenfolge keine Rolle spielen.
1.2 Definition von Sortierproblemen
Übliche Formulierung:
- Gegeben ist eine Menge von Sätzen (Datenobjekten).
- Jeder Satz enthält einen Schlüssel, nach dem sortiert wird.
- Zusätzlich zu diesem Schlüssel können Sätze weitere Attribute enthalten, die für die Sortierung nicht relevant sind.
- Zwischen den Schlüsseln ist eine Ordnungsrelation
oder definiert, sodass je zwei Elemente vergleichbar sind.
Annahmen zur Schlüsselstruktur:
Falls nicht anders angegeben, werden üblicherweise folgende Annahmen für Schlüssel getroffen:
- Ganzzahlige Schlüssel – oder solche, die auf ganze Zahlen abgebildet werden können.
- Typische Beispiele für numerische Schlüssel:
- Artikelnummern
- Hausnummern
- Personalnummern
- Schulnoten
- Alternative Schlüsselformate:
- Alphabetische Reihenfolge – beispielsweise für Namen in einer Kundenliste.
- Lexikografische Ordnung – etwa bei der Sortierung von Wörtern oder Dateinamen.
Ein häufiges Zusatzkriterium ist das Fehlen von Duplikaten, d. h. jede betrachtete Datenmenge enthält eindeutige Schlüssel. Falls Duplikate zugelassen werden, ergeben sich zusätzliche Anforderungen an die Sortierverfahren, insbesondere im Hinblick auf die Stabilität eines Sortieralgorithmus (d. h. ob die ursprüngliche Reihenfolge gleicher Schlüssel erhalten bleibt).
1.3 Herausforderungen bei der Sortierung
Sortierprobleme sind vielfältig, und es gibt verschiedene Herausforderungen, die eine effiziente Lösung erfordern:
- Große Datenmengen: Bei Millionen oder Milliarden von Elementen ist eine effiziente Laufzeit entscheidend.
- Dynamische Daten: In vielen Anwendungen ändern sich Daten kontinuierlich, sodass ein effizientes Neusortieren erforderlich ist.
- Mehrere Sortierkriterien: Manchmal sind mehrere Attribute für die Sortierung relevant, z. B. Produkte zuerst nach Kategorie und dann nach Preis zu ordnen.
- Ressourcenbeschränkungen: Manche Sortieralgorithmen sind speicherintensiv, was insbesondere bei eingebetteten Systemen oder datenintensiven Anwendungen eine Rolle spielt.
2. Mathematische Grundlagen
2.1 Ordnungsrelationen
Definition: Eine Relation
- Reflexivität: Für alle
gilt . - Antisymmetrie: Für alle
gilt: Wenn und , dann muss sein. - Transitivität: Für alle
gilt: Aus und folgt . - Totalität: Für alle
gilt: oder .
2.2 Permutationen
Definition: Eine Permutation ist eine bijektive Abbildung einer Menge auf sich selbst, also eine Umordnung der Elemente.
- Anwendung im Sortieren: Beim Sortieren soll eine Permutation gefunden werden, die die Elemente eines Arrays so anordnet,
dass für alle Indizesgilt:
Hinweis: Oft wird das Bild einer Permutation – also die neu geordnete Folge – ebenfalls als Permutation bezeichnet.
2.3 Inversionszahl
- Definition: Gegeben eine Folge
aus einem total geordneten Universum.
Dann ist die Inversionszahldefiniert als die Anzahl der Paare , für die gilt:
Beobachtungen:
, falls die Folge sortiert ist. - Die maximale Inversionszahl beträgt
, was in der Größenordnung liegt. - Die Überprüfung aller Paare auf Inversionen hat eine Laufzeit von
. - Die Inversionszahl dient als Maß für die Vorsortiertheit einer Folge: Je niedriger der Wert, desto näher ist die Folge an einer vollständig sortierten Reihenfolge.
2.4 Sortierprobleme: Definitionsvarianten
Gegeben:
Eine Menge
Gesucht:
Eine Permutation der Elemente in
INFO
Es wird oft angenommen, dass keine Duplikate in der Schlüsselmenge vorhanden sind.
3. Überprüfung der Sortiertheit
Grundidee vieler Sortierverfahren:
Solange es Indizes
werden die Elemente
Dieses Vorgehen führt schrittweise zur vollständigen Sortierung der Elemente.
Java Code für Sortiertheit
boolean istSortiert(T[] a) {
for (int i = 0 ; i <= a.length – 2 ; i++) {
if (a[i] > a[i + 1]) return false;
}
return true;
}4. Sortieralgorithmen
Eine kurze Code-Vorstellung der ersten drei Algorithmen
public static void bubbleSort(int[] a) {
boolean swapped;
do {
swapped = false;
for (int i = 0; i < a.length - 1; i++) {
if (a[i] > a[i + 1]) { // Falls die Reihenfolge falsch ist
swap(a, i, i + 1); // Vertausche Elemente
swapped = true;
}
}
} while (swapped); // Wiederhole, bis keine Vertauschung mehr erfolgt
}public static void selectionSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
int minElementIndex = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[minElementIndex]) {
minElementIndex = j;
}
}
swap(a, i, minElementIndex);
}
}public static void insertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int toInsert = a[i];
int decreasingIndex = i - 1;
while (decreasingIndex >= 0 && toInsert < a[decreasingIndex]) {
a[decreasingIndex + 1] = a[decreasingIndex];
decreasingIndex--;
}
a[decreasingIndex + 1] = toInsert;
}
}4.1 BubbleSort
Prinzip:
BubbleSort basiert auf der Idee, dass größere Werte durch paarweisen Vergleich und Vertauschung nach und nach ans Ende der Liste "aufsteigen" – ähnlich wie Luftblasen in Wasser.
Implementation in Java:
Algorithmus:
- Durchlaufe das Array mehrfach.
- Vergleiche jeweils zwei benachbarte Elemente:
- Falls das linke größer als das rechte ist, vertausche sie.
- Wiederhole diesen Vorgang, bis keine Vertauschungen mehr erforderlich sind.
public static void bubbleSort(int[] a) {
boolean swapped;
do {
swapped = false;
for (int i = 0; i < a.length - 1; i++) {
if (a[i] > a[i + 1]) { // Falls die Reihenfolge falsch ist
swap(a, i, i + 1); // Vertausche Elemente
swapped = true;
}
}
} while (swapped); // Wiederhole, bis keine Vertauschung mehr erfolgt
}Laufzeitanalyse:
- Im schlechtesten Fall muss der Algorithmus das Array
Mal durchlaufen, wobei die innere Schleife jeweils Vergleiche ausführt: - Bester Fall: Wenn das Array bereits sortiert ist, reicht eine einzige Iteration
.
4.2 SelectionSort
Prinzip:
SelectionSort findet in jedem Durchlauf das kleinste (oder größte) Element im unsortierten Teil der Liste und tauscht es an die richtige Position.
Implementation in Java:
Algorithmus:
- Gehe durch das Array und finde das kleinste Element.
- Tausche es mit dem ersten Element.
- Wiederhole den Vorgang für den restlichen unsortierten Teil des Arrays.
public static void selectionSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
int minElementIndex = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[minElementIndex]) {
minElementIndex = j;
}
}
swap(a, i, minElementIndex);
}
}Laufzeitanalyse:
- Da immer das gesamte unsortierte Feld durchsucht werden muss, beträgt die Anzahl der Vergleiche:
- Die Anzahl der Vertauschungen ist jedoch nur
, da maximal Vertauschungen erfolgen.
4.3 InsertionSort
Prinzip:
InsertionSort baut eine sortierte Teilliste auf, indem Elemente einzeln an die richtige Stelle eingefügt werden.
Implementation in Java:
Algorithmus:
- Beginne mit dem zweiten Element.
- Verschiebe alle größeren Elemente nach rechts, um Platz zu machen.
- Füge das aktuelle Element an die richtige Stelle ein.
public static void insertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int toInsert = a[i];
int decreasingIndex = i - 1;
while (decreasingIndex >= 0 && toInsert < a[decreasingIndex]) {
a[decreasingIndex + 1] = a[decreasingIndex];
decreasingIndex--;
}
a[decreasingIndex + 1] = toInsert;
}
}Laufzeitanalyse:
- Best-Case (vorsortierte Liste):
. - Worst-Case (umgekehrte Reihenfolge):
, da jedes Element bis zum Anfang verschoben werden muss.
Verbesserung: Statt eine lineare Suche für den Einfügepunkt zu nutzen, kann eine binäre Suche verwendet werden, um die Anzahl der Vergleiche auf
4.3 MergeSort
Der MergeSort-Algorithmus wird anhand des Divide-and-Conquer-Prinzips erklärt, inklusive der Beschreibung der Rekursionsanker, eines Code-Gerüsts und der Komplexitätsanalyse mittels Rekursionsbaum.
4.4 Quicksort
In diesem Teil wird das Quicksort-Verfahren erläutert, wobei die Bedeutung von Pivot-Element, Partitionierung und rekursiver Implementierung (in-place mit
5. Komplexitätsanalysen
Dieser Abschnitt widmet sich der präzisen Ermittlung der
6. Zusammenfassung und Ausblick
Abschließend werden die zentralen Erkenntnisse des Kapitels zusammengefasst und ein Ausblick auf weiterführende Themen im Bereich der Sortierung und algorithmischen Optimierung gegeben.