uni/ad/AD-Gruppe_8_Koehler_Krabbe_...

227 lines
9.9 KiB
TeX

\documentclass[10pt,a4paper,oneside,ngerman,numbers=noenddot]{scrartcl}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{bytefield}
\usepackage{paralist}
\usepackage{gauss}
\usepackage{pgfplots}
\usepackage{textcomp}
\usepackage[locale=DE,exponent-product=\cdot,detect-all]{siunitx}
\usepackage{tikz}
\usepackage{algorithm}
\usepackage{algorithmic}
\usetikzlibrary{matrix,fadings,calc,positioning,decorations.pathreplacing,arrows,decorations.markings}
\usepackage{polynom}
\polyset{style=C, div=:,vars=x}
\pgfplotsset{compat=1.8}
\pagenumbering{arabic}
\def\thesection{\arabic{section})}
\def\thesubsection{(\alph{subsection})}
\def\thesubsubsection{(\roman{subsubsection})}
\makeatletter
\renewcommand*\env@matrix[1][*\c@MaxMatrixCols c]{%
\hskip -\arraycolsep
\let\@ifnextchar\new@ifnextchar
\array{#1}}
\makeatother
\parskip 12pt plus 1pt minus 1pt
\parindent 0pt
\begin{document}
\author{Reinhard Köhler (6425686), Tronje Krabbe (6435002), \\
Jim Martens (6420323)}
\title{Hausaufgaben zum 6. November}
\subtitle{Gruppe 8}
\maketitle
\section{} %1
\subsection{} %a
In Level $l$ liegen maximal $k^{l}$ Knoten. Dies ist in einem vollen Baum auf jeder Ebene gegeben.
\subsection{} %b
Ein voller Baum der Tiefe $l$ hat auf der untersten Ebene $k^{l}$ Knoten. Daraus ergibt sich diese Summe:
\[
\sum\limits_{i=0}^{l} k^{i} = k^{l+1} - 1
\]
Dies gilt da in einem vollen Baum die Anzahl Knoten in einer Ebene immer einer Potenz von $k$ entsprechen.
\subsection{} %c
Ein vollständiger Baum der Tiefe $l$ gleicht bis auf die letzte Ebene einem vollen Baum. In der letzten Ebene $l$ kommen maximal $k^{l} - 1$ Knoten vor, damit es ein vollständiger Baum, aber kein voller Baum ist. Daraus ergibt sich diese leicht abgewandelte Formel:
\begin{alignat*}{2}
\sum\limits_{i=0}^{l-1} \left(k^{i}\right) + c &:& 1 \leq c < k^{l}
\end{alignat*}
\subsection{} %d
Jeder Knoten hat genau ein Elternknoten mit dem er über eine Kante verbunden ist. Einzige Ausnahme ist der Wurzelknoten, der kein Elternelement hat und damit auch keine Kante, die mit einem solchen verbunden sein könnte. Daher gibt es genau $n-1$ Kanten.
\section{} %2
\subsection{} %a
Laufzeit von Order1:
\[
T(n) = 2T\left(\frac{n}{2}\right) + n^{0}
\]
Laufzeit von Order2:
\[
T(n) = 2T\left(\frac{n}{2}\right) + n^{0}
\]
Laufzeit von Order3:
\[
T(n) = 2T\left(\frac{n}{2}\right) + n^{0}
\]
\subsection{} %b
Die Laufzeiten von Order1, Order2 und Order3 können im best-case auf $1$ verbessert werden.
\subsection{} %c
Order1: NAOEIFMRLUSGARTH \\
Order2: IEOFARMLNGSAUTRH \\
Order3: IEFORLMAGASTHRUN
\subsection{} %d
T = \begin{bytefield}{10}
\bitbox{1}{T}
\bitbox{1}{E}
\bitbox{1}{E}
\bitbox{1}{O}
\bitbox{1}{Y}
\bitbox{1}{R}
\bitbox{1}{E}
\bitbox{1}{L}
\bitbox{1}{V}
\bitbox{1}{L}
\end{bytefield}
\subsection{} %e
ALGORITHMSAREFUN
\section{} %3
\subsection{} %a
\begin{alignat*}{2}
f(x) &=& x \cdot \frac{\ln(n)}{\ln(x)} \\
&=& \frac{x}{\ln(x)} \cdot \ln(n) \\
f'(x) &=& \left(\frac{x}{\ln(x)} \cdot \ln(n)\right)' \\
&=& \left(\frac{x}{\ln(x)}\right)' \cdot \ln(n) \\
&=& \frac{\ln(x) - 1}{\ln^{2}(x)} \cdot \ln(n) \\
\intertext{Einsetzen von $e$ für $x$}
f'(e) &=& \frac{\ln(e) - 1}{\ln^{2}(e)} \cdot \ln(n) \\
&=& \frac{1 - 1}{1^{2}} \cdot \ln(n) \\
&=& 0 \\
\intertext{Da $f'(x)$ offensichtlich nur eine Nullstelle hat, haben wir hiermit die einzige Extremstelle von $f$ gefunden.}
f(e) &=& \frac{e}{\ln(e)} \cdot \ln(n) \\
&=& e \cdot \ln(n)
\end{alignat*}
Das Ergebnis der letzten Gleichung ist somit das Minima von $f$. Als weitere Absicherung kann das asymptotische Wachstum betrachtet werden. Für einen kleineren Wert als $e$, ist $\ln(x)$ kleiner als $1$. Das Teilen von $x$ durch diesen Wert geringer als $1$ sorgt dafür, dass das Ergebnis größer als $x$ ist. Lässt man $x$ gegen $1$ laufen, läuft der Bruch gegen unendlich. Auf der anderen Seite kann man $x$ gegen unendlich gehen lassen, dann läuft der Bruch auch gegen unendlich, da eine lineare Funktion schneller wächst, als eine logarithmische. Der konstante Faktor am Ende kann dabei außer Acht gelassen werden.
\subsection{} %b
Die beste Wahl für $k^{*}$ ist $3$. Es werden im worst-case bei der Heap-Größe $n=10^{l}$ mit $l \in \{1,...,9\}$ diese Anzahl an Schritten benötigt.
\begin{tabular}{c|c|c}
$l$ & $k = 3$ & $k = 2$ \\
\hline
1 & 7 & 7 \\
2 & 13 & 14 \\
3 & 19 & 20 \\
4 & 26 & 27 \\
5 & 32 & 34 \\
6 & 38 & 40 \\
7 & 45 & 47 \\
8 & 51 & 54 \\
9 & 57 & 60
\end{tabular}
\subsection{} %c
Ein binärer Heap (dementsprechend $k=2$) ist deutlich übersichtlicher als ein ternärer Heap. Außerdem ist ein binärer Heap leichter zu be- bzw. verarbeiten und der Unterschied des Laufzeitaufwandes zwischen einem binären und einem ternären Heap ist nicht sonderlich groß.
\subsection{} %d
Pro Vertauschen werden $k+1$ Schritte benötigt. Ein Schritt wird benötigt, um das Maximum herauszufinden und $k$ Schritte, um den Max-Heap des aktuellen Knoten nach dem Vertauschen wieder zu einem solchen zu machen. Damit werden zwar viele Schritte zum Finden eines Maximums der Kinder eingespart, allerdings an anderer Stelle wieder durch das Aufrufen von Heapify auf den zusätzlichen Max-Heap ausgegeben. Im Endeffekt ergibt sich damit eine Gesamtlaufzeit von $\lceil (k+1)\log_{k}(n) \rceil$.
\subsection{} %e
Anwenden von \textsc{Decrease}$(9 \mapsto 1)$ auf Ergebnis von 3d: 2 Vertauschungen \\
Anwenden von \textsc{Decrease}$(9 \mapsto 1)$ auf Ergebnis von 3f: eine Vertauschung
\subsection{} %f
Ein ternärer Heap hat bei gleicher Anzahl an Knoten maximal gleich viele Level, wodurch dieselbe \textsc{Decrease}-Operation bei einem ternären Heap immer maximal gleich viele Vertauschungen wie bei einem binären Heap erfordert.
\section{} %4
\subsection{} %a
merge (2 2 5 7 9, 1 2 4 8) \\
1 $\circ$ merge (2 2 5 7 9, 2 4 8) \\
1 2 $\circ$ merge (2 5 7 9, 2 4 8) \\
1 2 2 $\circ$ merge (5 7 9, 2 4 8) \\
1 2 2 2 $\circ$ merge (5 7 9, 4 8) \\
1 2 2 2 4 $\circ$ merge (5 7 9, 8) \\
1 2 2 2 4 5 $\circ$ merge (7 9, 8) \\
1 2 2 2 4 5 7 $\circ$ merge (9, 8) \\
1 2 2 2 4 5 7 8 $\circ$ merge (9, []) \\
1 2 2 2 4 5 7 8 9
\subsection{} %b
Input 6 7 8 3 4 2 9 1 \\
Rekursiv in einzelne Ziffern zerlegt und dann zusammengefügt: \\
\begin{tikzpicture}
\tikzset{
position label/.style={
below = 3pt,
text height = 1.5ex,
text depth = 1ex
},
brace/.style={
decoration={brace, mirror},
decorate
}
}
\node [position label] (a1) at (0,0) {$6$};
\node [position label] (a2) at (0.5,0) {$7$};
\node [position label] (a3) at (1,0) {$8$};
\node [position label] (a4) at (1.5,0) {$3$};
\node [position label] (a5) at (2,0) {$4$};
\node [position label] (a6) at (2.5,0) {$2$};
\node [position label] (a7) at (3,0) {$9$};
\node [position label] (a8) at (3.5,0) {$1$};
\draw [brace] (a1.south) -- (a2.south);
\draw [brace] (a3.south) -- (a4.south);
\draw [brace] (a5.south) -- (a6.south);
\draw [brace] (a7.south) -- (a8.south);
\node [position label] (b1) at (0.25,-0.7) {$67$};
\node [position label] (b2) at (1.25,-0.7) {$38$};
\node [position label] (b3) at (2.25,-0.7) {$24$};
\node [position label] (b4) at (3.25,-0.7) {$19$};
\draw [brace,decoration={raise=4ex}] (a1.south) -- (a4.south);
\draw [brace,decoration={raise=4ex}] (a5.south) -- (a8.south);
\node [position label] (c1) at (0.75,-1.4) {$3678$};
\node [position label] (c2) at (2.75,-1.4) {$1249$};
\draw [brace,decoration={raise=8ex}] (a1.south) -- (a8.south);
\node [position label] (d1) at (1.75,-2.1) {$12346789$};
\end{tikzpicture}
\subsection{} %c
Eine Möglichkeit eine absteigende Sortierung zu erreichen, ist das Umkehren von $x[1] \leq y[1]$ zu $x[1] \geq y[1]$.
Eine andere Möglichkeit ist das Vertauschen der Fälle in der \texttt{if}-Abfrage. Dabei bleibt die Bedingung der Abfrage gleich, allerdings wird statt dem ersten Element von $x$ das erste Element von $y$ genommen. Im \texttt{else}-Fall wird dann dementsprechend das erste Element von $x$ genommen.
\section{} %5
\subsection{} %a
Man benutzt einen Stack als Zwischenspeicher und einen Stack als die eigentliche Queue. Soll ein Element in die Queue eingefügt werden, wird jedes Element des Hauptstacks nach und nach entfernt und auf den Speicherstack geschrieben. Dann wird das hinzuzufügende Element in den Hauptstack geschrieben. Danach werden nach und nach alle Elemente aus dem Speicherstack entfernt und auf den Hauptstack geschrieben. So sind in dem Hauptstack die Elemente in der Reihenfolge gespeichert, in der sie ausgelesen werden sollen (FIFO-Prinzip). Soll nun ein Element aus der Queue entfernt werden, wird einfach die pop-Operation an dem Hauptstack aufgerufen, womit das Element, das zuerst eingefügt wurde, entfernt wird, wie es bei einer Queue der Fall ist.
\begin{verbatim}
function ENQUEUE(e):
if Hauptstack.isEmpty():
Hauptstack.push(e);
else:
for element in Hauptstack:
Speicherstack.push(element);
Hauptstack.pop();
end for
Hauptstack.push(e);
for element in Speicherstack:
Hauptstack.push(element);
Speicherstack.pop();
end for
end if
end function
function DEQUEUE():
Hauptstack.pop();
end function
\end{verbatim}
Im worst-case ist die Laufzeit von \textsc{Dequeue} $\theta(1)$. Die worst-case Laufzeit von \textsc{Enqueue} ist bei $k$ Elementen $2k+1$.
\subsection{} %b
Die worst-case Laufzeit von $n$ \textsc{Enqueue}-Operationen beträgt $n \cdot (2n+1) = 2n^{2} + n$. Die amortisierte Laufzeit beträgt dann $\frac{n(2n+1)}{n} = 2n+1$. Dabei muss beachtet werden, dass bei weniger \textsc{Enqueue}- und mehr \textsc{Dequeue}-Operationen dementsprechend auch das Ergebnis weniger groß ausfällt.
\end{document}