diff --git a/201_quicksort.tex b/201_quicksort.tex deleted file mode 100644 index f961c8bc22c1bdfffb3c6846bad210a1fd451ac6..0000000000000000000000000000000000000000 --- a/201_quicksort.tex +++ /dev/null @@ -1,290 +0,0 @@ -\section{Quicksort} -Quicksort ist einer der meist verwendeten Sortieralgorithmen. Die Idee ist wieder das divide-et-impera-Prinzip, -der Kern des Algorithmus ist die Zerlegung des Arrays, die sogenannte Partition. - -\subsection{Partition} -Für eine Partition eines Arrays $\mathcal{A}$ wählen wir zuerst ein Pivotelement $p$. Nun sortieren wir das Array so, -dass links von $p$ nur Elemente stehen, die kleiner als $p$ sind, rechts nur Elemente, die größer sind. -Vorerst nehmen wir als Pivotelement einfach das letzte Arrayelement. - -\noindent Die Funktion $\texttt{swap}(a,b)$ im folgenden Pseudocode tauscht hierbei die Werte von $a$ und~$b$. - -\begin{algorithm}[H] - \SetNlSty{texttt}{[}{]} - \caption{\texttt{partition}($\mathcal{A}$)} - \KwIn{An unsorted array $\mathcal{A}$ of lenght $n$} - \KwOut{The partition of $\mathcal{A}$ wrt. the pivot $p$ and its index $k$} - $p \leftarrow \mathcal{A}[n-1]$ \; - $i \leftarrow -1$ \; - \For{$j \leftarrow 0$ \KwTo $n-2$}{ - \If{$\mathcal{A}[j] \leq p$}{ - $\texttt{swap}(\mathcal{A}[i+1], \mathcal{A}[j])$ \; - $i \leftarrow i + 1$ \; - } - } - $\texttt{swap}(\mathcal{A}[i+1], \mathcal{A}[n-1])$ \; - $i \leftarrow i + 1$ \; - \KwRet $(\mathcal{A}, i)$ -\end{algorithm} - -\subsubsection{Korrektheitsüberlegungen:} -Was passiert hier? Das Pivotelement $p$ ist das letzte Element von $\mathcal{A}$, siehe Zeile [1]. -Wie oben erwähnt, partionieren wir das Array in -Elemente, welche kleiner oder gleich $p$ sind, sowie die Elemente, welche größer als $p$ sind. -Das Pivotelement $p$ dient erstmal nur zum Vergleichen ([4]), bleibt aber -ansonsten außen vor, bis es in Zeile [7] an seine endgültige Position getauscht wird. - -Die endgültige Position von $p$ wird von dem Index $i$ bestimmt. Dabei erfüllt $i$ zu jedem Zeitpunkt die Bedingung, -dass alles, was sich links von $i$ befindet ($i$ eingeschlossen), stets kleiner oder gleich $p$ ist (Zeile [4] und [5])! Unter -den Elementen, die bereits mit $p$ verglichen wurden (siehe Laufindex $j$) nimmt $i$ dabei den maximalen Wert ein (Nach -Ausführung von Zeile [6], bzw Zeile [8]). Zu beachten ist außerdem, dass durch das rechtzeitige Addieren von $i+1$ nie ein -Arrayzugriff an undefinierter Stelle geschieht([5]), selbst wenn $i$ mit $-1$ initialisiert wurde([2]). - -Als letztes muss nur noch die Rolle des Laufindex $j$ geklärt werden. Definiert in Zeile [3] startet $j$ beim ersten Element -und geht bis zum vorletzten (also exklusiv $p$). Da pro Schleifendurchgang $i$ um maximal eins inkrementiert werden -kann, ist $j$ also stets größergleich $i$. Dabei zeigt $j$ an, welche Elemente des Arrays bereits mit $p$ verglichen -wurden. -Findet $j$ mit Zeile $[4]$ ein Element, welches kleiner ist als $p$, wird dieses in den von $i$ markierten Bereich -vertauscht([5]). Dabei wird eine Sortierung innerhalb einer Partition zwar vielleicht zerstört, aber das ist für die -Korrektheit des Algorithmus nicht relevant. - -Man kann also feststellen: Das Array ist stets in vier (möglicherweise leere) Teilbereiche unterteilt. Der -Speicherort von $p$ (Anfangs $n-1$), die noch nicht verglichenen Elemente $j < \text{ index } \leq n-2$, die kleiner als -$p$ eingestuften Elemente $0 \leq \text{ index } \leq i$ und die als größer als $p$ eingestuften Elemente $i < \text{ index } -\leq j$. - -Da ein Schleifendurchlauf die Schleifeninvarianten (also der Programmzustand exakt vor dem Inkrementieren von $j$) -\begin{itemize} - \item Elemente mit Index $x$, wobei $x \leq i$, sind kleiner oder gleich $p$, - \item Elemente mit Index $x$, wobei $i < x$ und $x \leq j$, sind größer $p$, - \item Elemente mit Index $x$, wobei $j < x$, sind noch nicht überprüft, -\end{itemize} -erhält, aber die Anzahl der nicht überprüften Elemente pro Schleifendurchlauf um eins schrumpft, terminiert der -Algorithmus. Mit Zeile [7] wird am Ende noch $p$ an seinen richtigen Platz verschoben, damit eine korrekte Partition -zurückgegeben werden kann. - -%TODO: Bild von partition analog zu Cormen, Figure 7.1 - -\subsubsection{Laufzeit:} -Die Schleife in Zeile [3] bis [6] hat an sich eine Laufzeit in $\mathcal{O}(1)$, wird aber $\mathcal{O}(n)$ mal -aufgerufen. Dadurch ergibt sich, dass die Laufzeit von $T_{\text{p}} = T_\texttt{partition}(\mathcal{A})$ in $\Theta(n)$ -liegt, wobei $n = \texttt{len}(\mathcal{A})$. Es gibt keinen asmyptotischen Unterschied zwischen best/worst-case. - - -\subsection{Quicksort} -Auf der Basis von $\texttt{partition}$ kann der Sortieralgorithmus $\texttt{quicksort}$ konstruiert werden. - -\begin{algorithm}[H] - \SetNlSty{texttt}{[}{]} - \caption{\texttt{quicksort}($\mathcal{A}$)} - \KwIn{An unsorted array $\mathcal{A}$ of length $n$} - \KwOut{The same array $\mathcal{A}$, but sorted} - \eIf{$n > 1$}{ - $(\mathcal{A}, k) \leftarrow \texttt{partition}(\mathcal{A})$ \; - \KwRet $\texttt{concat}(\texttt{quicksort}(\mathcal{A}[0, \dots, k-1]), [A[k]], \texttt{quicksort}(\mathcal{A}[k+1, - \dots, n-1])$ \; - }{ - \KwRet $\mathcal{A}$ - } -\end{algorithm} - -Die Funktion $\texttt{concat}$ ist hierbei eine $\mathcal{O}(1)$-Operation, da sie vom Compiler wegoptimiert werden -kann. Aber auch als $\mathcal{O}(n)$-Operation wäre sie asymptotisch irrelevant, da $\texttt{partition}$ bereits -$\mathcal{O}(n)$ Zeit braucht. Sollten wir Arrays auf Grenzen wie $[0,-1]$ aufrufen, so ist damit das leere Array -gemeint. - -\subsubsection{Korrektheitsüberlegungen:} -Die Korrektheit von $\texttt{quicksort}$ ist schneller einsehbar als die von $\texttt{partition}$. Ist die -Paritionseigenschaft bezüglich des Pivotelements in Position $k$ erfüllt, so haben wir links und rechts davon echt -kleinere Unterarrays, welche durch Rekursion sortiert werden (der Basisfall von einem Element ist trivial sortiert). Der -Algorithmus $\texttt{quicksort}$ terminiert als spätestens nach $n$ rekursiven Aufrufen und arbeitet dabei korrekt. - -\subsubsection{Laufzeit:} -Die allgemeine Rekusionsgleichung für $T_{\text{qs}} = T_{\texttt{quicksort}}$ lautet -\[ - T_{\text{qs}}(n) = T_{\text{qs}}(m) + T_{\text{qs}}(n-m) + T_p(n) + f(n), \text{ wobei } f \in \mathcal{O}(1). -\] -Dabei verschwindet $f$ völlig unter $T_p \in \mathcal{O}(n)$. -Anders als bei $\texttt{partition}$ gibt es hier aber Unterschiede im best/worst case. - -\paragraph{Best case} -Im besten Fall treffen wir mit dem Pivotelement $p$ genau den Median von $\mathcal{A}$. Dann haben wir durch $m = -\frac{n}{2}$ pro Rekursionsschritt eine balancierte Aufteilung des Rekursionsbaums. Dann haben wir nach dem Hauptsatz -der Laufzeitfunktionen also $a=2, b=2$, $f \in \mathcal{O}(n)$ und damit eine best-case-Laufzeit von -$\mathcal{O}(n \log(n))$. Wir rechnen aber noch einmal per Hand nach: Einerseits um zu sehen wie das geschieht, -andererseits, da der worst-case nicht mit dem Satz berechnet werden kann. -Sei der Einfachheit halber $n = 2^{k'}$ für ein $k' \in \mathbb{N}$. -\begin{align*} - T_{\texttt{qs}}^{\text{best}}(n) -&= T_{\text{qs}}^{\text{best}} \left(\frac{n}{2} \right) + T_{\text{qs}}^{\text{best}} \left(\frac{n}{2} \right) + T_{\text{p}}(n) \\ - &= 2T_{\text{qs}}^{\text{best}} \left(\frac{n}{2} \right) + T_{\text{p}}(n) \\ - &= 4T_{\text{qs}}^{\text{best}}\left(\frac{n}{4} \right) + 2T_{\text{p}} \left(\frac{n}{2} \right) + T_{\text{p}}(n) \\ - &= 8T_{\text{qs}}^{\text{best}}\left(\frac{n}{8} \right) + 4T_{\text{p}} \left(\frac{n}{4} \right) + 2T_{\text{p}}\left(\frac{n}{2} \right) + T_{\text{p}}(n) \\ - &= \ldots \\ - &= nT_{\text{qs}}^{\text{best}}(1) + \sum_{k=0}^{\log_2(n)} 2^k T_{\text{p}}\left(\frac{n}{2^k}\right) \\ -\end{align*} -Wir sehen nun in einer Nebenrechnung, dass $n \mapsto 2^k T_{\text{p}}(\frac{n}{2^k}) \in \Theta(n)$ für alle -$k \in \mathbb{N}$. -Sei also $k$ fixiert. Dann gilt $c_1 \floor*{\frac{n}{2^k}} \leq T_{\text{p}}(\floor*{\frac{n}{2^k}}) \leq c_2 -\ceil*{\frac{n}{2^k}}$ für alle $n \geq n_0 2^k$, wobei $n_0$ durch $T_{\text{p}} \in \Theta(n)$ gegeben ist. Wir -multiplizieren die Ungleichung mit $2^k$, erhalten damit $2^k c_1 \floor*{\frac{n}{2^k}} \leq 2^k -T_{\text{p}}(\floor*{\frac{n}{2^k}}) \leq 2^k c_2 \ceil*{\frac{n}{2^k}}$, was wieder für alle $n \geq n_0 2^k$ gültig ist. -Da $2^k \floor*{\frac{n}{2^k}} \leq n$ und $2^k \ceil*{\frac{n}{2^k}} \geq n$ gilt, können wir die Ungleichung zu -$c_1 n \leq 2^k T_{\text{p}}(\floor*{\frac{n}{2^k}}) \leq c_2 n$ vereinfachen. Damit gilt mit $c_1, c_2$ und $n_0' = 2^k -n_0$: -\[ - n \mapsto 2^k T_{\text{p}}\left(\floor*{\frac{n}{2^k}}\right) \in \Theta(n). -\] - -Damit haben wir -\begin{align*} - T_{\text{qs}}^{\text{best}}(n) - &= nT_{\text{qs}}^{\text{best}}(1) + \sum_{k=0}^{\log_2(n)} 2^k T_{\text{p}}\left(\frac{n}{2^k}\right) \\ - &\in \mathcal{O}(n) + \log_2(n) \mathcal{O}(n) \\ - &= \mathcal{O}(n \log(n)). \\ -\end{align*} - -\paragraph{Worst case} -Im schlechtesten Fall teilen wir das Array sehr ungünstig: Das Pivotelement ist immer das Maximum oder das Minimum, -unser Array wird also aufgeteilt in ein $n-1$ großes Array, $m=1$ in jedem Rekursionsschritt. -Damit haben wir keinen echten Bruchteil pro Rekursionsschritt und das Master-Theorem lässt sich nicht anwenden. Also -rechnen wir per Hand: -\begin{align*} - T_{\text{qs}}(n) & = T_{\text{qs}}(1) + T_{\text{qs}}(n-1) + T_\text{p}(n) \\ - &= 2T_{\text{qs}}(1) + T_{\text{qs}}(n-2) + T_\text{p}(n-1) + T_\text{p}(n) \\ - &= 3T_{\text{qs}}(1) + T_{\text{qs}}(n-3) + T_\text{p}(n-2) + T_\text{p}(n-1) + T_\text{p}(n) \\ - &\qquad \qquad \vdots \\ - &= \underbrace{n T_{\text{qs}}(1)}_{\text{$\in Θ(n)$}} - +\underbrace{\sum_{i=1}^n T_\text{p}(i)}_{\text{$\in Θ(n^2)?$}} \\ -\end{align*} -Dabei gibt uns die Eulersche Summenformel $\sum_{i=1}^n i = \frac{n^2 - n}{2} \in \mathcal{O}(n^2)$ einen Hinweis, in welcher -Aufwandsklasse der Summenterm liegen könnte. Wir fomalisieren jetzt also die Idee, dass wir $T_{\text{p}}(n)$ durch $c_2 n$ von -oben und $c_1 n$ von unten abschätzen können, sobald die $n$ groß genug werden. - -Betrachten wir also $\sum_{i=1}^k T_\text{p}(i)$. Mit $T_\text{p} \in Θ(n)$ wissen wir, dass $n_0,c_1,c_2$ existieren, sodass -$c_1 n' \leq T_\text{p}(n') \leq c_2 n'$ für alle $n' > n_0$. Dieses $n_0$ ist aber fix, d.h. für ein groß genuges $k$ (und -$k$ soll später gegen unendlich gehen) betrachten wir also $\sum_{i=n_0}^k T_p(i)$. Hier gilt: -\[ - \underbrace{c_1 \sum_{i=n_0}^k i}_{\text{$\in Ω(k^2)$}} - \leq \sum_{i=n_0}^k T_p(i) \leq - \underbrace{c_2 \sum_{i=n_0}^k i}_{\text{$\in \mathcal{O}(k^2)$}} -\] -wie uns die Eulersche Summenformel verrät. -Mit einer beidseitigen Abschätzung haben wir hier also obige Vermutung bewiesen. - - - -Wir würden nun gerne noch eine average-case Analyse durchführen. Bei einer Gleichverteilung des Inputs ergibt sich -nämlich auch eine average-case Aufwand von $T_{\text{qs}}^{\text{avg}} \in \mathcal{O}(n \log n)$. Allerdings benötigt eine -average-case Analyse Annahmen über die Verteilung des Inputs. - -Wir analysieren daher eine stark verwandte Variante, den randomisierten Quicksort. - -\subsection{Randomisierter Quicksort} -Grundidee des randomisierten Quicksort Algorithmus ist es, nicht mehr ein fixes Pivotelement in der Partition zu wählen -(wie bei uns das erste Element), sondern ein zufälliges. Dadurch kann man eine gute -\emph{durchschnittliche} Performance erreichen. - -Das Wort \emph{durchschnittlich} bekommt hier aber eine andere andere Bedeutung -als in der average-case-Analyse! In der average-case-Analyse betrachtet man die durchschnittliche Laufzeit über alle -möglichen Inputs (gewichtet mit der Wahrscheinlichkeit des entsprechenden Inputs), hier hingegen betrachtet man die -durchschnittliche Laufzeit \emph{über die verschiedenen zufälligen Läufe des nichtdeterministischen Algorithmus} -(gewichtet nach Wahrscheinlichkeit des entsprechenden Durchlaufs). - -Definieren wir zuerst die randomisierte Partition: - -\begin{algorithm}[H] - \SetNlSty{texttt}{[}{]} - \caption{\texttt{randomized\_partition}($\mathcal{A}$)} - \KwIn{An unsorted array $\mathcal{A}$ of length $n$} - \KwOut{The partition of $\mathcal{A}$ wrt. the randomized pivot $p$ and its post-partitioning-index $k$} - $i \leftarrow \texttt{random\_uniform}(n-1)$ \; - $\texttt{swap}(\mathcal{A}[0], \mathcal{A}[i])$ \; - $p \leftarrow \mathcal{A}[n-1]$ \; - $i \leftarrow -1$ \; - \For{$j \leftarrow 0$ \KwTo $n-2$}{ - \If{$\mathcal{A}[j] \leq p$}{ - $\texttt{swap}(\mathcal{A}[i+1], \mathcal{A}[j])$ \; - $i \leftarrow i + 1$ \; - } - } - $\texttt{swap}(\mathcal{A}[i+1], \mathcal{A}[n-1])$ \; - $i \leftarrow i + 1$ \; - \KwRet $(\mathcal{A}, i)$ -\end{algorithm} - -Neu ist also nur das Vertauschen des ersten Elements von $\mathcal{A}$ mit dem durch $\texttt{random\_uniform}$ -zufällig (gleichverteilt) erwählten anderen Elements des Arrays, der Rest ist wie bei $\texttt{partition}$. -Dabei hat $\texttt{random\_partition}$ die gleichbleibende Laufzeit $T_{\text{rp}} \in Θ(n)$ mit $n = -\texttt{len}(\mathcal{A})$. - -Der Algorithmus $\texttt{randomized\_quicksort}$ hat dabei genau den gleichen Pseudocode wie $\texttt{quicksort}$, nur -dass $\texttt{randomized\_partition}$ statt $\texttt{partition}$ in Zeile $[2]$ gerufen wird. Die Laufzeit von -$\texttt{randomized\_quicksort}$ mit Input der Länge $n$ bezeichnen wir mit $T_{\text{rqs}}(n)$. Wir werden nun zeigen, -dass $T_{\text{rqs}} \in \mathcal{O}(n \log n)$ liegt. - - -\subsubsection{Herleitung der average case Laufzeit von \texttt{randomized\_quicksort}:} Für die erwartete Laufzeit gilt -\begin{displaymath} - T_{\text{rqs}}(n) = \sum_{k=1}^{n-1} P(k) \left( T_{\text{rqs}}(k) + T_{\text{rqs}}(n-k) + T_{\text{rp}}(n) \right), -\end{displaymath} -wobei $P(k)$ die Wahrscheinlichkeit ist, dass $\texttt{randomized\_partition}(\mathcal{A})$ den Index $k$ liefert. -Wir nehmen mit $k \in \{1,\dots, n-1\}$ eine Gleichverteilung $P(k) = \frac{1}{n-1}$ an. -% TODO: Mehr Details -\begin{align*} - T_{\text{rqs}} & = \sum_{k=1}^{n-1} \left( \frac{1}{n-1} \left( T_{\text{rqs}}(k) + T_{\text{rqs}}(n-k) + T_{\text{rp}}(n) \right) \right) \\ - & = \frac{1}{n-1} \sum_{k=1}^{n-1} \left( T_{\text{rqs}}(k) + T_{\text{rqs}}(n-k) \right) + \frac{n-1}{n-1} T_{\text{rp}}(n) \\ - & = \frac{2}{n-1} \sum_{k=1}^{n-1} T_{\text{rqs}}(k) + T_{\text{rp}}(n). -\end{align*} -Wir zeigen nun vermöge einer vollständiger Induktion, dass mit $c = \max\{T_{\text{rqs}}(1) + T_{\text{rp}}(2), 8 c_{\text{rp}}\}$ gilt: -\[ - T_{\text{rqs}}(n) \leq c \cdot n \log_2 n \text{ für alle } n > 2. -\] - -Hierbei ist $c_{\text{rp}}$ die Konstante mit welcher $T_\text{rp}(n) \leq c_{\text{rp}} n$ gilt. Streng genommen -gilt das erst ab irgendeinem $n_0$, aber den Aspekt vernachlässigen wir hier, um die Beweisstruktur etwas -übersichtlicher zu gestalten. Es ist bei \texttt{randomized\_partition} aber auch leicht ersichtlich, dass $n_0 = 2$ -gewählt werden kann. - -Induktionsanfang $n=2$: -\begin{align*} - T_{\text{rqs}}(n) - &\leq 2 T_{\text{rqs}}(1) + T_{\text{rp}}(n) \\ - &\leq c n \log_2 2 -\end{align*} -Für $c = \max(T_{\text{rqs}}(1) + T_{\text{rp}}(2), 8 c_{\text{rp}}) \geq T_{\text{rqs}}(1) + T_{\text{rp}}(2)$ ist die -Abschätzung definitiv erfüllt. - -Induktionsschritt $n-1 \mapsto n$: Zu zeigen ist, dass mit mit der Aussage wahr für alle $n \in \{2, \dots, n-1\}$ gilt: -$T_{\text{rqs}}(n) \leq c n \log_2 n$. Wir rechnen: -\begin{align*} - T_{\text{rqs}}(n) &= \frac{2}{n-1} \sum_{k=1}^{n-1} T_{\text{rqs}}(k) + T_{\text{rp}}(n) \\ - &\overset{{\scriptscriptstyle \text{IV}}}{\leq} \frac{2}{n-1} \sum_{k=1}^{n-1} c \cdot k \log_2 k + T_{\text{rp}}(n) \\ - &= \frac{2c}{n-1} \sum_{k=1}^{n-1} k \log_2 k + T_{\text{rp}}(n) \\ - &\overset{\scriptscriptstyle (*)}{\leq} \frac{2c}{n-1} \left( (\log_2 n) \left( \frac{n(n-1)}{2} \right) - \frac{\frac{n}{2}(\frac{n}{2}-1)}{2} \right) + T_{\text{rp}}(n) \\ - &= c \cdot n \log_2 n - c \left( \frac{n}{4} - \frac{1}{2} \right) + \underbrace{T_{\text{rp}}(n)}_{\leq c_{\text{rp}} \cdot n} \\ - &\leq c \cdot n \log_2 n - \left( \frac{2c \frac{n}{2} (\frac{n}{2} - 1)}{(n-1)2} \right) + c_{\text{rp}} \cdot n \\ - &\leq c \cdot n \log_2 n - c \cdot \left( \frac{n}{4} \frac{(n-2)}{(n-1)} \right) + c_{\text{rp}} \cdot n \\ - &\leq c \cdot n \log_2 n. -\end{align*} -Damit der letzte Schritt geht, muss -\begin{align*} - c &\leq \frac{c_{\text{rp}}}{\frac{n}{4} \frac{n-2}{n-1}} \\ - &= 4 c_{\text{rp}} \underbrace{\frac{(n-1)}{(n-2)}}_{\text{$\leq 2$ für $n > 2$}} \\ - &\leq 8c_{\text{rp}} -\end{align*} -sein. Durch unsere Wahl von $c = \max\{T_{\text{rqs}}(1) + T_{\text{rp}}(2), 8 c_{\text{rp}}\}$ ist das der Fall. - -Jetzt gilt es nurnoch, die in Schritt $(*)$ getroffene Abschätzung der Summe $\sum_{k=1}^{n-1} k \log_2 k$ zu beweisen: -\begin{align*} - \sum_{k=1}^{n-1} k \log_2 k & = \sum_{k=1}^{\ceil*{\frac{n}{2}}-1} k \underbrace{\log_2 k}_{\leq \log_2 \frac{n}{2}} + - \sum_{k=\ceil*{\frac{n}{2}}}^{n-1} k \underbrace{\log_2 k}_{\leq \log_2 n} \\ - &\leq \sum_{k=1}^{\ceil*{\frac{n}{2}}-1} k (\log_2 n - 1) + \sum_{k=\ceil*{\frac{n}{2}}}^{n-1} k \log_2 n \\ - &= \log_2 n \sum_{k=1}^{\ceil*{\frac{n}{2}}-1} k - \sum_{k=1}^{\ceil*{\frac{n}{2}}-1} k + \log_2 n \sum_{k = \ceil*{\frac{n}{2}}}^{n-1} k \\ - &= \log_2 n \underbrace{\sum_{k=1}^{n-1} k}_{= \frac{n(n-1)}{2}} - \underbrace{\sum_{k=1}^{\ceil*{\frac{n}{2}}-1} k}_{\geq \frac{\frac{n}{2}(\frac{n}{2}-1)}{2}} \\ - &\leq (\log_2 n) \left( \frac{n(n-1)}{2} \right) - \frac{\frac{n}{2}(\frac{n}{2}-1)}{2} -\end{align*} - - -Unsere vollständige Induktion ist damit bewiesen, und wir haben gezeigt, dass die durchschnittliche Laufzeit von $\texttt{randomized\_quicksort}$ in -$\mathcal{O}(n \log n)$ liegt. diff --git a/202_mergesort.tex b/202_mergesort.tex deleted file mode 100644 index 640584cd326805a0719704e6326d14f7b5c3098c..0000000000000000000000000000000000000000 --- a/202_mergesort.tex +++ /dev/null @@ -1,80 +0,0 @@ -\section{Mergesort} -Diese Sortierverfahren ist ein Paragon der sogenannten \emph{divide et impera}-Strategie. - -Ausgehend von einem linearen Feld $\mathcal{A}$ der Größe $n$ unterteilen wir das Problem rekursiv immer weiter in kleinere Probleme, -welche superlinear schneller gelöst werden können. Der Basisfall, ein Array mit einem einzigen Element, ist dann immer -sortiert. Im letzen Schritt vereinen wir alle sortierten Teilarrays zu einem großen sortierten Array. Siehe Abbildung -\ref{fig:mergesort_diagram} für ein Beispiel. - -Im folgenden Pseudocode verwenden wir die Funktion $\texttt{Array}(n)$, welches uns einfach nur ein leeres (also z.B. -mit $0$ gefülltes) Array der Länge $n$ gibt, sowie die Funktion $\texttt{len}(\mathcal{A})$, welche die Länge des arrays $\mathcal{A}$ -zurückgibt. - -\begin{algorithm}[H] - \SetNlSty{texttt}{[}{]} - \caption{\texttt{mergesort}($\mathcal{A}$)} - \KwIn{An array $\mathcal{A}$ of size $n$} - \KwOut{The sorted array $\mathcal{A}$} - \eIf{$n = 1$}{ - \Return $\mathcal{A}$ \; - }{ - $k \leftarrow \floor*{\frac{n}{2}}$ \; - \KwRet $\texttt{merge}(\texttt{mergesort}(\mathcal{A}[0,\dots, k]), \texttt{mergesort}(\mathcal{A}[k+1, \dots, n]))$ - } -\end{algorithm} - -Wobei \texttt{merge} folgendermaßen definiert wird: - -\begin{algorithm}[H] - \SetNlSty{texttt}{[}{]} - \caption{\texttt{merge}($\mathcal{A}_1, \mathcal{A}_2$)} - \KwIn{Two sorted arrays $\mathcal{A}_1$ and $\mathcal{A}_2$} - \KwOut{The sorted union array $\mathcal{A}$ of the input} - $\mathcal{A} \leftarrow \texttt{Array}(\texttt{len}(\mathcal{A}_1) + \texttt{len}(\mathcal{A}_2))$ \; - $i_1, i_2 \leftarrow 0$ \; - \For{$i \leftarrow 0$ \KwTo $\texttt{len}(\mathcal{A})$}{ - \eIf{$\mathcal{A}_1[i_1] \leq \mathcal{A}_2[i_2]$}{ - $\mathcal{A}[i] \leftarrow \mathcal{A}_1[i_1]$ \; - $i_1 \leftarrow i_1 + 1$ \; - }{ - $\mathcal{A}[i] \leftarrow \mathcal{A}_2[i_2]$ \; - $i_2 \leftarrow i_2 + 1$ \; - } - } - \KwRet $\mathcal{A}$ -\end{algorithm} - -Die Laufzeit der Algorithmen ist nicht vom konkreten Input, sondern nur von der Länge des Inputs abhängig. Worst, best -und average case sind also gleich. Für \texttt{merge} ist die Länge des Inputs $n = \texttt{len}(\mathcal{A}_1) + -\texttt{len}(\mathcal{A}_2)$ und es gilt: $T_{\text{m}} = T_{\texttt{merge}} = f \in \mathcal{O}(n)$, da Zeile~3 alles dominiert. - -Damit ist die Laufzeitfunktion für \texttt{mergesort} -\[ - T_{\text{ms}}(n) = 2 T_{\text{ms}}(\frac{n}{2}) + T_{\text{m}}(n) -\] - -Mit dem Hauptsatz der Laufzeitfunktionen haben wir damit Fall 2, die asymptotisch exakte Laufzeit beträgt ($\log_2{2} = 1$) also -\[ - T^{\text{ms}} \in Θ(n \log{n}). -\] - -%Für den Speicherverbrauch ergibt sich -%\begin{align*} -%S(n) & = S(\frac{n}{2}) + O(n) \qquad S(1) = O(1) \\ -%& = S(\frac{n}{4}) + O(\frac{n}{2}) + O(n) \\ -%& = S(\frac{n}{8}) + O(\frac{n}{4}) + O(\frac{n}{2}) + O(n) \\ -%& = S(\frac{n}{2^k}) + O(n) \cdot \sum_{i=0}^{k-1} \frac{1}{2^i} \\ -%& \quad \frac{n}{2^k} = 1 \Rightarrow k = \textrm{ld} n \\ -%& = O(1) + O(n) \cdot \underbrace{\sum_{i=0}^{\textrm{ld} n-1} \frac{1}{2^i}}_{< \sum_{i=0}^{\infty} \frac{1}{2^i} = 2} \\ -%& = O(n). -%\end{align*} -Der Speicherverbrauch ist (bei passenden Compileroptimierungen) $\mathcal{O}(n)$. TODO - -\begin{figure}[!htb] - \centering - \includegraphics[width=0.8\textwidth]{bilder/mergesort_diagram} - \caption{Ein Beispiellauf des \texttt{mergesort}-Algorithmus auf den Input $\mathcal{A} = (38,27,43,3,9,82,10)$} - \label{fig:mergesort_diagram} -\end{figure} - -