Upload
eduard-kaufman
View
226
Download
0
Embed Size (px)
Citation preview
1DigInf 05/06
• Einfügen eines Elements an der richtigen Stelle eines bereits sortierten Arrays.
• Verschiebungen von Elementen statt Vertauschungen
Element x = A[k] wird aus dem Array herausgenommen. So entsteht eine Lücke:
InsertionSort
2DigInf 05/06
• Einfügen eines Elements an der richtigen Stelle eines bereits sortierten Arrays.
• Verschiebungen von Elementen statt Vertauschungen
• Invariante für die äußere Schleife:A[0, ..., k-1] ist sortiert
• Algorithmus: Ausgehend von Position k führen wir ein BubbleDown durch
• Damit ist InsertionSort eine Variante von BubbleSort
InsertionSort
3DigInf 05/06
• Folgendes Programm arbeitet ohne Swap.
• Die Invariante„Das Array A bleibt eine Permutation des ursprünglichen Arrays.“
muss wieder gesondert geprüft werden.
Verkürzte Auswertung
static void InsertionSort(char[] A) { int Hi = A.length – 1; for (int k = 1; k <= Hi; k++) if (A[k] < A[k-1]) { char x = A[k]; int i; for (i = k; ((i > 0) && (A[i–1] > x)); i--) A[i] = A[i–1]; A[i] = x; }}
InsertionSort
4DigInf 05/06
• Aufwand für InsertionSort wie für SelectionSort mit folgenden Unterschieden:
• Average case: Für das Aufspüren der endgültigen Position des Elements A[k] werden k / 2 Vergleiche benötigt, bei SelectionSort müssen zum Auffinden von minPos alle noch ungeordneten Elemente untersucht werden.
• Worst case: SelectionSort benötigt maximal N Swaps. Bei InsertionSort werden schlimmstenfalls N2 / 2 Elemente bewegt.
• Bei InsertionSort ist eine kürzere Laufzeit zu erwarten, wenn die Elemente kurz sind (geringer Aufwand für zusätzliche Swaps) oder wenn die Elemente gut vorsortiert sind.
InsertionSort - Laufzeitverhalten
5DigInf 05/06
Zeit in Sekunden,zufällig sortierte Elemente
Zeit in Sekunden,bereits sortierte Elemente
Laufzeitvergleiche
6DigInf 05/06
• Variante von InsertionSort• Name geht auf den Erfinder Donald Shell zurück• Idee:
• der letzte Durchgang ist ein klassisches InsertionSort• alle Durchgänge vorher sorgen für eine gute Vorsortierung
• Laufzeit: O(N1,5), experimentell sogar O(N1,25)• Mit h = 1 ergibt sich das klassische InsertionSort
static void InsertionSort(char[] A, int h) { int Hi = A.length – 1; for (int k = h; k <= Hi; k++) if (A[k] < A[k-h]) { char x = A[k]; int i; for (i = k; ((i > (h-1)) && (A[i–h] > x)); i = i-h) A[i] = A[i–h]; A[i] = x; }}
ShellSort
7DigInf 05/06
• InsertionSort wird mit einer bestimmten absteigenden Folge von Werten für h aufgerufen.
• Eine bewährte Folge ist ..., 121, 40, 13, 4, 1 mit dem Bildungsgesetz: h0 = 1, hn+1 = 3 * hn + 1
• Der letzte Aufruf mit 1 garantiert, dass korrekt sortiert ist.static void ShellSort(char[] A) { int Hi = A.length – 1; int hmax, h; for (hmax = 1; hmax < Hi; hmax = 3*hmax+1) { } for (h = hmax/3; h > 0; h = h/3) InsertionSort(A, h);}
ShellSort
8DigInf 05/06
• Es gibt Sortieralgorithmen mit einer Laufzeit von N * log N
• Dazu gehören HeapSort, QuickSort, MergeSort (dazu später mehr)
• Schneller geht’s im allgemeinen Fall nicht!
Schnelle Sortieralgorithmen
9DigInf 05/06
• QuickSort ist ein „Divide and Conquer“-Algorithmus, das bedeutet, er hat die folgende Struktur:
• Zerlege das Problem P in Teilprobleme P1, ..., Pn
• Finde die Lösungen L1, ..., Ln der Teilprobleme• Setze die Lösung L von P aus den Lösungen der Teilprobleme zusammen
• Divide and Conquer-Algorithmen sind oft rekursiv
• Für n = 2:
lösungsTyp dac(problemTyp P) { if (istTrivial(P)) return trivialLösung; else return combine(dac(Teil1(P)), dac(Teil2(P)));}
QuickSort
10DigInf 05/06
• 1960 erfunden von C.A.R. Hoare• Suche nach besserem Algorithmus zeigte sich als erfolgreicher als Tricksen an
existierenden Algorithmen
• Array A, w = A[k] irgendwie auswählen, w heißt Pivot-Element
• Partitioniere übrige Elemente von A in kleinere Arrays A1 und A2, so dass alle Elemente von A1 sind ≤ w, alle Elemente von A2 sind ≥ w.
• Bringt man nun w zwischen A1 und A2, so gilt für die neue Anordnung A1 w A2 : x A1, y A2 : x ≤ w ≤ y
• Nun sortiert man A1 und A2 mit QuickSort und erhält sortierte Variante des ursprünglichen Arrays A
A w
A1 A2w≤ ≤
A1 A2w≤ ≤
B1 B2w≤ ≤
QuickSortQuickSort
QuickSort
11DigInf 05/06
• QuickSort funktioniert besonders gut, wenn es gelingt das Pivot-Element so zu wählen, dass A1 und A2 gleich groß sind.
• QuickSort wird mit drei Argumenten aufgerufen:• Array A• Linke Grenze li• Rechte Grenze re
• Für die Wahl des Pivot-Elements gibt es eine Reihe von Heuristiken:• w = A[li]• w = A[re]• w = A[mid] mit mid = (li + re) / 2
• Für alle Heuristiken gibt es Beispielsituationen, in denen sie gut oder schlecht funktionieren.
• Im Beispiel „S O R T I E R B E I S P I E L“ ergibt sich mit w = A[mid] („B“) die schlechtestmögliche Partionierung, A1 bleibt leer.
• Verbesserte Heuristik: den mittleren Wert von A[li], A[re], A[mid]
QuickSort
12DigInf 05/06
Partitionierung:Setze i = li und j = re.Solange li < i < j < re gilt, führe folgende Schritte durch:
inkrementiere i solange A[ i ] < w ,dekrementiere j solange A[ j ] > w,vertausche A[ i ] mit A[ j ], inkrementiere i und dekrementiere j.
QuickSort – Partitionierung per swap
13DigInf 05/06
• Induktion über die Anzahl n der Elemente eines beliebig zu ordnenden Array-Abschnitts A[li .. re]. Vorausgesetzt sei die Korrektheit des Partionierungsalgorithmus.
• Falls n = 1 d.h. li = re ist A sortiert und QuickSort ändert nichts.
• Induktionsannahme: QuickSort sortiert maximal k-elementige Abschnitte korrekt.
• Wir betrachten ein A mit n = k+1 Elementen. Im ersten Schritt entsteht eine Partition A1 w A2, so dass gilt:• u w v für alle u aus A1 und alle v aus A2
• A1 w A2 ist eine Permutation von A
• QuickSort angewendet auf die höchstens k-elementigen A1 und A2 liefert A´1 und A´2. Die sind geordnete Permutation von A1 und A2.
• Es folgt u w v für alle u aus A´1 und alle v aus A´2
• Und damit: A´1 w A´2 ist geordnete Permutation von A1 w A2, also auch von A.
QuickSort – Beweis der Korrektheit
14DigInf 05/06
• Günstigster Fall: • Aufrufhierarchie hat log2(N) Ebenen.• Jede Ebene enthält alle Elemente des Arrays genau einmal, so dass
für die Partionierung einer Ebene die Zeit c * N benötigt wird.• Gesamtaufwand ist dann c * N * log2(N)
• Ungünstigster Fall:• Baum wird zur Kette von N Elementen.• Aufwand ist dann proportional zu N2.
QuickSort – Komplexität
15DigInf 05/06
static void QuickSort(char[] A) { int Hi = A.length – 1; RekQuickSort(A, 0, Hi);}
static void RekQuickSort(char[] A, int Lo, int Hi) { int li = Lo; int re = Hi; int mid = (li + re) / 2; if (A[li] > A[mid]) Swap(A, li, mid); if (A[mid] > A[re]) Swap(A, mid, re); if (A[li] > A[mid]) Swap(A, li, mid); if ((re – li) > 2) { char w = A[mid]; do { while (A[li] < w) li++; while (w < A[re]) re--; if (li <= re) { Swap(A, li, re); li++; re--; } } while (li <= re); if (Lo < re) RekQuickSort(A, Lo, re); if (li < Hi) RekQuickSort(A, li, Hi); }}
QuickSort – Implementierung
16DigInf 05/06
• MergeSort ist ein „Divide and Conquer“-Algorithmus.
• Aufteilung in zwei Hälften, die separat sortiert und dann zusammengemischt werden.
• Zusammenmischen bedeutet, dass die kleinsten Elemente jeweils verglichen und gemischt werden.
MergeSort
17DigInf 05/06
static void MSort(char[] A, int Lo, int Hi) { if (Lo < Hi) { int Mid = (Lo + Hi + 1) / 2; MSort(A, Lo, Mid – 1); MSort(A, Mid, Hi); Merge(A, Lo, Mid, Hi); }}
• Hilfsarray Temp nimmt die zusammengemischten Teil-Arrays auf.• Temp wird zum Schluss wieder in A[Lo...Hi] zurückkopiert.
static void Merge(char[] A, int Lo, int Mid, int Hi) { char Temp[] = new char[Hi – Lo + 1]; for (int i = 0, j = Lo, k = Mid; i < Temp.length; i++) { if ((k > Hi) || (j < Mid) && (A[j] < A[k])) { Temp[i] = A[j]; j++; } else { Temp[i] = A[k]; k++; } for (int i = 0; i < Temp.length; i++) A[Lo + i] = Temp[i]; } }
MergeSort – Implementierung
18DigInf 05/06
MergeSort – Beispiel
19DigInf 05/06
• Bisherige Sortieralgorithmen basierten auf Vergleich und Vertauschen zweier Elemente.
• Diese Algorithmen (und alle anderen auf Vergleichen basierende) haben mindestens den Aufwand N log (N).
• Distribution kommt ohne Vergleiche aus. Voraussetzung:• Daten haben Sortierschlüssel mit festem Format (z.B. Ziffernfolge)
• Grundidee:• Die zu sortierenden Daten werden anhand einer Ziffernposition auf Fächer
verteilt und wieder zusammen getragen. • Verteilung erfolgt zunächst anhand der letzten Position, trägt zusammen
und setzt dann mit Verteilung gemäß zweitletzter Ziffernposition fort.
DistributionSort
20DigInf 05/06
• Briefe werden nach Postleitzahlen sortiert.• Verteilen aller Briefe auf Fächer gemäß der letzten Ziffer
der PLZ• Zusammentragen unter Beibehaltung der Ordnung• Verteilung aller Briefe gemäß der vorletzten Ziffer• Zusammentragen unter Beibehaltung der Ordnung• Fortsetzen bis zur ersten PLZ-Ziffer• Zusammentragen führt zu vollständiger Sortierung
DistributionSort – Beispiel
21DigInf 05/06
DistributionSort – Beispiel
22DigInf 05/06
DistributionSort – Beispiel
23DigInf 05/06
Induktion über die Länge des Sortierschlüssels
DistributionSort – Korrektheitsbeweis durch Induktion
24DigInf 05/06
• DistributionSort arbeitet mit beschränktem Schlüssel und kann deshalb auch nur für eine feste Zahl von zu sortierenden Daten eingesetzt werden.
• Von daher ist kein „fairer“ Vergleich bei asymptotischer Abschätzung möglich.
• Innerhalb der maximalen Sortiermenge ist DistributionSort laufzeit-linear.
• Konkret: Jedes Einordnen ist linear, also c*N und das ist k-mal (k = Schlüssellänge) zu wiederholen, also ist der Aufwand k*c*N, d.h. O(N).
DistributionSort – Laufzeit und Einschränkungen
25DigInf 05/06
Zwei Probleme:• Ordnung der Schlüsselwerte spiegelt nicht immer die Ordnung auf den Daten wider (z.B.
Kodierung von Zeichenketten)
• In jedem Sortierfach muss Platz für alle Datensätze sein. Bei 10 Sortierfächern braucht man entsprechend 10 mal Platz für das gesamte Array.
DistributionSort – Laufzeit und Einschränkungen
26DigInf 05/06
static void DistributionSort(Element[] A, int m) { // m Schlüssellänge for (int k = m; k > 0; k--) { // für jede Schlüsselposition int Count[] = new int[d]; // Count initialisieren for (int z = 0; z < d; z++) // Zeichenmenge für Schlüssel Count[z] = 0; // ist 0..(d-1) for (int i = 0; i < A.length; i++) // Bedarf für die Fachgröße Count[ Key(k, A[i]) ]++; // bestimmen for (int z = 0; z < d; z++) // Aufsummieren Count[z] += Count[z-1]; for (int z = d-1; z > 0; z--) // Beginn d. Fächer bestimmen Count[z] = Count[z-1]; // Fach für Zeichen z beginnt Count[0] = 0; // jetzt an Pos. Count[z]
Element B[] = new Element[A.length]; for (int i = 0; i < A.length; i++) { // Einordnen, int z = Key(k, A[i]); // dabei Fachgrenze B[Count[z]++] = A[i]; // anpassen } System.arraycopy(B, 0, A, 0, A.length); }}
DistributionSort – Implementierung
27DigInf 05/06
• Zusätzlich: java.util.Arrays.sort(int[] a) (Quicksort ähnlich)
• Grundlage: Pentium 4 PC
• Zufällig sortierte Daten, N = Anzahl der zu sortierenden Daten
Laufzeit der schnellen Sortieralgorithmen
28DigInf 05/06
• Zusätzlich: java.util.Arrays.sort(int[] a) (Quicksort ähnlich)
• Grundlage: Pentium 4 PC
• Vollständig vorsortierte Daten, N = Anzahl der zu sortierenden Daten
Laufzeit der schnellen Sortieralgorithmen
29DigInf 05/06
• Bei geringen Datenmengen (kleiner als 20000) nimmt man einen einfachen Algorithmus (InsertionSort, SelectionSort, BubbleSort)
• Für einen gut vorsortierten Datenbestand bieten sich InsertionSort und BubbleSort an.
• Hat man zufällig verteilte Daten und muss man oft sortieren, dann bietet sich eine Anpassung von DistributionSort an.
• Ist das Risiko einer ungünstigen Verteilung (und damit das Worst Case Verhalten) ertragbar, dann QuickSort.
• Ansonsten ShellSort, MergeSort, HeapSort.
Regeln zur Wahl des Sortieralgorithmus
30DigInf 05/06
Online-Feedback
• Bewertung von• Vorlesung• Übungen• Begleitmaterial
• bis Sonntag unter• www.lpz-ebusiness.de
• nur mit Feedback-TAN• gültig für eine Stimme• Austeilung in Vorlesung
• Ergebnisse• ab 6.2. im Netz