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

Merge branch 'master' of...

Merge branch 'master' of gitlab.tugraz.at:6048DFF64EAA81BC/datenstrukturen-und-algorithm-ss2020-skript
parents ad166213 34e3a1e4
No related branches found
No related tags found
No related merge requests found
...@@ -209,13 +209,13 @@ die Motivation für die folgende Alternativdefinition: ...@@ -209,13 +209,13 @@ die Motivation für die folgende Alternativdefinition:
\subsection{Definition der asymptotischen Schranken über den Limes} \subsection{Definition der asymptotischen Schranken über den Limes}
Wir erinnern uns an die Definition des Limes superior bzw Limes inferior: Wir erinnern uns an die Definition des Limes superior bzw Limes inferior:
\begin{definition}[Limes superior und Limes inferior] \begin{definition}[Limes superior und Limes inferior]
Sei $g: \mathbb{N} \rightarrow \mathbb{R}^+$ eine positive Funktion. Wir definieren Sei $f: \mathbb{N} \rightarrow \mathbb{R}^+$ eine positive Funktion. Wir definieren
\[ \[
\limsup_{n \rightarrow \infty} g(n) := \inf_{n_0 \in \mathbb{N}} \sup_{n \ge n_0} g(n) \limsup_{n \rightarrow \infty} f(n) := \inf_{n_0 \in \mathbb{N}} \sup_{n \ge n_0} g(n)
\] \]
und analog und analog
\[ \[
\liminf_{n \rightarrow \infty} g(n) := \sup_{n_0 \in \mathbb{N}} \inf_{n \ge n_0} g(n) \liminf_{n \rightarrow \infty} f(n) := \sup_{n_0 \in \mathbb{N}} \inf_{n \ge n_0} g(n)
\] \]
\end{definition} \end{definition}
Für uns der größte Vorteil ist, dass unter den gegebenen Umständen sowohl der Limes superior als auch der Limes inferior Für uns der größte Vorteil ist, dass unter den gegebenen Umständen sowohl der Limes superior als auch der Limes inferior
...@@ -238,8 +238,9 @@ Damit können wir nicht nur Kriterien für asymptotisches Wachstum benennen, son ...@@ -238,8 +238,9 @@ Damit können wir nicht nur Kriterien für asymptotisches Wachstum benennen, son
\begin{lemma} \begin{lemma}
Seien $f, f', g, g', h: \mathbb{N} \rightarrow \mathbb{R}^+$. Seien $f, f', g, g', h: \mathbb{N} \rightarrow \mathbb{R}^+$.
\begin{labeling}{Multiplikation\ \ } \begin{labeling}{Multiplikation\ \ }
\item[Addition] Seien $f \in \mathcal{O}(g)$ und $f' \in \mathcal{O}(g')$. Dann ist $f+f' \in \mathcal{O}(g) \item[Addition] Seien $f \in \mathcal{O}(g)$ und $f' \in \mathcal{O}(g')$. Dann ist $f+f' \in \mathcal{O}(m)$, wobei
\cup \mathcal{O}(g')$. $m(n) = \text{max}(g(n), g'(n))$ die Funktion in Abhängigkeit von $g$ und $g'$ ist, welche Elementweise das Maximum
nimmt.
\item[Subtraktion] Seien $f, f' \in \mathcal{O}(g)$. Dann gilt $f-f' \in \mathcal{O}(g)$. \item[Subtraktion] Seien $f, f' \in \mathcal{O}(g)$. Dann gilt $f-f' \in \mathcal{O}(g)$.
\item[Multiplikation] Seien $f \in \mathcal{O}(g)$ und $f' \in \mathcal{O}(g')$. Dann gilt $ff' \in \item[Multiplikation] Seien $f \in \mathcal{O}(g)$ und $f' \in \mathcal{O}(g')$. Dann gilt $ff' \in
\mathcal{O}(gg')$. \mathcal{O}(gg')$.
...@@ -247,10 +248,8 @@ Damit können wir nicht nur Kriterien für asymptotisches Wachstum benennen, son ...@@ -247,10 +248,8 @@ Damit können wir nicht nur Kriterien für asymptotisches Wachstum benennen, son
\end{labeling} \end{labeling}
\label{lemma:asymptotische_rechenregeln} \label{lemma:asymptotische_rechenregeln}
\end{lemma} \end{lemma}
Zur Additionsregel sei angemerkt, dass sie durch die Transitivitätsregel meist weiter vereinfachen lässt. Wachsen Zur Additionsregel sei angemerkt, dass in vielen praktischen Fällen meist $g \in \mathcal{O}(g')$ oder andersherum gilt.
$g$ und $g'$ gleich schnell, also $g \in \Theta(g')$, so ist $\mathcal{O}(g) = \mathcal{O}(g')$. Wächst o.B.d.A. $g'$ stärker Dann kann man $m$ einfach durch $g'$ (bzw $g$) ersetzen.
als $g$, also $g \in \mathcal{O}(g')$, so ist $\mathcal{O}(g) \cup \mathcal{O}(g') = \mathcal{O}(g')$. Nur für den
seltenen Fall, das $g \notin \mathcal{O}(g')$ und $g' \notin \mathcal{O}(g)$ bleibt es bei der Meingenvereinigung.
\begin{proof} \begin{proof}
Wir betrachten zur Veranschaulichung die Transitivität genauer. Wir betrachten zur Veranschaulichung die Transitivität genauer.
......
...@@ -5,27 +5,18 @@ Was ist eine Datenstruktur? Diesen Begriff mathematisch präzise zu fassen ist s ...@@ -5,27 +5,18 @@ Was ist eine Datenstruktur? Diesen Begriff mathematisch präzise zu fassen ist s
anderen Vorlesungen und begnügen uns mit einer unpräziseren Definition: Datenstrukturen sind \emph{Daten} mit einer anderen Vorlesungen und begnügen uns mit einer unpräziseren Definition: Datenstrukturen sind \emph{Daten} mit einer
\emph{Struktur}. \emph{Struktur}.
Daten meint dabei sowohl elementare Datentypen wie \texttt{int, float, char}, aber auch komplexere oder abstraktere Daten meint dabei sowohl elementare Datentypen wie \texttt{int, float, char}, aber auch komplexere Gebilde wie ein Tupel
Gebilde wie die Repräsentation eines Gegenstandes oder $x \in \mathbb{R}$. $(\text{int}, \text{float})$ oder abstraktere Gebilde wie die Repräsentation eines Gegenstandes oder $x \in \mathbb{R}$.
Die Menge der Daten bezeichnen wir mit $D$.
Struktur kann viel bedeuten. In diesem Kapitel befassen wir uns hauptsächlich mit Strukturen, die eine lineare Ordnung Struktur kann viel bedeuten. In diesem Kapitel befassen wir uns hauptsächlich mit Strukturen, die eine lineare Ordnung
ermöglichen, die uns also erlauben zu sagen, in welcher ``eindeutigen Reihenfolge'' sich die Daten befinden. ermöglichen, die uns also erlauben zu sagen, in welcher ``eindeutigen Reihenfolge'' sich die Daten befinden.
Datenstrukturen, in denen mehr als ein Nachfolger erlaubt ist, behandeln wir in späteren Kapiteln. Datenstrukturen, in denen mehr als ein Nachfolger erlaubt ist, behandeln wir in späteren Kapiteln.
\paragraph{Die Menge: Eine Datenstruktur fast ohne Struktur}
Ein kurzes, einführendes Beispiel betrachtet aber eine Datenstruktur mit noch viel weniger Struktur, die sogenannte
Menge (set). Ganz wie die naive Definition einer mathematischen Menge ist das eine ``Zusammenfassung bestimmter,
wohlunterschiedener Objekte unserer Anschauung oder unseres Denkens zu einem Ganzen'' (Cantor).
Vorstellen kann man es sich als Sack, in welchem verschiedene Dinge sind. Die einzige Struktur, die die Menge
auferlegt, ist die Tatsache, das ein Element nicht mehrfach vorkommen darf (das wäre dann ein Multiset). Ansonsten haben
wir keinerlei Reihenfolge oder sonstige Struktur. Das Entfernen eines Elements geschieht in konstanter
Zeit ($\mathcal{O}(1)$), das Suchen und Einfügen muss durch elementweises Vergleichen geschehen ($\mathcal{O}(n)$, wobei $n$ die
Anzahl der Elemente ist). Sortieren ist schlicht nicht möglich.
\paragraph{Datenstrukturen mit einer Reihenfolge} \paragraph{Datenstrukturen mit einer Reihenfolge}
Im Folgenden betrachen wir die vier bekanntesten Datenstrukturen, die uns zumindest eine Reihenfolge geben. Das Im Folgenden betrachen wir die vier bekanntesten Datenstrukturen, die uns zumindest eine Reihenfolge geben. Das
mathematische Äquivalent wäre also nicht mehr die Menge, sondern ein Tupel $(d_0, d_1, d_2, \dots)$ mit $d_i$ als mathematische Äquivalent wäre also nicht eine Menge, sondern ein Tupel $(d_0, d_1, d_2, \dots)$ mit $d_i$ als
das Nutzdatum an der $i$-ten Stelle. Damit einher geht das Nutzdatum an der $i$-ten Stelle. Damit einher geht
die Einführung des Index $i \in \mathbb{N}_0$, wir fangen also (um Verwirrung beim Programmieren vorzubeugen) mit die Einführung des Index $i \in \mathbb{N}_0$, wir fangen also (um Verwirrung beim Programmieren vorzubeugen) mit
$0$ an zu zählen. Dieser Index erlaubt uns, den Index $i$ (manchmal auch Schlüssel) und die Daten der Datenstruktur $0$ an zu zählen. Dieser Index erlaubt uns, den Index $i$ (manchmal auch Schlüssel) und die Daten der Datenstruktur
...@@ -33,8 +24,32 @@ $\mathcal{D}$ zu unterscheiden. Die Daten der Datenstruktur an der Stelle $i$ ne ...@@ -33,8 +24,32 @@ $\mathcal{D}$ zu unterscheiden. Die Daten der Datenstruktur an der Stelle $i$ ne
Wir werden zusätzlich Implementierungen dieser Datenstrukturen angeben. Diese sind in imperativer Perspektive für ein Wir werden zusätzlich Implementierungen dieser Datenstrukturen angeben. Diese sind in imperativer Perspektive für ein
Maschinenmodell mit Random-Access-Memory (RAM) $\mathcal{M}$ zu verstehen. Maschinenmodell mit Random-Access-Memory (RAM) $\mathcal{M}$ zu verstehen.
Die definierende Eigenschaft dieses Maschinenmodells ist, dass der Zugriff $\mathcal{M}[i]$ auf ein Speicherfeld stets gleich lange braucht, Um zu beschreiben, an welcher Stelle in $\mathcal{M}$ wir zugreifen wollen benutzen wir sogenannte pointer $p \in P =
egal wo es liegt. Der Einfachheit halber nehmen wir zudem an, unendlich viel Speicher zu haben, also $i \in \mathbb{N}$. \mathbb{N} \cup \{\text{void}\}$. Ein Zugriff ist dann wie folgt definiert:
\[
\mathcal{M}[p] =
\begin{cases}
\texttt{error} &\text{ if } p = \texttt{void} \\
d_p &\text{ else},
\end{cases}
\]
wobei $d_p$ der Speicherinhalt an der Stelle $p$ ist.
Die definierende Eigenschaft dieses Maschinenmodells ist, dass der Zugriff $\mathcal{M}[p]$ auf ein Speicherfeld stets gleich lange braucht,
egal wo es liegt. Desweiteren nehmen wir an, unendlich viele Speicherfelder zu haben und nicht nur das, auch jedes
einzelne Speicherfeld fasst unser Datum $d$, egal wie groß es sein mag.
\begin{figure}[!htp]
\centering
\tikzsetnextfilename{ram}
\begin{tikzpicture}[box/.style = {draw, minimum size=9mm, inner sep=0pt, outer sep=0pt, anchor=center},]
\matrix (array) [matrix of nodes, nodes={box}, column sep=-\pgflinewidth, inner sep=0pt]
{
$d_0 $ & $d_1$ & \qquad $\dots$ \qquad \vphantom{3} & $d_{p-1}$ & $d_p$ & $d_{p+1}$ & \qquad $\dotsm$ \qquad \vphantom{3} \\
};
\end{tikzpicture}
\caption{Der (unendliche) random access Maschinenspeicher $\mathcal{M}$.}
\end{figure}
\paragraph{Notation} \paragraph{Notation}
In der Hoffnung, die folgende Mischung aus mathematischer Notation, Pseudocode und Erklärungen verständlicher zu In der Hoffnung, die folgende Mischung aus mathematischer Notation, Pseudocode und Erklärungen verständlicher zu
...@@ -45,7 +60,7 @@ notationell zu den Speicherstrukturen. Systemfunktionen wie $\texttt{reference\_ ...@@ -45,7 +60,7 @@ notationell zu den Speicherstrukturen. Systemfunktionen wie $\texttt{reference\_
$\texttt{len}$ für die Länge von Speicherstrukturen werden in \texttt{truetype} gesetzt, um ihre Maschinennähe darzustellen. $\texttt{len}$ für die Länge von Speicherstrukturen werden in \texttt{truetype} gesetzt, um ihre Maschinennähe darzustellen.
Ebenso bekommen all unsere in Psudeocode notierten Algorithmen diese Notation. Ebenso bekommen all unsere in Psudeocode notierten Algorithmen diese Notation.
\subsection{Lineares Feld (Array)} \subsection{Arrays $\mathcal{A}$}
Das Array $\mathcal{A}$ ist eine Datenstruktur von fixer Größe $n \in \mathbb{N}$. Aus logischer Perspektive stehen benachbarte Das Array $\mathcal{A}$ ist eine Datenstruktur von fixer Größe $n \in \mathbb{N}$. Aus logischer Perspektive stehen benachbarte
Elemente direkt nebeneinander. Das Array $\mathcal{A}$ von Größe $n$ ist also nichts weiter als Elemente direkt nebeneinander. Das Array $\mathcal{A}$ von Größe $n$ ist also nichts weiter als
$\mathcal{M}[s,\dots,s+n-1]$, wobei $s \in \mathbb{N}$ ein gewisser Versatz ist. $\mathcal{M}[s,\dots,s+n-1]$, wobei $s \in \mathbb{N}$ ein gewisser Versatz ist.
...@@ -62,13 +77,21 @@ Wir geben daher später keine Laufzeiten für das Einfügen oder Entfernen von E ...@@ -62,13 +77,21 @@ Wir geben daher später keine Laufzeiten für das Einfügen oder Entfernen von E
\begin{tikzpicture}[box/.style = {draw, minimum size=9mm, inner sep=0pt, outer sep=0pt, anchor=center},] \begin{tikzpicture}[box/.style = {draw, minimum size=9mm, inner sep=0pt, outer sep=0pt, anchor=center},]
\matrix (array) [matrix of nodes, nodes={box}, column sep=-\pgflinewidth, inner sep=0pt] \matrix (array) [matrix of nodes, nodes={box}, column sep=-\pgflinewidth, inner sep=0pt]
{ {
$\mathcal{A}[0]$ & $\mathcal{A}[1]$ & $\mathcal{A}[2]$ & \quad$\dotsm$\quad\vphantom{4} & $\mathcal{A}[n-1]$ \\ & & & $\mathcal{A}[0]$ & $\mathcal{A}[1]$ & \quad $\dotsm$
\quad \vphantom{3} & \enspace $\mathcal{A}[n-1]$ \enspace & \\
$d_0 $ & \quad $\dots$ \quad \vphantom{3} & $d_{s-1}$ & $d_s$ & $d_{s+1}$ & \quad $\dotsm$
\quad \vphantom{3} & \quad $d_{s+n-1}$ \quad & \quad $\dots$ \quad \vphantom{3} \\
}; };
\end{tikzpicture} \end{tikzpicture}
\caption{Ein Array $\mathcal{A}$ von Länge $n$} \caption{Ein Array $\mathcal{A}$ von Länge $n$ mit offset $s$ in $\mathcal{M}$.}
\end{figure} \end{figure}
\subsection{Listen} Arrays sind die einfachsten Datenstrukturen auf Maschinen mit Random-Access-Memory. Allerdings steht der Vorteil der immer gleichen
Zugriffszeit wird dem Nachteil der Inflexibilität gegenüber: Zur Laufzeit können Elemente weder hinten, noch irgendwie
in der Mitte eingefügt werden. Es ist lediglich ein neubeschreiben der Felder möglich. Desweiteren sind Arrays
\emph{endlich} lang.
\subsection{Listen $\mathcal{L}$}
Nicht immer können wir vorhersagen, wie viele Datensätze wir im Laufe des Programmablaufs entgegennehmen. Daher benötigen wir Nicht immer können wir vorhersagen, wie viele Datensätze wir im Laufe des Programmablaufs entgegennehmen. Daher benötigen wir
Datenstrukturen, in welche wir Daten einfügen, aber auch wieder löschen können. Ein elementarer Prototyp ist die einfach Datenstrukturen, in welche wir Daten einfügen, aber auch wieder löschen können. Ein elementarer Prototyp ist die einfach
verkettete Liste: verkettete Liste:
...@@ -81,22 +104,20 @@ verkettete Liste: ...@@ -81,22 +104,20 @@ verkettete Liste:
\label{fig:linked_list} \label{fig:linked_list}
\end{figure} \end{figure}
Eine Liste $\mathcal{L}$ besteht dabei aus Elementen $(d,p_{\text{next}})$, wobei $d$ die Nutzlast und Aus einer (etwas abstrakteren) Sicht gibt es dann Nodes $D \times P$ und Listen an sich sind eigentlich nur
$p_{\text{next}}$ einen Zeiger (also die Speicheradresse bzw der Index in unserem RAM) auf das nächste Listenelement oder den sogenannten headnodes, also ein Node, der keine Daten enthält, sondern nur auf das erste Element der Liste zeigt.
Nullpointer \texttt{void} darstellt. Wir wollen dabei folgende Operationen haben:
\begin{itemize}
Haben wir also einen Zeiger $p$ zu einem Element, so bekommen wir mit der Funktion $\texttt{data}(\mathcal{M}[p])$ die Datennutzlast $d$ und mit \item $\texttt{is\_empty}: \mathcal{L} \rightarrow \text{Bool}$,
$\texttt{next}(\mathcal{M}[p])$ den Zeiger $p_{\text{next}}$ zum nächsten Element. Ist der Zeiger $p_{\text{next}} = \item $\texttt{head}: \mathcal{L} \rightarrow \text{Node}$,
\texttt{void}$, so handelt es sich also um das letzte Element dieser Liste. Sowohl die Anfrage \item $\texttt{next}: \text{Node} \rightarrow \text{Node}$,
$\texttt{next}(\mathcal{M}[\texttt{void}])$ als auch $\texttt{data}(\mathcal{M}[\texttt{void}])$ bringen uns einen \item $\texttt{data}: \text{Node} \rightarrow D$,
Fehler. \item $\texttt{pointer}: \text{Node} \rightarrow P$,
\item $\texttt{insert\_after}: \text{Node} \times D \rightarrow \{\}$,
Nun hat unsere Liste ein Ende, sie braucht nur noch einen Anfang. Wir haben dafür ein spezielles Element \item $\texttt{delete\_after}: \text{Node} \rightarrow \{\}$,
$\texttt{head}(\mathcal{L})$, welches wir als nutzlastfreies Element verstehen, welches nur die Referenz auf das erste \end{itemize}
(richtige) Element enthält. Ein Node $(d,\texttt{void})$ ist dabei das letzte Element einer Liste, ein Node $(\texttt{void}, p)$ ist der headnode.
Eine neue, leere Liste ist also lediglich dieses Kopfelement $(\texttt{void} ,\texttt{void})$.
Eine neue, leere Liste ist also lediglich dieses Kopfelement $(\texttt{void} ,\texttt{void})$: In diesem Fall haben wir
keine Nutzlast, weil wir das Headelement sind (hier ebenfalls $\texttt{void}$ ausgedrückt) und der Pointer auf das nächste Element ist der Nullpointer $\texttt{void}$.
Der Bequemlichkeit halber führen wir auch für Listen eine Indexnotation ein: $\mathcal{L}[0]$ referiert auf das Der Bequemlichkeit halber führen wir auch für Listen eine Indexnotation ein: $\mathcal{L}[0]$ referiert auf das
erste Element der Liste, $\mathcal{L}[2]$ auf das dritte, etc. Dabei ist die Abkürzung definiert als erste Element der Liste, $\mathcal{L}[2]$ auf das dritte, etc. Dabei ist die Abkürzung definiert als
...@@ -107,32 +128,29 @@ erste Element der Liste, $\mathcal{L}[2]$ auf das dritte, etc. Dabei ist die Abk ...@@ -107,32 +128,29 @@ erste Element der Liste, $\mathcal{L}[2]$ auf das dritte, etc. Dabei ist die Abk
\end{align*} \end{align*}
Offensichtlich ist hier, anders als beim Array, die Zugriffszeit linear abhängig von~$i$. Offensichtlich ist hier, anders als beim Array, die Zugriffszeit linear abhängig von~$i$.
\subsubsection{Implementierung von Listen auf RAM}
\subsubsection{Listenoperationen} Nodes sind Tupel $(d,p)$, headnode ist $(\texttt{void}, p)$. Die meisten Operationen sind trivial:
Beginnen wir mit einem einfachen Check, ob unsere Liste $\mathcal{L}$ leer ist oder nicht:\\ \begin{itemize}
\begin{algorithm}[H] \item $\texttt{is\_empty}(\mathcal{L}) = \text{True}$ genau dann, wenn
\SetNlSty{texttt}{[}{]} $\texttt{pointer}(\texttt{head}(\mathcal{L})) = \texttt{void}$.
\caption{\texttt{is\textunderscore empty}$(\mathcal{L})$} \item $\texttt{head}(\mathcal{L}) = (\texttt{void},p)$,
\KwIn{A list $\mathcal{L}$} \item $\texttt{next}((d,p)) = \mathcal{M}[p]$,
\KwOut{A binary value. \texttt{True} in case $\mathcal{L}$ is empty, \texttt{False} otherwise} \item $\texttt{data}((d,p)) = d$,
\eIf{$\texttt{next}(\texttt{head}(\mathcal{L})) = \texttt{void}$}{ \item $\texttt{pointer}((d,p)) = p$,
return \texttt{True} \; \end{itemize}
}{
return \texttt{False} \;
}
\end{algorithm}
Anders als in einem Array können wir nun an jeder Stelle der Liste etwas einfügen, siehe Abbildung Anders als in einem Array können wir nun an jeder Stelle der Liste etwas einfügen, siehe Abbildung
\ref{fig:linked_list_insert}. Beachtenswert ist hierbei, dass bei Kenntnis der Adresse des vorherigen Listenelements nur \ref{fig:linked_list_insert}. Beachtenswert ist hierbei, dass bei Kenntnis der Adresse des vorherigen Listenelements nur
$\mathcal{O}(1)$ Aufwand vonnöten ist. \\ $\mathcal{O}(1)$ Aufwand vonnöten ist. \\
\begin{algorithm}[H] \begin{algorithm}[H]
\SetNlSty{texttt}{[}{]} \SetNlSty{texttt}{[}{]}
\caption{\texttt{insert\textunderscore after}$(p, d)$} \caption{\texttt{insert\textunderscore after}$((d,p), d')$}
\KwIn{A pointer $p$ to the list element after which to insert the new list element storing $d$} \KwIn{A node $(d,p)$ to the list element after which to insert the new list element storing $d'$}
\KwOut{Side effects in the memory $\mathcal{M}$} \KwOut{Side effects in the memory $\mathcal{M}$}
new\_element = ($d$, \texttt{next}($\mathcal{M}$[p])) \; new\_element $= (d', \texttt{pointer}(\texttt{next}((d,p))))$ \;
\texttt{next}($\mathcal{M}[p]$) $\leftarrow$ \texttt{reference\_of}(new\_element)\; $\texttt{next}(\mathcal{M}[p]) \leftarrow \texttt{reference\_of}(new\_element)$\;
\end{algorithm} \end{algorithm}
Hier gibt $\texttt{reference\_of}$ die Speicherposition des frisch erstellten Nodes zurück.
\begin{figure}[!htb] \begin{figure}[!htb]
\centering \centering
...@@ -145,10 +163,10 @@ Auch für das Löschen ist nur das Umbiegen eines einzelnen Zeigers nötig, sieh ...@@ -145,10 +163,10 @@ Auch für das Löschen ist nur das Umbiegen eines einzelnen Zeigers nötig, sieh
\begin{algorithm}[H] \begin{algorithm}[H]
\SetNlSty{texttt}{[}{]} \SetNlSty{texttt}{[}{]}
\caption{\texttt{delete\textunderscore after(p)}} \caption{\texttt{delete\textunderscore after((d,p))}}
\KwIn{A pointer $p$ to the list element whose successor shall be removed from the list} \KwIn{A node $(d,p)$ to the list element whose successor shall be removed from the list}
\KwOut{Side effects in the memory $\mathcal{M}$} \KwOut{Side effects in the memory $\mathcal{M}$}
$\texttt{next}(\mathcal{M}[p]) \leftarrow \texttt{next}(\texttt{next}(\mathcal{M}[p]))$ \; $((d,p)) \leftarrow (d, \texttt{pointer}(\texttt{next}((d,p))))$ \;
\end{algorithm} \end{algorithm}
\begin{figure}[!htb] \begin{figure}[!htb]
...@@ -161,7 +179,7 @@ Auch für das Löschen ist nur das Umbiegen eines einzelnen Zeigers nötig, sieh ...@@ -161,7 +179,7 @@ Auch für das Löschen ist nur das Umbiegen eines einzelnen Zeigers nötig, sieh
\subsubsection{Varianten von Listen} \subsubsection{Varianten von Listen}
Es gibt Listen in fast allen Geschmacksrichtungen. Häufig verwendet werden beispielsweise die sogenannten doppelt Es gibt Listen in fast allen Geschmacksrichtungen. Häufig verwendet werden beispielsweise die sogenannten doppelt
verketteten Listen (Abbildung verketteten Listen (Abbildung
\ref{fig:doubly_linked_list}). Deren Elemente $(d,p_{\text{prev}}, p_{\text{next}})$ haben zwei Pointer, sodass in beide \ref{fig:doubly_linked_list}). Deren Nodes $(d,p_{\text{prev}}, p_{\text{next}})$ haben zwei Pointer, sodass in beide
Richtungen traversiert werden kann. Richtungen traversiert werden kann.
\begin{figure}[!htb] \begin{figure}[!htb]
...@@ -181,8 +199,13 @@ bei denen anstelle der Referenz auf $\texttt{void}$ wieder auf den Anfang refere ...@@ -181,8 +199,13 @@ bei denen anstelle der Referenz auf $\texttt{void}$ wieder auf den Anfang refere
\label{fig:circular_linked_list} \label{fig:circular_linked_list}
\end{figure} \end{figure}
\subsubsection{Anwendung}
Verkettete Listen sind nur noch in den seltensten Fällen wirklich notwendig, meistens gibt es bessere Datenstrukturen.
Allerdings kann eine doppelt verkettete Liste als die natürlichste Datenstruktur einer Turing-Maschine betrachtet
werden.
\subsection{Stack}
\subsection{Stacks $\mathcal{S}$}
Ein Stack $\mathcal{S}$ ist wie ein Stapel Teller: Hinzufügen oder Entnehmen von weiteren Tellern ist nur an der Spitze möglich. Ein Stack $\mathcal{S}$ ist wie ein Stapel Teller: Hinzufügen oder Entnehmen von weiteren Tellern ist nur an der Spitze möglich.
Viele Anwendungen brauchen genau das, aber auch nicht mehr. Beispielsweise das Umdrehen der Reihenfolge einer Sequenz, Viele Anwendungen brauchen genau das, aber auch nicht mehr. Beispielsweise das Umdrehen der Reihenfolge einer Sequenz,
...@@ -202,7 +225,7 @@ $\texttt{pop}$, welches den obersten Teller entfernt. Die LIFO-Bedingung wird da ...@@ -202,7 +225,7 @@ $\texttt{pop}$, welches den obersten Teller entfernt. Die LIFO-Bedingung wird da
beschrieben. beschrieben.
\subsubsection{Stackoperationen} \subsubsection{Stackoperationen}
Wir implementieren den Stack $\mathcal{S}$ als oben definierte verkettete Liste. Ein leerer Stack ist daher auch einfach Wir implementieren den Stack $\mathcal{S}$ mittels einer oben definierten verkettete Liste. Ein leerer Stack ist daher auch einfach
eine leere Liste $\mathcal{L}$. eine leere Liste $\mathcal{L}$.
Der Test auf Inhalt ist somit ident zum entsprechenden $\texttt{is\_empty}$ für Listen. Der Test auf Inhalt ist somit ident zum entsprechenden $\texttt{is\_empty}$ für Listen.
...@@ -226,19 +249,15 @@ Auslesen, und dem Löschen: \\ ...@@ -226,19 +249,15 @@ Auslesen, und dem Löschen: \\
Charmant für uns ist, dass alle Operationen auf Stacks jeweils $\mathcal{O}(1)$ Zeit brauchen, meist also Charmant für uns ist, dass alle Operationen auf Stacks jeweils $\mathcal{O}(1)$ Zeit brauchen, meist also
vernachlässigbar sind. vernachlässigbar sind.
\subsection{Queue} \subsection{Queues $\mathcal{Q}$}
Anders als der Stapel, der die LIFO-Strategie verfolgt, beschreibt die Queue eine (faire) Warteschlange: Wer als erstes Anders als der Stapel, der die LIFO-Strategie verfolgt, beschreibt die Queue eine (faire) Warteschlange: Wer als erstes
da war, kommt als erstes dran. Man nennt es auch die FIFO-Strategie (first-in, first-out). da war, kommt als erstes dran. Man nennt es auch die FIFO-Strategie (first-in, first-out).
Ansonsten hat auch sie zwei Funktion, $\texttt{put}$, das Hintanstellen, sowie $\texttt{get}$, das Drankommen. Ansonsten hat auch sie zwei Funktion, $\texttt{put}$, das Hintanstellen, sowie $\texttt{get}$, das Drankommen.
Anwendungsfälle sind vor allem die namensgebenden Warteschlangen. Man könnte also beispielsweise einen sehr primitiven
Betriebsystemscheduler damit beschreiben. Häufiger kommt es aber beispielsweise bei der Arbeitsverteilung eines aus
vielen unabhängigen Einzeloperationen bestehenden Programms auf einzelne Threads vor.
\subsubsection{Queueoperationen} \subsubsection{Queueoperationen}
Wir implementieren eine Queue $\mathcal{Q}$ wieder über eine verkettete Liste $\mathcal{L}$, aber diesmal müssen wir Wir implementieren eine Queue $\mathcal{Q}$ wieder über eine verkettete Liste $\mathcal{L}$, aber diesmal sollten wir
noch eine Referenz auf das letzte Element mitführen (sonst müssten wir immer mit $\mathcal{O}(n)$ Aufwand zum Ende der noch eine Referenz auf das letzte Element mitführen (sonst müssten wir immer mit $\mathcal{O}(n)$ Aufwand zum Ende der
verketteten Liste laufen). Die Queue $\mathcal{Q}$ besteht also also aus $\mathcal{Q} = (\mathcal{L}, p_{\text{last}})$. verketteten Liste laufen. Das ist prinzipiell möglich, aber langsam.). Die Queue $\mathcal{Q}$ besteht also also aus $\mathcal{Q} = (\mathcal{L}, p_{\text{last}})$.
Wir fügen Elemente am Ende der Liste hinzu und lesen sie vom Anfang der Kette ab. Wir fügen Elemente am Ende der Liste hinzu und lesen sie vom Anfang der Kette ab.
Der Test auf Leere ist wieder analog wie bei Listen. Der Test auf Leere ist wieder analog wie bei Listen.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment