Sie sind auf Seite 1von 7

Listen

Knoten
Ein Knoten (engl. node) bezeichnet ein Element der Liste, welches die Daten und einen
Zeiger auf seinen Nachfolger enthält.

Typen
Man unterscheidet grundsätzlich zwischen einfach und mehrfach oder doppelt verketteten
Listen.

Einfach verkettete Liste


Sie ist die einfachste Form der verketteten Liste, da sie neben den einzelnen Knoten nur einen
"Start"-Zeiger besitzt. Dieser zeigt auf den ersten Knoten in der Liste (roter Pfeil). Der letzte
Knoten in der Liste zeigt auf den Nullwert(hier NIL für Not In List). (Als Nullwert (kurz
NULL oder NIL) bezeichnet man in der Informatik einen Wert, der das Fehlen eines gültigen
Wertes anzeigen soll. Er steht für „nicht vorhanden“ und darf nicht mit dem Zahlenwert 0
verwechselt werden. Die Vereinbarung eines undefinierten Werts ist eine wesentliche
semantische bzw. pragmatische Ergänzung der Informatik (bzw. Angewandten Mathematik)
gegenüber der theoretischen Mathematik.)
Verbale Definition: Eine Liste ist entweder leer oder sie besteht aus einem Kopf und einem
Zeiger, der wiederum eine Liste ist.

Vorteile
• Elemente können sehr schnell am Anfang der Liste eingefügt werden; Sehr geringer
Speicherbedarf
Nachteile
• Es ist aufwändig nach Daten zu suchen, Knoten einzufügen, zu löschen und die Liste
zu sortieren, da über jedes einzelne Element gegangen (iteriert) werden und das
Einfügen an der ersten und der letzten Stelle gesondert behandelt werden muss.
Erweiterungen Da sich die zuvor erwähnte Variante als zu umständlich (siehe Nachteile)
erwiesen hat, wird oft eine der hier nachstehenden Erweiterungen verwendet.
• Liste mit "Ende"-Zeiger: Führt einen Zeiger Ende ein, der auf das letzte Element der
Liste zeigt.
Vorteile:
• Es können Elemente zusätzlich sehr schnell an das Ende der Liste angehängt werden
Nachteile:
• Etwas mehr Speicherbedarf
Doppelt (mehrfach) verkettete Liste

Verkettete Liste
Hier hat jedes Element der Liste nicht nur einen Zeiger auf das nachfolgende, sondern auch
einen zusätzlichen zweiten auf das Vorgänger-Element.
Der Vorgänger-Zeiger des ersten Elementes zeigt auf ein Dummy-Element, wie auch der
Nachfolger-Zeiger des letzten Elementes. Dieses besondere Element dient zum Auffinden
des Anfangs und des Endes der doppelt verketteten Liste.
Vorteile
• Über die Liste kann von hinten nach vorne iteriert werden.
• Die Elemente in der zweiten Hälfte einer sortierten Liste sind schneller aufzufinden.
Nachteile
• Es wird etwas mehr Speicher für die zusätzlichen Zeiger benötigt.

Skip Liste

Bei den SkipListen unterscheidet man drei Arten von Typen:


1. ausgeglichene SkipList
2. unausgeglichene SkipList (siehe Bild)
3. randomisierte SkipList
Bei allen Typen ist das mehrfache Auftreten des Inhaltes erlaubt. Allerdings sind die aus- und
die unausgeglichene SkipList geordnet. Wohin gegen die randomisierte SkipList nicht
notwendigerweise geordnet sein muss. Durch das Einfügen von Zwischenstationen, welches
den Aufwand der Implementierung erhöht, kann die mittlere Zugriffszeit (und damit
verbunden die Komplexität) gesenkt werden. Eine Erweiterung des SkipList-Prinzip ist die
Verknüpfung mit dem Prinzip der doppelt verknüpften Liste, welche die Möglichkeit eines
„Rücksprunges“ ermöglicht. Bei einer ausgeglichenen SkipList senkt dies allerdings nicht die
Komplexität, wohingegen bei einer unausgeglichenen SkipList auf Zwischenstationen
verzichtet werden kann, welches dann wiederum den Zugriff auf Elemente in der Nähe der
nächsten Zwischenstation erhöht.

Adaptive Listen
Da der Aufwand ein Element einer einfach verknüpften Liste mit der Entfernung vom Start
um n Schritte zunimmt, kam man auf das Prinzip der adaptiven Listen. Dieses Prinzip geht
von der Voraussetzung aus und sortiert die Listenelemente nach ihrer Zugriffshäufigkeit
(Vergangenheitsortiert). Dabei gibt es drei grundsätzliche Strategien:
• MoveToFront: Bei jedem Zugriff auf ein Element wird dieses entfernt und am Anfang
der Liste eingefügt.
• Transpose: Bei jedem Zugriff auf ein Element wird es mit seinem Vorgänger
vertauscht (Sonderfall: 1. Element)
• Gratification: Zu jedem Element werden dessen Zugriffshäufigkeit gespeichert. In
einem bestimmten Intervall wird anhand der Zugriffshäufigkeit die Liste neu sortiert.

Listen in der objektorientierten Programmierung


In der objektorientierten Programmierung zeichnen sich Listen gemäß dem Prinzip der
Kapselung durch eine Menge von Listenoperationen aus. Intern können dabei
unterschiedliche und durchaus auch kompliziertere Datenstrukturen, wie binäre Bäume zum
Einsatz kommen. Aufgrund der internen Datenstruktur können dabei oft auch weitere
Funktionen, wie z.B. Sortierung, sortiertes Einfügen, Entfernen des größten Elementes, etc.
angeboten werden.
Je nach Einsatzzweck kann es sinnvoll sein, zwischen konkreten Implementierungen der
Schnittstelle Liste zu wählen. Wird beispielsweise hauptsächlich wahlfrei über Index auf die
List zugegriffen, wäre eine verkettete Liste eine schlechte Wahl, da dort n Operation nötig
sind, um das nte Element zu adressieren.
Daher werden in objektorientierten Bibliotheken oft neben der Schnittstelle verschiedene
konkrete Implementierungen angeboten. Beispielsweise gibt es in der Programmiersprache
Java als Schnittstelle java.util.List und als konkrete Implementierungen java.util.LinkedList
und java.util.ArrayList.

Beispiele
Nachfolgend Beispiele in Pseudocode (an C angelehnt). Dabei gibt es in der Liste das Feld
Key (also den Schlüssel, den man sucht) und das Feld Data (die Nutzdaten, die hinter dem
Schlüssel stecken). Das Beispiel:
• Gib mir die Lottozahlen vom 1. Januar 2006. Dabei ist 1. Januar 2006 der Schlüssel
und die Lottozahlen sind die Nutzdaten.
• pNext in einem Knoten (Element) ist der Zeiger auf das nächste Element in der Liste

Neues Element in Liste einfügen


FUNKTION insertElement( Datum, Lottozahlen )
{
Zeiger *NeuesElement = Speicherallozierung(size(Datum) + size(Lottozahlen));

WENN (Speicherallozierung erfolgreich)


{
Kopieren von Datum und Lottozahlen nach *NeuesElement;
NeuesElement->pNext = NULL;
Zeiger *LetztesElement = FindeLetztesElement();

WENN (Letztes Element gefunden)


{
LetztesElement->pNext = NeuesElement;
return GELUNGEN;
}
SONST
{
return FEHLER;
}

}
SONST
{
return FEHLER;
}
}

Element suchen

FUNKTION sucheElement( Datum )


{

Variable lz = NULL;
Zeiger *AktuellesElement = ErstesElement();

WENN (Suche erfolgreich)


{
SOLANGE (lz == NULL UND AktuellesElement != NULL)
{
WENN (AktuellesElement->Datum == Datum)
lz = AktuellesElement->Lottozahlen;
SONST
AktuellesElement = AktuellesElement->pNext;
}

WENN (lz != NULL)


return lz;
SONST
return FEHLER;
}
SONST
{
return FEHLER;
}
}

Element aus Liste löschen


FUNKTION deleteElement( Datum )
{
Zeiger *aktuellesElement;
Zeiger *nächstesElement = ErstesElement();

SOLANGE ( nächstesElement != null )


{
WENN (nächstesElement->Datum == Datum) // zu löschendes Element gefunden
{
WENN ( aktuellesElement != NULL ) // wir befinden uns nicht am Listenbeginn
{
WENN ( nächstesElement->pNext != NULL) // wir befinden uns nicht am
Listenende
{
aktuellesElement->pNext = nächstesElement->pNext;
Lösche nächstesElement;
}
SONST // wir befinden uns am Listenende
{
aktuellesElement->pNext = NULL;
Lösche nächstesElement;
}
}
SONST // wir befinden uns am Listenbeginn
{
WENN ( nächstesElement->pNext != NULL) // wir befinden uns nicht am
Listenende
Setze Listenbeginn auf nächstesElement->pNext;
SONST wir befinden uns am Listenende - wir löschen das einzige Element, Liste ist
nun leer
Setze Listenbeginn auf NULL;

Lösche nächstesElement;
}
}
AktuellesElement = nächstesElement;
nächstesElement = nächstesElement->pNext,
}
}

Suchbäume

Ein Baum ist in der Graphentheorie ein spezieller Graph, mit dem sich eine Monohierarchie
modellieren lässt. Je nachdem, ob die Kanten des Baums eine ausgezeichnete Richtung
besitzen, lassen sich graphentheoretische Bäume unterteilen in ungerichtete Bäume und
gewurzelte Bäume, und für gewurzelte Bäume in Out-Trees, bei denen die Kanten von der
Wurzel ausgehen, und In-Trees, bei denen Kanten in Richtung Wurzel zeigen.
In der Informatik werden Bäume häufig als Datenstruktur eingesetzt. In diesem Fall werden
sie aber anders repräsentiert als allgemeine Graphen. Durch Entfernen einer Kante zerfällt ein
Baum in zwei Teilbäume und bildet damit einen so genannten Wald.

Bäume als Datenstruktur


Gewurzelte Bäume, insbesondere Out-Trees, werden häufig als Datenstruktur verwendet. Bei
beschränkter Ordnung können diese so implementiert werden, dass jeder Knoten einen festen
Satz an Variablen oder ein Array für die Referenzen auf seine Kinder enthält. Häufig besitzen
die Knoten auch eine Referenz auf ihren Elternknoten (back pointer). Ein Baum
unbeschränkter Ordnung kann implementiert werden, indem man statt Arrays dynamische
Listen verwendet. In Programmiersprachen ohne dynamische Listen hat sich auch ein
Verfahren bewährt, bei dem ein allgemeiner Baum durch einen Binärbaum implementiert
wird:
Die rötliche Linie zeigt dabei den realisierten allgemeinen Baum, während die Pfeile die
tatsächlichen Zeigerstrukturen repräsentieren. Das Grundprinzip besteht darin, dass ein Zeiger
jeweils auf den am weitesten links stehenden Sohn zeigt, während der andere auf den rechten
Bruder verweist. Hierbei ist zwar ein direkter Zugriff auf einzelne bestimmte Sohn-Knoten
nicht mehr möglich, da man sich über die Geschwister voranarbeiten muss. Dafür ist diese
Implementierung sehr speichereffizient.

Binärbaum

Ein voller, aber nicht vollständiger Binärbaum

Als Binärbaum bezeichnet man in der Graphentheorie eine spezielle Form eines Graphen.
Genauer gesagt handelt es sich um einen gewurzelten Baum, bei dem jeder Knoten höchstens
zwei Kindknoten besitzt. Oft wird verlangt, dass sich die Kindknoten eindeutig in linkes und
rechtes Kind einteilen lassen.

Ein Binärbaum heißt geordnet, wenn jeder innere Knoten ein linkes und eventuell zusätzlich
ein rechtes Kind besitzt (und nicht etwa nur ein rechtes Kind). Man bezeichnet ihn als voll,
wenn jeder Knoten entweder Blatt ist (also kein Kind besitzt), oder aber zwei (also sowohl ein
linkes wie ein rechtes) Kinder besitzt. Man bezeichnet ihn als vollständig, wenn alle Blätter
die gleiche Tiefe besitzen. Induktiv lässt sich zeigen, dass ein vollständiger Binärbaum der
Höhe n, den man häufig auch als Bn bezeichnet, genau
• 2n+1-1 Knoten,
• 2i Knoten in Tiefe i, insbesondere also
• 2n Blätter
besitzt, wobei mit Höhe n die Länge des Pfades zu einem tiefsten Knoten bezeichnet wird.
Eine Darstellung eines Binärbaumes, in dem die Knoten mit rechtwinkligen Dreiecken und
die Kanten mit Rechtecken dargestellt werden, nennt man pythagoräischen Binärbaum.
Anwendungen
Viele Datenstrukturen wie beispielsweise binäre Suchbäume, AVL-Bäume, Fibonacci-Bäume
und binäre Heaps basieren auf Binärbäumen.
Eine verbale Definition: Ein Baum ist entweder leer, oder er besteht aus einem linken oder
rechten Teilbaum, die wiederum Bäume sind!

Linearisierung
Es gibt verschiedene Möglichkeiten, die Knoten von Binärbäumen zu durchlaufen. Diesen
Prozess bezeichnet man auch als Linearisierung. Man unterscheidet hier in
• pre-order (W - L - R): wobei zuerst die Wurzel (W) betrachtet wird und anschließend
zuerst der linke (L), dann der rechte (R) Teilbaum durchlaufen wird,
• in-order (L - W - R): wobei zuerst der linke (L) Teilbaum durchlaufen wird, dann die
Wurzel (W) betrachtet wird und anschließend der rechte (R) Teilbaum durchlaufen
wird und
• post-order (L - R - W): wobei zuerst der linke (L), dann der rechte (R) Teilbaum
durchlaufen wird und anschließend die Wurzel (W) betrachtet wird.

Rekursive Implementierungen

Funktion Preorder(Baum)
W <- Baum.Wurzel //W:= Wurzel des übergebenen Baumes
If Baum.Links <> NIL //Existiert ein linker Unterbaum?
L <- Preorder(Baum.Links) // dann: L:= Präorder von linkem Unterbaum
If Baum.Rechts <> NIL //Existiert ein rechter Unterbaum?
R <- Preorder(Baum.Rechts) // dann: R:= Präorder von rechtem Unterbaum
Return W°L°R //Rückgabe: Verkettung aus W, L und R

Funktion Inorder(Baum)
W <- Baum.Wurzel //W:= Wurzel des übergebenen Baumes
If Baum.Links <> NIL //Existiert ein linker Unterbaum?
L <- Inorder(Baum.Links) // dann: L:= Inorder von linkem Unterbaum
If Baum.Rechts <> NIL //Existiert ein rechter Unterbaum?
R <- Inorder(Baum.Rechts) // dann: R:= Inorder von rechtem Unterbaum
Return L°W°R //Rückgabe: Verkettung aus L, W und R

Funktion Postorder(Baum)
W <- Baum.Wurzel //W:= Wurzel des übergebenen Baumes
If Baum.Links <> NIL //Existiert ein linker Unterbaum?
L <- Postorder(Baum.Links) // dann: L:= Postorder von linkem Unterbaum
If Baum.Rechts <> NIL //Existiert ein rechter Unterbaum?
R <- Postorder(Baum.Rechts) // dann: R:= Postorder von rechtem Unterbaum
Return L°R°W //Rückgabe: Verkettung aus L, R und W

Wikipedia