Skip to content
Snippets Groups Projects
Commit feaa9471 authored by Florian Unger's avatar Florian Unger
Browse files

cleaning

parent ce9d297c
No related branches found
No related tags found
No related merge requests found
\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.
\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}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment