Sandbox
<math>f(x)=\sqrt[4]{x}</math>
<math>f(x)=\frac{y}{x}</math>
hallo
There was a young lady from Riga,
who smiled as she rode on a tiger.
they returned from the ride
with the lady inside
and the smile on the face of the tiger.
hello | is |
---|---|
a | table |
3.Stunde am 16.04.2008
Sortierverfahren
Motivation
Def: Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.
Anwendungen
- Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für Algorithmen mit Log(N) Komplexität)
- Darstellung von Daten gemäß menschlicher Wahrnehmung
- Bemerkung: aus programmiertechnischer Anwenwendungssicht hat das Sortierproblem an Relevanz verloren, da
- Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass komplizierte, speicher-sparende Sortieralgorithmen nur noch in wenigen Fällen benötigt werden.
- gängige Programmiersprachen heute typenabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht deshalb sich in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der STL
Vorraussetzungen/ Spielregeln
Mengentheoretische Anforderungen
- Definition Totale Ordnung/ Total gordnete Menge
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation
<math>R \subseteq M \times M</math> über einer Menge <math>M</math>, die transitiv, antisymmetrisch und total ist.
<math>R</math> sei dargestellt als infix Notation <math>\le </math> dann, falls M total geordnet, gilt
<math> \forall a,b,c \ \epsilon M </math>
(1) <math>a \le b \bigwedge b \le a \Rightarrow a=b </math> (antisymmetrisch)
(2) <math>a \le b \bigwedge b \le c \Rightarrow a \le c </math> (transitiv)
(3) <math>a \le b \bigvee b \le a </math> (total)
Bemerkung: aus (3) folgt <math> a \le a </math> (reflexiv)
Hab in der Wiki eine gute Seite dazu gefunden Ordnungsrelation
Datenspeicherung
a) Array (Grafik folgt noch)
b) Vekettete Liste
Nachteil Adressierung bsp: 10 > 9
Charakterisierung der Effizienz von Algorithmen
- (a) Komplexität O(1), O(n), etc. wird in Kapitel Effizienz erklärt.
- (b) Zählen der notwendigen Vergleiche
- (c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)
Rekursive Zerlegung zerlegt Ursprüngliche Probleme in kleinere Probleme und wendet sie auf die kleineren Probleme an; daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. Aufwand für N Eingaben, hängt ab vom Aufwand der Eingaben geringeren Umfangs ab. (Teilprobleme)
----
4. Stunde, am 17.04.2008
(Fortsetzung der Stunde vom 16.04.2008)
3.3 Mergesort
a) Algorithmus
- Zugrunde liegende Idee:
- "Teile und herrsche"-Prinzip (divide and conquer) zur Zerlegung in Teilprobleme
- Zusammenführen der Lösungen über Mischen (merging)
- two-way
- multi-way
a)/1 Algorithmus c ← merge(a,b)
c = merge(a,b) ← { # Kombination zweier sortierter Listen a_i und b_j # zu einer sortierten Ausgabeliste c_k a[M+1] ← maxint b[N+1] ← maxint for k ← 1 to M+N if a[i] < b[j] c[k] > a[i] i ← i+1 else c[k] ← b[j] j ← j+1 }
rekursiver Mergesort
mergesort(m) ← { #m ist ein Array if |m| > 1 #True, wenn m mehr als 1 Element hat. a ← mergesort(m[1:<|m|/2]) b ← mergesort(m[(|m|/2)+1:|m|]) c ← merge(a,b) return(c) else return(m) }
(Eine In-place-Implementierung siehe bei Sedgewick.)
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es entsteht ein Binärbaum der Tiefe <math>lgN</math>.
Arraylängen Vergleiche
SORTING ------- OS RT IN N/4 N/4 * 4 G
ORST GIN N/2 N/2 * 2 GINORST N N
Zeitkomplexität: <math>C(N) - N \cdot lgN</math>
Komplexität
Komplexität: <math>C(N) = 2 \cdot \left( \frac{N}{2} \right) + N = N \cdot log_2 N
\cdot N</math> (für N = <math>2^n</math> )
Erklärungen zur Formel:
- <math> C \left(\frac{N}{2}\right) </math>: für jede Hälfte des Arrays
- <math> + N </math>: für das Zusammenführen
weitere Eigenschaften von MergeSort
- Mergesort ist stabil, weil die Position gleicher Schlüssel im Algorithmus
merge(a,b) nicht verändert wird - wegen „<” hat das linke
Element Vorrang.
- Mergesort ist unempfindlich gegenüber der Reihenfolge der Eingabedaten.
Grund dafür ist die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge
1.
- Diese Eigenschaft ist dann unerwünscht, wenn ein Teil des Arrays oder gar das
ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu
sortiert.
Quicksort
- Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [1] entwickelt. Es gibt viele Implementierungen von Quicksort, siehe vgl. [2].
- Dieser Algorithmus gehört zu den "Teile und herrsche"-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.
grundlegender Algorithmus
quicksort(l,r) ← { #l: linke Grenze, r: rechte Grenze des Arrays #Das Array läuft also von l bis r (a[l:r]) if r > l i ← partition(l,r) #i ist das Pivot-Element quicksort(l,i-1) #quicksort auf beide Hälfte des Arrays anwenden quicksort(i+1,r) }
Dieser Algorithmus wird rekursiv für zwei Teilstücke links und rechts des Pivot-Elements aufgerufen. Das Pivot-Element ist nach diesem Schritt an der richtigen Position (d.h. links von der Stelle i stehen nur kleinere, rechts von i nur größere Elemente als das Pivot-Element). Die Methode partition sorgt dafür, dass diese Bedingung erfüllt ist.
Algorithmus für partition
Aufgabe: Ordne a so um, dass nach der Wahl von i (Pivot-Element)
gilt:
- <math>a[i]</math> ist sortiert, d.h. dieses Element ist am endgültigen Platz.
- <math>\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right]
\right\} : x \leq a \left[ i \right]</math>
- <math>\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right]
\right\} : x \geq a \left[ i \right]</math>
Abbildung fehlt noch a[i] heißt Pivot-Element (p)
i ← partition(l,r) ← { p ← a[r] #p: Pivot-Element. Hier wird willkürlich das rechteste Element # als Pivot-Element genommen. i ← l-1 #i und j sind Laufvariablen j ← r repeat repeat i ← i+1 #Finde von links den ersten Eintrag >= p until a[i] >= p repeat j ← j+1 #Finde von rechts den ersten Eintrag <= p until a[j] <= p swap(a[i], a[j]) until j <= i #Nachteile: p steht noch rechts swap(a[i], a[j]) #Letzter Austausch zwischen i und j muss #zurückgenommen werden swap(a[i], a[r]) return(i) }
Bemerkungen zur gegebenen Implementierung:
- Sie benötigt ein Dummy-Minimalelement.
- Dieses Element ist durch zusätzliche if-Abfrage vermeidbar, aber die
if-Abfrage erhöht die Komplexität des Algorithmus (schlechte Performanz).
- Sie ist ineffizient für (weitgehend) sortierte Arrays, da sich folgende
Aufteilung für die Partitionen ergibt:
- Erste Partition: [l,i-1], zweite Partition: [i+1,r]
- Die erste Partition umfasst N-1 Elemente
- Die zweite Partition ist leer (bzw. sie existiert nicht), weil das Pivot-Element p = r gewählt wurde. Das Array wird also elementweise abgearbeitet.
- → N einzelne Aufrufe ⇒ Zeitkomplexität: <math>\approx
\frac{N^2}{2}</math> (siehe Berechnung vom 16.04.2008)
- Für identische Schlüssel sollten beide Laufvariablen stehen bleiben.
- Bei der gegebenen Implementierung tauscht auch gleiche Elemente aus.
- Für identische Schlüssel können die Abbruchbedingungen verbessert werden (siehe
Sedgewick).
Komplexität
<math>C(N) = (N+1) + \frac{1}{N} \sum_{m=1}^{N} \left[ C(k-1) + C(N-k)
\right]</math> für <math> N>1;\, C_1 = C_0 =0 </math>
Anmerkungen zur Formel:
- <math>(N+1)</math>: Vergleiche für jeden Aufruf
- <math>k</math>: Teilungspunkt
<math>
\frac{1}{N} \sum_{m=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N}
\sum_{k=1}^{N} C(k-1)
</math>
<math>
C(N) = (N+1) + \frac{1}{N} \sum_{m=1}^{N} \left[ C(k-1) + C(N-k) \right]
\overset{\cdot N}{\longleftrightarrow} </math>
<math>
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right]
\overset{-\, (N-1) \cdot C(N-1)}{\longleftrightarrow} </math>
<math> N \cdot C(N) - (N-1) \cdot C(N-1) = N(N+1) - (N-1) \cdot N + 2 \sum_{k=1}^{N}
C(k-1) - 2 \sum_{k=1}^{N} C(k-1) </math>
<math>
= N(N+1) - (N-1) \cdot N + 2 \cdot C(N-1) \longleftrightarrow </math>
<math> N \cdot C(N) = N(N+1) - (N-1) \cdot N + 2 \cdot C(N-1) + (N-1) + (N-1) \cdot
C(N-1) = 2N + (N+1) \cdot C(N-1) \overset{/N(N+1)}{\longleftrightarrow} </math>
<math>
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} </math>
<math>
= \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} </math>
<math>
= \frac{C(N-3)}{N-2} + \frac{2}{N-1} + \frac{2}{N} + \frac{2}{N+1} </math>
<math>
= ... = </math>
<math> = \frac{C(2)}{3} + 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \sum_{k=3}^{N}
\frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot ln N
</math>
Für sehr große N gilt:
<math>\approx 2 \sum_{k=1}^{N} \frac{1}{k}</math> beziehungsweise <math> \geq 2
\sum_{k=1}^{N} \frac{1}{k}</math>
Mittlere Komplexität:
<math>C(N) = 2(N+1) \cdot lnN \approx 2N \cdot lnN </math>
Verbesserungen des Algorithmus
- Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks.
- "r" wird immer kleiner → Der rekursive Aufruf lohnt sich nicht mehr. → Explizites Sortieren einsetzen.
- Das Pivot-Element könnte geschickter gewählt werden: Median.
147.142.207.188 19:31, 23 April 2008 (UTC)