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

local changes on figipc50

parent db0412f0
No related branches found
No related tags found
No related merge requests found
...@@ -6,7 +6,8 @@ Sortieralgorithmus anhand seines best/worst/average-case-Verhaltens zu beurteile ...@@ -6,7 +6,8 @@ Sortieralgorithmus anhand seines best/worst/average-case-Verhaltens zu beurteile
\subsection{worst-case-optimal} \subsection{worst-case-optimal}
Im Zusammenhang von vergleichsbasierten Sortieralgorithmen bedeutet die Eigenschaft worst-case-optimal, dass die Im Zusammenhang von vergleichsbasierten Sortieralgorithmen bedeutet die Eigenschaft worst-case-optimal, dass die
Untergrenze von $Ω(n \log n)$ \emph{immer} eingehalten wird. Untergrenze von $Ω(n \log n)$ auch die asymptotische Obergrenze ist, also die worst-case-Laufzeit in i$Θ(n \log n)$
liegt.
\subsection{Stabilität} \subsection{Stabilität}
Ein Sortieralgorithmus ist stabil, wenn durch das Sortieren die Reihenfolge innerhalb bezüglich der Quasiordnung Ein Sortieralgorithmus ist stabil, wenn durch das Sortieren die Reihenfolge innerhalb bezüglich der Quasiordnung
...@@ -35,7 +36,7 @@ sind. Insbesondere muss der best-case also in $\mathcal{O}(n)$ liegen. Diese Def ...@@ -35,7 +36,7 @@ sind. Insbesondere muss der best-case also in $\mathcal{O}(n)$ liegen. Diese Def
Fehlstellungen präzisieren: Fehlstellungen präzisieren:
\begin{definition}[Fehlstände und Fehlstandszahl] \begin{definition}[Fehlstände und Fehlstandszahl]
Sei A eine geordnete Menge und sei $[a_1, \dots, a_n] \in A^n$ eine Folge von Elementen in A. Die Anzahl der Sei $A$ eine geordnete Menge und sei $[a_1, \dots, a_n] \in A^n$ eine Folge von Elementen in $A$. Die Anzahl der
Fehlstände bezüglich $a_i$ ist Fehlstände bezüglich $a_i$ ist
\[ \[
f_i := |\{a_j : j > i, a_j < a_i \}|, f_i := |\{a_j : j > i, a_j < a_i \}|,
......
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
\input{301_dynamische_Arrays} \input{301_dynamische_Arrays}
\include{302_Hashtabellen} \include{302_Hashtabellen}
%\include{303_Binaerbaeume} \include{303_Binaerbaeume}
%\include{304_Halden} \include{304_Halden}
%\include{305_2-4-Baeume} %\include{305_2-4-Baeume}
...@@ -251,7 +251,7 @@ Damit können wir zeigen, dass das dynamische Array einen amortisierten Aufwand ...@@ -251,7 +251,7 @@ Damit können wir zeigen, dass das dynamische Array einen amortisierten Aufwand
\end{theorem} \end{theorem}
\begin{proof} \begin{proof}
Sollten keinerlei Umstrukturierungen nötig sein, ist $a_i < e_i$ klar. Aber auch bei Umstrukturierungen ist aus dem Sollten keinerlei Umstrukturierungen nötig sein, ist $a_i < e_i$ klar. Aber auch bei Umstrukturierungen ist aus dem
vorherigen Lemma klar: Ist $K_0 = 0$ und damit nicht negativ, bleibt $K_i \leq 0$ für alle $i \in \mathbb{N}$. vorherigen Lemma klar: Ist $K_0 = 0$ und damit nicht negativ, bleibt $K_i 0$ für alle $i \in \mathbb{N}$.
Da die tatsächliche Laufzeit $T(f_i) = a_i$, haben wir also Da die tatsächliche Laufzeit $T(f_i) = a_i$, haben wir also
\[ \[
\sum_{i=1}^n T(f_i) = \sum_{i=1}^n a_i \leq \sum_{i=1}^n e_i \leq 3n. \sum_{i=1}^n T(f_i) = \sum_{i=1}^n a_i \leq \sum_{i=1}^n e_i \leq 3n.
......
...@@ -297,7 +297,7 @@ Soll der Knoten $k$ gelöscht werden, müssen drei Fälle unterschieden werden, ...@@ -297,7 +297,7 @@ Soll der Knoten $k$ gelöscht werden, müssen drei Fälle unterschieden werden,
die Referenz des Elternknotens von $k$ auf das Kind von $c$ um. die Referenz des Elternknotens von $k$ auf das Kind von $c$ um.
\item Im letzten Fall hat $k$ hat zwei Kinder. Wir ersetzen die Daten von $k$ durch die Daten des nächstgrößeren \item Im letzten Fall hat $k$ hat zwei Kinder. Wir ersetzen die Daten von $k$ durch die Daten des nächstgrößeren
Knotens $t$ und löschen dann den Knoten $t$. Da dieser nur maximal ein Kind hat (er kann kein linkes Kind haben, Knotens $t$ und löschen dann den Knoten $t$. Da dieser nur maximal ein Kind hat (er kann kein linkes Kind haben,
sonst wäre dass das nächstgrößere) terminert dieser Rekursive Aufruf von $\texttt{delete}$ nach einem Schritt, da sonst wäre dass das nächstgrößere) terminert dieser rekursive Aufruf von $\texttt{delete}$ nach einem Schritt, da
wir einem der letzten beiden Fälle landen. Ist das Verschieben der Daten sehr teuer, so können wir alternativ auch wir einem der letzten beiden Fälle landen. Ist das Verschieben der Daten sehr teuer, so können wir alternativ auch
alle Pointer so umbiegen, dass $t$ in der Baumstruktur den Platz von $k$ einnimmt (nicht gezeigt). alle Pointer so umbiegen, dass $t$ in der Baumstruktur den Platz von $k$ einnimmt (nicht gezeigt).
\end{enumerate} \end{enumerate}
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Halden sind eine Datenstrukturen, welche insbesondere zur Implementierung von priority queues geeignert sind, aber auch Halden sind eine Datenstrukturen, welche insbesondere zur Implementierung von priority queues geeignert sind, aber auch
einen worst-case optimalen in-place Sortieralgorithmus bereitstellen. Es gibt zwei Perspektiven auf Halden: Einmal als einen worst-case optimalen in-place Sortieralgorithmus bereitstellen. Es gibt zwei Perspektiven auf Halden: Einmal als
Array und einmal als vollständiger, sortierter Binärbaum: Array und einmal als vollständiger Binärbaum:
\begin{definition}[Halde: Arraydefinition] \begin{definition}[Halde: Arraydefinition]
Eine Halde $\mathcal{H}$ ist ein Array $\mathcal{A}$ von $n$ Elementen mit einer Quasiordnung, welches der folgenden Haldenbedingung genügt: Eine Halde $\mathcal{H}$ ist ein Array $\mathcal{A}$ von $n$ Elementen mit einer Quasiordnung, welches der folgenden Haldenbedingung genügt:
...@@ -42,41 +42,35 @@ definiert als $h(n) := \floor*{\log_2 n}$. Eine leere Halde hat die Höhe $h(0) ...@@ -42,41 +42,35 @@ definiert als $h(n) := \floor*{\log_2 n}$. Eine leere Halde hat die Höhe $h(0)
\subsection{Verhalden (\texttt{heapify})} \subsection{Verhalden (\texttt{heapify})}
Der Algorithmus $\texttt{heapify}$ repariert eine Fast-Halde, bei dem die Haldenbedingung an genau einer Stelle $i$ verletzt Der Algorithmus $\texttt{heapify}$ repariert eine Fast-Halde, bei dem die Haldenbedingung an genau an der Wurzel
ist. Wir nehmen dabei an, dass der linke und rechte Teilbaum von $i$ Halden sind. beziehungweise im ersten EElement verletzt ist ist. Wir nehmen also an, dass der linke und rechte Teilbaum von $i$ Halden sind.
Um die Haldenbedingung zu erzwingen, muss das $i.$ Element also mit dem Maximum seiner beiden Kinder getauscht werden. Ist das Wurzelelement ein Blatt, so ist die Haldenbedingung immer erfüllt.
Ist das $i.$ Element aus Baumperspektive ein Blatt (also $i \geq \frac{n}{2}$), so ist die Haldenbedingung immer erfüllt. Um sonst die Haldenbedingung zu erzwingen, muss das Wurzelelement also mit dem Maximum seiner beiden Kinder getauscht werden.
Dabei wird die Haldenbedingung aber möglicherweise bei betauschten Kindknoten verletzt. Also rufen wir den Algorithmus dort noch einmal auf.
Andernfalls vertauschen wir das $i.$ Elemente mit maximum seiner Kindknoten. Damit ist bei $i$ die Haldenbedingung In Pseudocode aus der Array-Perspektive sieht das folgendermaßen aus (Der Pseudocode in Arrayperspektive ist eine
erfüllt, aber möglicherweise bei einem Kindknoten nun verletzt. Also rufen wir den Algorithmus dort noch einmal auf.
In Pseudocode aus der Array-Perspektive sieht das folgendermaßen aus (Der Pseudocode in Baumperspektive ist eine
Übungsaufgabe): Übungsaufgabe):
\begin{algorithm}[H] \begin{algorithm}[H]
\SetNlSty{texttt}{[}{]} \SetNlSty{texttt}{[}{]}
\caption{\texttt{heapify}($\mathcal{H}, i$)} \caption{\texttt{heapify}($\mathcal{H}$)}
\KwIn{An almost-heap $\mathcal{H}$ of size $n$ with above conditions and the index $i \in \{0, \cdots, n-1\}$} \KwIn{An almost-heap $\mathcal{H}$ with $r := \text{root}(\mathcal{H})$ with above conditions}
\KwOut{A modified $\mathcal{H}$ with its heap structure intact} \KwOut{A modified $\mathcal{H}$ with its heap structure intact}
$l \leftarrow 2i + 1$ \; $m \leftarrow \max{\texttt{data}(\texttt{left}(r)), \texttt{data}(\texttt{right}(r))}$ \;
$r \leftarrow 2i + 2$ \; \If{$m > \texttt{data}(r)$}{
$j \leftarrow i$ \; \eIf{$\texttt{data}(\texttt{left}(r)) = m$}{
\If{$l < n$ and $\mathcal{H}[l] > \mathcal{H}[i]$}{ $n \leftarrow \texttt{left}(r)$ \;
$j \leftarrow l$ \; }{
} $n \leftarrow \texttt{right}(r)$ \;
\If{$r < n$ and $\mathcal{H}[r] > \mathcal{H}[j]$}{ }
$j \leftarrow r$ \; $\texttt{swap}(\texttt{data}(n), \texttt{data}(r))$ \;
$\texttt{heapify}(n)$ \;
} }
\If{$i \neq j$}{
$\texttt{swap}(\mathcal{H}[i], \mathcal{H}[j])$ \;
$\mathcal{H} \leftarrow \texttt{heapify}(\mathcal{H},j)$ \;
}
\KwRet $\mathcal{H}$
\end{algorithm} \end{algorithm}
\paragraph{Korrektheit:} \paragraph{Korrektheit:}
Unter obig beschriebenen Bedingungen repariert der Algorithmus den Knoten $i$, möglicherweise auf Kosten der Unter obig beschriebenen Bedingungen repariert der Algorithmus den Wurzelknoten, möglicherweise auf Kosten der
Haldenbedingung auf in einer seiner Kindknoten. Da in dem Fall der Algorithmus rekursiv aufgerufen wird und der Haldenbedingung auf in einer seiner Kindknoten. Da in dem Fall der Algorithmus rekursiv aufgerufen wird und der
Basisfall (Knoten ist ein Blatt) die Haldenbedingung trivial erfüllt, terminiert der Algorithmus und arbeitet korrekt. Basisfall (Knoten ist ein Blatt) die Haldenbedingung trivial erfüllt, terminiert der Algorithmus und arbeitet korrekt.
...@@ -89,18 +83,20 @@ $\texttt{heapify}$ ohne seine rekursiven Unteraufrufe $c \in \mathcal{O}(1)$ dau ...@@ -89,18 +83,20 @@ $\texttt{heapify}$ ohne seine rekursiven Unteraufrufe $c \in \mathcal{O}(1)$ dau
\subsection{Aufbau einer Halde aus einem Array/Baum} \subsection{Aufbau einer Halde aus einem Array/Baum}
Wir können aus einem beliebigen Array (oder vollständigen Binärbaum) eine Halde machen, indem wir, angefangen an den Wir können aus einem beliebigen Array (oder vollständigen Binärbaum) eine Halde machen, indem wir, angefangen an den
Blättern bis hin zur Wurzel, immer wieder $\texttt{heapify}$ rufen. Bei den Blättern können wir es uns sparen, da sie eh Blättern bis hin zur Wurzel, immer wieder $\texttt{heapify}$ aufrufen.
schon Heaps sind. Damit haben wir (in Array-Perspektive) folgenden Pseudocode
\begin{algorithm}[H] \begin{algorithm}[H]
\SetNlSty{texttt}{[}{]} \SetNlSty{texttt}{[}{]}
\caption{\texttt{heapify\_array}($\mathcal{A}$)} \caption{\texttt{heapify\_tree}($\mathcal{B}$)}
\KwIn{An array $\mathcal{A}$ of size $n$} \KwIn{A complete binary tree $\mathcal{B}$ with $r := \text{root}(\mathcal{B})$}
\KwOut{The same Array but resorted as a heap} \KwOut{The same tree but resorted as a heap}
\For{$i \leftarrow \floor*{\frac{n}{2}}$ \KwTo $0$}{ \eIf{$\mathcal{B}$ is a leaf}{
$\texttt{heapify}(\mathcal{A}, i)$ \; \KwRet $\mathcal{B}$ \;
}{
$\texttt{left}(r) \leftarrow \texttt{heapify\_tree}(\texttt{left}(r))$ \;
$\texttt{right}(r) \leftarrow \texttt{heapify\_tree}(\texttt{right}(r))$ \;
\KwRet $\texttt{heapify}(\mathcal{B})$ \;
} }
\KwRet $\mathcal{A}$
\end{algorithm} \end{algorithm}
\paragraph{Korrektheit:} \paragraph{Korrektheit:}
...@@ -108,19 +104,19 @@ Dadurch, dass wir von unten nach oben arbeiten, sind Teilbäume bereits Halden. ...@@ -108,19 +104,19 @@ Dadurch, dass wir von unten nach oben arbeiten, sind Teilbäume bereits Halden.
$\texttt{heapify}$ erfüllt und der Algorithmus arbeitet korrekt. $\texttt{heapify}$ erfüllt und der Algorithmus arbeitet korrekt.
\paragraph{Laufzeit:} \paragraph{Laufzeit:}
Die Funktion $\texttt{heapify}$ wird $\frac{n}{2}$ mal aufgerufen und hat selber einen Aufwand von Die Funktion $\texttt{heapify}$ wird $n$ mal aufgerufen und hat selber einen Aufwand von
$\mathcal{O}(\log n)$. Dadurch ist die Abschätzung für den asymptotischen Aufwand von $\texttt{heapify\_array}$ mit $\mathcal{O}(\log n)$. Dadurch ist die Abschätzung für den asymptotischen Aufwand von $\texttt{heapify\_array}$ mit
$\mathcal{O}(n \log n)$ schnell getroffen. Allerdings stellt sich heraus, dass man auch exakter abschätzen kann, da die $\mathcal{O}(n \log n)$ schnell getroffen. Allerdings stellt sich heraus, dass man auch exakter abschätzen kann, da die
Laufzeit von $\texttt{heapify}$ nicht immer der Höhe des kompletten Baumes entspricht. Dadurch erreicht man eine Laufzeit von $\texttt{heapify}$ nicht immer der Höhe des kompletten Baumes entspricht. Dadurch erreicht man eine
genauere Abschätzung: Die Laufzeit von $\texttt{heapify\_array}(\mathcal{A})$ liegt in $Θ(n)$, wobei $n$ die Länge des genauere Abschätzung: Die Laufzeit von $\texttt{heapify\_tree}(\mathcal{A})$ liegt in $Θ(n)$, wobei $n$ die Größe des
Array $\mathcal{A}$ ist (Übungsaufgabe). Baumes $\mathcal{B}$ ist (Übungsaufgabe).
\subsection{Sortieren mit Halden (Heapsort)} \subsection{Sortieren mit Halden (Heapsort)}
Mit Hilfe der beiden vorherigen Algorithmen kann man auch einen Sortieralgorithmus konstruieren. Wir starten mit einem Mit Hilfe der beiden vorherigen Algorithmen kann man auch einen Sortieralgorithmus konstruieren. Wir starten mit einem
Array/Baum und formen ihn in eine Halde um. An der Wurzel des Baumes bzw dem Anfang des Arrays haben wir dann das Array/Baum und formen ihn in eine Halde um. An der Wurzel des Baumes bzw dem Anfang des Arrays haben wir dann das
Maximum aller Elemente. Dieses Element gilt als sortiert und wird mit dem letzten Platz des Arrays vertauscht, welcher Maximum aller Elemente. Dieses Element gilt als sortiert und wird mit dem letzten Platz des Arrays vertauscht, welcher
nun nichtmehr angefasst wird. Ein erneuter Aufruf von $\texttt{heapify}$ findet wieder das Maximum, wir vertauschen mit nun nicht mehr angefasst wird. Ein erneuter Aufruf von $\texttt{heapify}$ findet wieder das Maximum, wir vertauschen mit
dem vorletzten Feld, etc. dem vorletzten Feld, etc.
Damit haben wir ein worst-case optimalen in-place Sortieralgorithmus: Damit haben wir ein worst-case optimalen in-place Sortieralgorithmus:
...@@ -152,6 +148,13 @@ n)$ hat. Die Laufzeit wird von den $\frac{n}{2}$ aufrufen von $\texttt{heapify}$ ...@@ -152,6 +148,13 @@ n)$ hat. Die Laufzeit wird von den $\frac{n}{2}$ aufrufen von $\texttt{heapify}$
hat, eh dominiert. Also liegt die Laufzeit in $\mathcal{O}(n \log n)$. Damit ist $\texttt{heapsort}$ also worst-case hat, eh dominiert. Also liegt die Laufzeit in $\mathcal{O}(n \log n)$. Damit ist $\texttt{heapsort}$ also worst-case
optimal. optimal.
\paragraph{Eigenschaften:}
Der Algorithmus \texttt{heapsort} arbeitet in-place, solang man Zeile [1] in-place hinbekommt (Übungsaufgabe).
Desweiteren ist er worst-case-optimal. Allerdings ist er nicht stabil (Übungsaufgabe) und durch das initiale Verhalden
auch nicht adaptiv.
Desweiteren wird (ungeschickterweise) sehr häufig ein besonders kleines Element an die Spitze getauscht, womit sich auch
die echte Laufzeit, abseits von asymptotischer Analyse, unattraktiv verhält.
\begin{figure}[H] \begin{figure}[H]
\centering \centering
\input{bilder/heapsort_1.tex} \input{bilder/heapsort_1.tex}
...@@ -182,18 +185,19 @@ optimal. ...@@ -182,18 +185,19 @@ optimal.
\begin{figure}[H] \begin{figure}[H]
\centering \centering
\input{bilder/heapsort_4.tex} \input{bilder/heapsort_4.tex}
\caption{Der Algorithmus \texttt{heapsort} auf den Array $\mathcal{A} = [5,7,3,1,8,2]$. Im dritten Schritt wird \caption{Der Algorithmus \texttt{heapsort} auf den Array $\mathcal{A} = [5,7,3,1,8,2]$. Im vierten Schritt wird
\texttt{heapify} auf den Knoten mit dem Wert $7$ ausgeführt, also die Haldenbedingung zu seinem Unterbaum \texttt{heapify} auf den (getauschten) Knoten mit dem Wert $5$ ausgeführt, also die Haldenbedingung zu seinem Unterbaum
überprüft und gegenbenenfalls repariert. Da $8$ größer ist als $7$, werden die beiden Werte getauscht.} überprüft und gegenbenenfalls repariert. Da $7$ größer ist als $5$, werden die beiden Werte getauscht.}
\label{fig:heapsort_4} \label{fig:heapsort_4}
\end{figure} \end{figure}
\begin{figure}[H] \begin{figure}[H]
\centering \centering
\input{bilder/heapsort_5.tex} \input{bilder/heapsort_5.tex}
\caption{Der Algorithmus \texttt{heapsort} auf den Array $\mathcal{A} = [5,7,3,1,8,2]$. Im dritten Schritt wird \caption{Der Algorithmus \texttt{heapsort} auf den Array $\mathcal{A} = [5,7,3,1,8,2]$. Im fünften und letzten Schritt wird
\texttt{heapify} auf den Knoten mit dem Wert $7$ ausgeführt, also die Haldenbedingung zu seinem Unterbaum \texttt{heapify} auf den Knoten mit dem Wert $5$ ausgeführt, also die Haldenbedingung zu seinem Unterbaum
überprüft und gegenbenenfalls repariert. Da $8$ größer ist als $7$, werden die beiden Werte getauscht.} überprüft und gegenbenenfalls repariert. Da es sich hierbei um ein Blatt handelt, ist $\texttt{heapify}$ fertig und
damit auch $\texttt{heapify\_tree}$}.
\label{fig:heapsort_5} \label{fig:heapsort_5}
\end{figure} \end{figure}
...@@ -212,7 +216,39 @@ optimal. ...@@ -212,7 +216,39 @@ optimal.
\subsection{Wartschlange (Priority Queue)*} \subsection{Wartschlange (Priority Queue)*}
Eine der häufigsten Anwendungen von Halden: Warteschlangen mit einer Priorität, bei der immer das dringenste (also das Eine der häufigsten Anwendungen von Halden: Warteschlangen mit einer Priorität, bei der immer das dringenste (also das
größte) Element gesucht wird. Da das sofort das erste Arrayelement ist, kommt der Vorteil von Halden voll zum tragen. größte) Element gesucht wird. Da das sofort das erste Arrayelement ist, kommt der Vorteil von Halden voll zum tragen.
Einfügen und Löschen erfolgt mit $\mathcal{O}(\log n)$ aufwand, wie bei Baumstrukturen üblich. Einfügen und Löschen erfolgt mit $\mathcal{O}(\log n)$ Aufwand, wie bei Baumstrukturen üblich.
TODO: Details %
%\begin{algorithm}[H]
% \SetNlSty{texttt}{[}{]}
% \caption{\texttt{heapify}($\mathcal{H}, i$)}
% \KwIn{An almost-heap $\mathcal{H}$ of size $n$ with above conditions and the index $i \in \{0, \cdots, n-1\}$}
% \KwOut{A modified $\mathcal{H}$ with its heap structure intact}
% $l \leftarrow 2i + 1$ \;
% $r \leftarrow 2i + 2$ \;
% $j \leftarrow i$ \;
% \If{$l < n$ and $\mathcal{H}[l] > \mathcal{H}[i]$}{
% $j \leftarrow l$ \;
% }
% \If{$r < n$ and $\mathcal{H}[r] > \mathcal{H}[j]$}{
% $j \leftarrow r$ \;
% }
% \If{$i \neq j$}{
% $\texttt{swap}(\mathcal{H}[i], \mathcal{H}[j])$ \;
% $\mathcal{H} \leftarrow \texttt{heapify}(\mathcal{H},j)$ \;
% }
% \KwRet $\mathcal{H}$
%\end{algorithm}
%
%
%\begin{algorithm}[H]
% \SetNlSty{texttt}{[}{]}
% \caption{\texttt{heapify\_array}($\mathcal{A}$)}
% \KwIn{An array $\mathcal{A}$ of size $n$}
% \KwOut{The same Array but resorted as a heap}
% \For{$i \leftarrow \floor*{\frac{n}{2}}$ \KwTo $0$}{
% $\texttt{heapify}(\mathcal{A}, i)$ \;
% }
% \KwRet $\mathcal{A}$
%\end{algorithm}
%
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment