diff --git a/src/2. Sorting.md b/src/2. Sorting.md deleted file mode 100644 index 54be1ee..0000000 --- a/src/2. Sorting.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -tags: aud ---- -# 2. Sorting - -# Das Sortierproblem -Gegeben: Folge von Objekten -Gesucht: Sortierung gemäß bestimmten Schlüsselwertes -## Schlüsselproblem -Schlüssel müssen nicht eindeutig, aber sortierbar sein -Im Folgenden Annahme, dass es totale Ordnung $\le$ auf der Menge $M$ aller Schlüsselwerte gibt -Betrachtung von Schlüsselwerten ohne Satellitendaten, meist Zahlen -Satellitendaten sind "unnötig", nur Schlüsselwert ist relevant -# Insertion Sort -``` -insertionSort(A) - FOR i=1 TO A.length-1 DO - // insert A[i] in pre-sorted sequence A[0..i-1] - key=A[i]; - j=i-1; // search for insertion point backwards - WHILE j>=0 AND A[j]>key DO - A[j+1]=A[j]; // move elements to right - j=j-1; - A[j+1]=key; -``` -- $A$ ist ein Array/Liste/... -- $A[0..i-1]$ ist immer bereits sortiert -- Wert an der Stelle $A[i]$ wird dann im sortierten Bereich an der richtigen Stelle eingefügt, dabei wird alles immer verschoben -# Laufzeitanalysen: O-Notation -Wie viele Schritte macht ein Algorithmus in Abhängigkeit von der Eingabekomplexität? -- Man nimmt meist Worst-Case für alle Eingaben gleicher Komplexität -- (Worst-Case-)Laufzeit: $T(n) = \max \{\text{Anzahl Schritte}\}$ für eine Aufgabe -- Komplexität wird meist von einem Faktor dominiert, wie z.B. der Anzahl zu sortierender Zahlen $n$ -## Laufzeitanalyse für einen Algorithmus -- Nehme ein festes $n$, z.B. Anzahl zu sortierender Elemente -- Wie oft wird jede Zeile maximal ausgeführt (in Abhängigkeit von $n$)? -- Jeder Zeile $i$ wird Aufwand $ci$ zugeordnet, wird dann mit Anzahl der Ausführungen multipliziert -- Elementare Operationen (Zuweisung, Vergleich,...) haben konstanten Aufwand $1$ -- $T(n)$ ist dann sehr komplex, siehe Insertion Sort-Beispiel -## Asymptotische Vereinfachung -- 1. Vereinfachung: Man nimmt nur dominanten Term $D(n)$ von $T(n)$ -- 2. Vereinfachung: Nur abhängigen $A(n)$ Term betrachten, Vorfaktoren entfernen - - Konstante Vorfaktoren sind von Berechnungsmodell, Leistung abhängig -## $\Theta$-Notation/Landau-Symbole -Seien $f,g: \mathbb{N} \to \mathbb{R}_{>0}$ Funktionen, $\mathbb{N}$ ist die Eingabekomplexität, $\mathbb{R}_{>0}$ die Laufzeit. $$\Theta(g) := \{f: \exists c_1,c_2 \in \mathbb{R}, n_0 \in \mathbb{N}, \forall n \ge n_0, 0 \le c_1g(n) \le f(n) \le c_2g(n)\}$$ -Schreibweise: $f \in \Theta(g), f = \Theta(g)$ -- $g(n)$ ist eine asymptotisch scharfe Schranke von $f(n)$ -- $\Theta$-Notation beschränkt eine Funktion asymptotisch von oben und unten -- Beispiel: [[#Insertion Sort]]: $T(n) \in \Theta(n^2)$ für $c_1 = \frac{3}{2}, c_2 = 7, n_0 = 2$ -## $O$-Notation -$g$ ist obere Schranke von $f$ -$$O(g) := \{f: \exists c \in \mathbb{R}_{>0}, n_0 \in \mathbb{N}, \forall n \ge n_0, 0 \le f(n) \le g(n)\}$$Sprechweise: $f$ wächst höchstens so schnell wie $g$ -Schreibweise: $f = O(g), f \in O(g)$ -$\Theta(g(n)) \subseteq O(g(n)) \leadsto f(n) \in \Theta(g) \Rightarrow f(n) \in O(g)$ -### Rechenregeln -- Konstanten: $f(n) = a, a\in \mathbb{R}_{>0} \Rightarrow f(n) \in O(1)$ -- Skalarmultiplikation: $f \in O(g), a \in \mathbb{R}_{>0} \Rightarrow a \cdot f \in O(g)$ -- Addition: $f_1 \in O(g_1), f_2 \in O(g_2) \Rightarrow f_1 + f_2 \in O(\max\{g_1,g_2\}), \max$ ist punktweise -- Multiplikation: $f_1 \in O(g_1), f_2 \in O(g_2) \Rightarrow f_1 \cdot f_2 \in O(g_1 \cdot g_2)$ -## $\Omega$-Notation -$g$ ist untere Schranke von $f$ -$$\Omega(g) := \{f: \exists c \in \mathbb{R}_{>0}, n_0 \in \mathbb{N}, 0 \le cg(n) \le f(n)\}$$Sprechweise: $f$ wächst mindestens so schnell wie $g$ -Schreibweise: $f = \Omega(g), f \in \Omega(g)$ -$\Theta(g(n)) \subseteq \Omega(g(n)) \leadsto f(n) \in \Theta(g) \Rightarrow f(n) \in \Omega(g)$ -## Zusammenhang $O$, $\Omega$, $\Theta$ -$f(n) \in \Theta(g(n))$ gdw. $f(n) \in O(g(n))$ und $f(n) \in \Omega(g(n))$ -## Anwendung $O$-Notation -$f = O(g)$ ist üblich, $f \in O(g)$ ist wahre Bedeutung und besser, da $O(g)$ Menge ist -$O(n^4) = O(n^5)$ gilt, nicht jedoch $O(n^5) = O(n^4)$! -### Ungleichungen -- $\le$ nur mit $O$ verwenden -- $\ge$ nur mit $\Omega$ verwenden -### Insertion Sort Beispiel -Algorithmus macht maximal $T(n)$ viele Schritte, $T(n) \in \Theta(n^2)$ -$\leadsto$ Laufzeit $\le T(n) \in O(n^2)$ -Für "gute" Eingaben (bereits vorsortiert) macht Algorithmus $\Theta(n)$ viele Schritte -Es wird aber mit Worst-Case gearbeitet, Insertion Sort hat quadratische Laufzeit -## Komplexitätsklassen -|Klasse|Bezeichnung|Beispiel| -|---|---|---| -|$\Theta(1)$|Konstant|Einzeloperation| -|$\Theta(\log n)$|Logarithmisch|Binäre Suche| -|$\Theta(n)$|Linear|Sequentielle Suche| -|$\Theta(n \log n)$|Quasilinear|Sortieren eines Arrays| -|$\Theta(n^2)$|Quadratisch|Matrixaddition| -|$\Theta(n^3)$|Kubisch|Matrixmultiplikation| -|$\Theta(n^k)$|Polynomiell -|$\Theta(2^n)$|Exponentiell|Travelling-Salesman| -|$\Theta(n!)$|Faktoriell|Permutationen| -## $o$-Notation, $\omega$-Notation -Gelten für alle Konstanten, nicht nur eine -$$o(g) := \{f: \forall c \in \mathbb{R}_{>0}, \exists n_0 \in \mathbb{N}, \forall n \ge n_0, 0 \le f(n) < cg(n)\}$$ -$2n \in o(n^2), 2n^2 \notin o(n^2)$ -$$\omega(g) := \{f: \forall c \in \mathbb{R}_{>0}, \exists n_0 \in \mathbb{N}, \forall n \ge n_0, 0 \le cg(n) < f(n)\}$$ -$\frac{n^2}{2} \in \omega(n), \frac{n^2}{2} \notin \omega(n^2)$ -# Bubble Sort -``` -bubbleSort(A) - FOR i=A.length-1 DOWNTO 0 DO - FOR j=0 TO i-1 DO - IF A[j]>A[j+1] THEN SWAP(A[j],A[j+1]); - // SWAP: temp=A[j+1]; A[j+1]=A[j]; A[j]=temp; -``` -- Quadratische Laufzeit -- Große Werte "steigen nach oben" und sammeln sich am Ende -- $A[i..A.length-1]$ ist nach jedem Durchlauf der äußeren Schleife korrekt -# Merge Sort -## Idee: Divide & Conquer (& Combine) -Teile Liste in Hälften, sortiere (rekursiv) Hälften, sortiere wieder zusammen -(Teil-)Sortierung erfolgt im Array selbst, Teillisten werden genutzt -Siehe auch [[7. Advanced Designs]] -## Algorithmus -``` -mergeSort(A,l,r) // initial call: l=0,r=A.length-1 - IF lr OR (pl= 1, f(n)$ asymptotisch positive Funktion. -### Interpretation -- Problem wird in $a$ Teilprobleme der Größe $\frac{n}{b}$ aufgeteilt -- Lösen jeder der $a$ Teilprobleme benötigt Zeit $T(\frac{n}{b})$ -- $f(n)$ umfasst Kosten für Aufteilen und Zusammenfügen -## Mastertheorem -Seien $a \ge 1, b > 1$ konstant, $f(n)$ eine positive Funktion und $T(n)$ über den nicht-negativen ganzen Zahlen durch folgende Rekursiongleichung definiert: $$T(n) = aT\left(\frac{n}{b}\right)+ f(n), T(1) \in \Theta(1)$$ -$\frac{n}{b}$ wird hierbei entweder auf- oder abgerundet. -Dann besitzt $T(n)$ die folgenden asymptotischen Schranken: -1. Gilt $f(n) \in O(n^{\log_b(a) - \epsilon})$ für ein $\epsilon > 0$, dann gilt $T(n) \in \Theta(n^{\log_ba})$ -2. Gilt $f(n) \in \Theta(n^{\log_ba})$, dann gilt $T(n) \in \Theta(n^{\log_ba} \cdot \log_2n)$ -3. Gilt $f(n) \in \Omega(n^{\log_b(a)+\epsilon})$ für ein $\epsilon > 0$ und $af\left(\frac{n}{b}\right)\le cf(n)$ für ein $c < 1$ und hinreichend große $n$, dann ist $T(n) \in \Theta(f(n))$ -### Interpretation -Entscheidend ist das Verhältnis von $f(n)$ zu $n^{\log_ba}$: -1. Wenn $f(n)$ polynomiell kleiner als $n^{\log_ba}$, dann gilt $T(n) \in \Theta(n^{\log_ba})$ -2. Wenn $f(n), n^{\log_ba}$ gleiche Größenordnung, dann $T(n) \in \Theta(n^{\log_ba} \cdot \log n)$ -3. Wenn $f(n)$ polynomiell größer als $n^{\log_ba}$ und $af\left(\frac{n}{b}\right) \le cf(n)$, dann $T(n) \in \Theta(f(n))$ -- Regularität Fall 3: $af\left(\frac{n}{b}\right)\le cf(n), c < 1$ -- $f(n)$ dominiert asymptotisch den Ausdruck -- Fall 3 bedeutet, dass die Wurzel den größten Arbeitsaufwand verrichtet, diese wird hiermit sichergestellt -Wenn das Mastertheorem nicht anwendbar ist, ist die Baumstruktur zu analysieren -# Quicksort -## Idee -- Divide & Conquer -- Mehr Arbeit in Aufteilen, Zusammenfügen kostenlos -- Wählt 1. Element als Pivot-Element -- Dann Partitionieren der Elemente, sodass $\le$ Pivot links, $\ge$ Pivot rechts -- Rekursiv fortsetzen -## Algorithmus -``` -quicksort(A,l,r) // initial call: l=0,r=A.length-1 - IF l=pivot; //move left up - REPEAT pr=pr-1 UNTIL A[pr]==0 AND A[j]>key DO + A[j+1]=A[j]; // move elements to right + j=j-1; + A[j+1]=key; +``` +- $A$ ist ein Array/Liste/$dots$ +- $A[0..i-1]$ ist immer bereits sortiert +- Wert an der Stelle $A[i]$ wird dann im sortierten Bereich an der richtigen Stelle eingefügt, dabei wird alles immer verschoben += Laufzeitanalysen: O-Notation +Wie viele Schritte macht ein Algorithmus in Abhängigkeit von der Eingabekomplexität? +- Man nimmt meist Worst-Case für alle Eingaben gleicher Komplexität +- (Worst-Case-)Laufzeit: $T(n) = max {"Anzahl Schritte"}$ für eine Aufgabe +- Komplexität wird meist von einem Faktor dominiert, wie z.B. der Anzahl zu sortierender Zahlen $n$ +== Laufzeitanalyse für einen Algorithmus +- Nehme ein festes $n$, z.B. Anzahl zu sortierender Elemente +- Wie oft wird jede Zeile maximal ausgeführt (in Abhängigkeit von $n$)? +- Jeder Zeile $i$ wird Aufwand $c i$ zugeordnet, wird dann mit Anzahl der Ausführungen multipliziert +- Elementare Operationen (Zuweisung, Vergleich,$dots$) haben konstanten Aufwand $1$ +- $T(n)$ ist dann sehr komplex, siehe Insertion Sort-Beispiel +== Asymptotische Vereinfachung +- 1. Vereinfachung: Man nimmt nur dominanten Term $D(n)$ von $T(n)$ +- 2. Vereinfachung: Nur abhängigen $A(n)$ Term betrachten, Vorfaktoren entfernen + - Konstante Vorfaktoren sind von Berechnungsmodell, Leistung abhängig +== $Theta$-Notation/Landau-Symbole +Seien $f,g: NN arrow.r RR_(>0)$ Funktionen, $NN$ ist die Eingabekomplexität, $RR_(>0)$ die Laufzeit. $ Theta(g) := {f: exists c_1,c_2 in RR, n_0 in NN, forall n gt.eq n_0, 0 lt.eq c_1g(n) lt.eq f(n) lt.eq c_2g(n)} $ +Schreibweise: $f in Theta(g), f = Theta(g)$ +- $g(n)$ ist eine asymptotisch scharfe Schranke von $f(n)$ +- $Theta$-Notation beschränkt eine Funktion asymptotisch von oben und unten +- Beispiel: Insertion Sort: $T(n) in Theta(n^2)$ für $c_1 = 3/2, c_2 = 7, n_0 = 2$ +== $O$-Notation +$g$ ist obere Schranke von $f$\ +$ O(g) := {f: exists c in RR_(>0), n_0 in NN, forall n gt.eq n_0, 0 lt.eq f(n) lt.eq g(n)} $ Sprechweise: $f$ wächst höchstens so schnell wie $g$\ +Schreibweise: $f = O(g), f in O(g)$\ +$Theta(g(n)) subset.eq O(g(n)) ~> f(n) in Theta(g) arrow.r.double f(n) in O(g)$ +=== Rechenregeln +- Konstanten: $f(n) = a, a in RR_(>0) arrow.r.double f(n) in O(1)$ +- Skalarmultiplikation: $f in O(g), a in RR_(>0) arrow.r.double a dot f in O(g)$ +- Addition: $f_1 in O(g_1), f_2 in O(g_2) arrow.r.double f_1 + f_2 in O(max{g_1,g_2}), max$ ist punktweise +- Multiplikation: $f_1 in O(g_1), f_2 in O(g_2) arrow.r.double f_1 dot f_2 in O(g_1 dot g_2)$ +== $Omega$-Notation +$g$ ist untere Schranke von $f$\ +$ Omega(g) := {f: exists c in RR_(>0), n_0 in NN, 0 lt.eq c g(n) lt.eq f(n)} $ Sprechweise: $f$ wächst mindestens so schnell wie $g$\ +Schreibweise: $f = Omega(g), f in Omega(g)$\ +$Theta(g(n)) subset.eq Omega(g(n)) arrow.r.long.squiggly f(n) in Theta(g) arrow.r.double f(n) in Omega(g)$ +== Zusammenhang $O$, $Omega$, $Theta$ +$f(n) in Theta(g(n))$ gdw. $f(n) in O(g(n))$ und $f(n) in Omega(g(n))$ +== Anwendung $O$-Notation +$f = O(g)$ ist üblich, $f in O(g)$ ist wahre Bedeutung und besser, da $O(g)$ Menge ist\ +$O(n^4) = O(n^5)$ gilt, nicht jedoch $O(n^5) = O(n^4)$! +=== Ungleichungen +- $lt.eq$ nur mit $O$ verwenden +- $gt.eq$ nur mit $Omega$ verwenden +=== Insertion Sort Beispiel +Algorithmus macht maximal $T(n)$ viele Schritte, $T(n) in Theta(n^2)$\ +$arrow.r.long.squiggly$ Laufzeit $lt T(n) in O(n^2)$\ +Für "gute" Eingaben (bereits vorsortiert) macht Algorithmus $Theta(n)$ viele Schritte\ +Es wird aber mit Worst-Case gearbeitet, Insertion Sort hat quadratische Laufzeit +== Komplexitätsklassen +#table(align: horizon, +columns: (auto, auto, auto), +table.header([Klasse],[Bezeichnung],[Beispiel]), +$Theta(1)$,"Konstant","Einzeloperation", +$Theta(log n)$,"Logarithmisch","Binäre Suche", +$Theta(n)$,"Linear","Sequentielle Suche", +$Theta(n log n)$,"Quasilinear","Sortieren eines Arrays", +$Theta(n^2)$,"Quadratisch","Matrixaddition", +$Theta(n^3)$,"Kubisch","Matrixmultiplikation", +$Theta(n^k)$,"Polynomiell","", +$Theta(2^n)$,"Exponentiell","Travelling-Salesman", +$Theta(n!)$,"Faktoriell","Permutationen") +== $o$-Notation, $omega$-Notation +Gelten für alle Konstanten, nicht nur eine +$ o(g) := {f: forall c in RR_(>0), exists n_0 in NN, forall n gt.eq n_0, 0 lt.eq f(n) < c g(n)} $ +$2n in o(n^2), 2n^2 in.not o(n^2)$ +$ omega(g) := {f: forall c in RR_(>0), exists n_0 in NN, forall n gt.eq n_0, 0 lt.eq c g(n) < f(n)} $ +$n^2/2 in omega(n), n^2/2 in.not omega(n^2)$ += Bubble Sort +``` +bubbleSort(A) + FOR i=A.length-1 DOWNTO 0 DO + FOR j=0 TO i-1 DO + IF A[j]>A[j+1] THEN SWAP(A[j],A[j+1]); + // SWAP: temp=A[j+1]; A[j+1]=A[j]; A[j]=temp; +``` +- Quadratische Laufzeit +- Große Werte "steigen nach oben" und sammeln sich am Ende +- $A[i.."A.length"-1]$ ist nach jedem Durchlauf der äußeren Schleife korrekt += Merge Sort +== Idee: Divide \& Conquer (\& Combine) +Teile Liste in Hälften, sortiere (rekursiv) Hälften, sortiere wieder zusammen +(Teil-)Sortierung erfolgt im Array selbst, Teillisten werden genutzt +== Algorithmus +``` +mergeSort(A,l,r) // initial call: l=0,r=A.length-1 + IF lr OR (pl= 1, f(n)$ asymptotisch positive Funktion. +=== Interpretation +- Problem wird in $a$ Teilprobleme der Größe $n/b$ aufgeteilt +- Lösen jeder der $a$ Teilprobleme benötigt Zeit $T(n/b)$ +- $f(n)$ umfasst Kosten für Aufteilen und Zusammenfügen +== Mastertheorem +Seien $a gt.eq 1, b gt 1$ konstant, $f(n)$ eine positive Funktion und $T(n)$ über den nicht-negativen ganzen Zahlen durch folgende Rekursiongleichung definiert: $ T(n) = a T(n/b)+ f(n), T(1) in Theta(1) $ +$n/b$ wird hierbei entweder auf- oder abgerundet.\ +Dann besitzt $T(n)$ die folgenden asymptotischen Schranken: +1. Gilt $f(n) in O(n^(log_b (a) - epsilon))$ für ein $epsilon > 0$, dann gilt $T(n) in Theta(n^(log_b a))$ +2. Gilt $f(n) in Theta(n^(log_b a))$, dann gilt $T(n) in Theta(n^(log_b a) dot log_2n)$ +3. Gilt $f(n) in Omega(n^(log_b (a) + epsilon))$ für ein $epsilon > 0$ und $a f(n/b)lt.eq c f(n)$ für ein $c < 1$ und hinreichend große $n$, dann ist $T(n) in Theta(f(n))$ +=== Interpretation +Entscheidend ist das Verhältnis von $f(n)$ zu $n^(log_b a)$: +1. Wenn $f(n)$ polynomiell kleiner als $n^(log_b a)$, dann gilt $T(n) in Theta(n^(log_b a))$ +2. Wenn $f(n), n^(log_b a)$ gleiche Größenordnung, dann $T(n) in Theta(n^(log_b a) dot log n)$ +3. Wenn $f(n)$ polynomiell größer als $n^(log_b a)$ und $a f(n/b) lt.eq c f(n)$, dann $T(n) in Theta(f(n))$ +- Regularität Fall 3: $a f(n/b)lt.eq c f(n), c < 1$ +- $f(n)$ dominiert asymptotisch den Ausdruck +- Fall 3 bedeutet, dass die Wurzel den größten Arbeitsaufwand verrichtet, diese wird hiermit sichergestellt.\ + Wenn das Mastertheorem nicht anwendbar ist, ist die Baumstruktur zu analysieren += Quicksort +== Idee +- Divide \& Conquer +- Mehr Arbeit in Aufteilen, Zusammenfügen kostenlos +- Wählt 1. Element als Pivot-Element +- Dann Partitionieren der Elemente, sodass $lt.eq$ Pivot links, $gt.eq$ Pivot rechts +- Rekursiv fortsetzen +== Algorithmus +``` +quicksort(A,l,r) // initial call: l=0,r=A.length-1 + IF l=pivot; //move left up + REPEAT pr=pr-1 UNTIL A[pr]=