Liste (Datenstruktur)
Eine verkettete Liste ist eine dynamische Datenstruktur, in der Datenelemente geordnet gespeichert sind. Bei ihrer Erstellung braucht die maximale Anzahl der Elemente nicht festgelegt zu werden, und die Anzahl darf während der Laufzeit beliebig variieren.
Einfach verkettete Listen
Der Datentyp Fehler beim Parsen (Konvertierungsfehler. Der Server („https://wikimedia.org/api/rest_“) hat berichtet: „Cannot get mml. Server problem.“): {\displaystyle L_{A}} der einfach verketteten Listen mit Elementen vom Typ Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle A} ist rekursiv definiert als . Die technische Realisierung erfolgt meistens durch einzelne Knoten, die aus den Nettodaten selbst und einem Verweis auf den Nachfolgeknoten bestehen. Im letzten Knoten ist als Nachfolgeknoten der sogenannte Null-Zeiger angegeben, der auch Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \operatorname{\mathit{Nil}}} heißt.[1]
Elementare Listenoperationen sind das Erweitern der Liste um einen Knoten am Anfang und das Entfernen des ersten Knotens, die in der Zeit Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \mathcal{O}(1)} erfolgen können.
Vorteile
- Wenn das Suchen erledigt und der Einfügepunkt gefunden ist, ist der Aufwand fürs Einfügen an jeder Stelle Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \mathcal{O}(1)} . In einem Array Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \mathcal{O}(n)} müssten hingegen Datensätze umkopiert werden.
- Geringer zusätzlicher Speicherbedarf (1 Zeiger).
Nachteile
- Die Kosten fürs Suchen sind Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \mathcal{O}(n)} , da ungünstigstenfalls über jeden Knoten iteriert werden muss.
Anwendungen
Einfach verkettete Listen werden in hochdynamischen Umgebungen verwendet, in denen mit Arrays nicht mehr sinnvoll gearbeitet werden kann, da diese den Umgang mit syntaktischen Operationen enorm erschweren. So ist die einfach verkettete Liste mit Datentyp Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle L_U = \operatorname{\mathit{Nil}} \mid [\, U, L_U \,]} mit Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle U = L_U \mid E} wobei Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle E} weitere elementare LISP-Datentypen bezeichnet, der zentrale Datentyp der Programmiersprache LISP. Sogar LISP-Programme sind selbst solche Listen.
Doppelt verkettete Liste
Im Gegensatz zur einfach-verketteten Liste hat jedes Element sowohl einen Zeiger auf das nachfolgende als auch auf das vorhergehende Element.
Der Vorgänger-Zeiger des ersten und der Nachfolger-Zeiger des letzten Elementes zeigen auf den Wert NULL. Dieses besondere Element dient zum Feststellen des Anfangs und des Endes einer doppelt verketteten Liste.[1]
Vorteile
- Das Entfernen eines Elements aus der Liste kann in Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \mathcal{O}(1)} geschehen, auch wenn die Ankunft beim Element über keine der zwei Verkettungen geschah. In diesem Fall müsste bei der einfach verketteten Liste der Vorgänger gesucht werden.
- Über die Liste kann von hinten nach vorne iteriert werden.
Nachteile
- Höherer Speicherbedarf für die zusätzlichen Zeiger.
- Beim Löschen und Einfügen müssen auch die Vorgänger-Zeiger der nachfolgenden Listenelemente angepasst werden.
Weitere Formen
Listenkopf
Der Listenkopf (oder Listenanker) ist ein Datenfeld, welches zusätzliche Informationen, wie beispielsweise die Anzahl der Knoten in der Liste, enthalten kann. Er zeigt auf das erste Element.
Skip-Liste
Wie bei verketteten Listen werden auch bei der Skipliste die Daten in Containern abgelegt. Diese enthalten einen Schlüssel und einen Zeiger auf den nächsten Container. Allerdings können Container in Skiplisten auch Zeiger auf andere Container enthalten, welche nicht direkt nachfolgen. Es können also Schlüssel übersprungen werden. Jeder Container hat eine bestimmte Höhe Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\textstyle h} , welche um Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\textstyle 1} kleiner ist als die Anzahl der Zeiger, die ein Container enthält. Die Zeiger werden von bis Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\textstyle h} nummeriert. Grundsätzlich imitiert eine Skipliste also die Binäre Suche auf einem Feld.
Skip-Liste mit mehreren Sprüngen
Bei den Skip-Listen unterscheidet man drei Arten von Typen:
- ausgeglichene SkipList
- unausgeglichene SkipList (siehe Bild)
- randomisierte SkipList
Bei allen Typen ist das mehrfache Auftreten des Inhaltes erlaubt. Allerdings sind die aus- und die unausgeglichene SkipList geordnet, wohingegen 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, wodurch „Rücksprünge“ ermöglicht werden. 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.
Die Operationen Einfügen, Suchen und Löschen haben jeweils eine erwartete Laufzeit von Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\displaystyle \mathcal{O}(\log n)} .
Berechnung der Containerhöhe
Bei der randomisierten Skipliste erfolgt die Berechnung der Höhe nach dem Zufallsprinzip. Die Wahrscheinlichkeit, dass eine bestimmte Höhe erreicht wird, kann folgendermaßen ermittelt werden: Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\textstyle \frac{1}{2 \cdot 2^h}}
Bei nicht randomisierten Skiplisten wird die Höhe so bestimmt, dass jeder Zeiger mit Zeigerhöhe Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\textstyle z} auf einen Container Fehler beim Parsen (MathML mit SVG- oder PNG-Rückgriff (empfohlen für moderne Browser und Barrierefreiheitswerkzeuge): Ungültige Antwort („Math extension cannot connect to Restbase.“) von Server „https://wikimedia.org/api/rest_v1/“:): {\textstyle 2^z} Positionen weiter hinten in der Liste zeigen kann – also alle Container dazwischen eine geringere Höhe haben als der Zeiger.
Adaptive Listen
Da der Aufwand des Zugriffes auf ein Element einer einfach verknüpften Liste mit der Entfernung vom Start pro Einzelschritt zunimmt, kam man auf das Prinzip der adaptiven Listen. Im Versuch, diesen Aufwand im Mittel möglichst niedrig zu halten, werden die Listenelemente nach ihrer Zugriffshäufigkeit sortiert. 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: erstes Element)
- Gratification: Zu jedem Element wird dessen Zugriffshäufigkeit gespeichert. In einem bestimmten Intervall wird anhand der Zugriffshäufigkeit die Liste neu sortiert.
Abstrakter Datentyp
Die Daten werden in einer Sequenz von Schlüsseln in einer Liste gespeichert, in der eine Struktur besteht, die aus Zähler, Zeiger und der Adresse zu einer Vergleichsfunktion besteht. Der Datenknoten enthält den Zeiger auf eine Datenstruktur und einen selbstreferenzierenden Zeiger, der auf den nächsten Knoten in der Liste zeigt.
In C++ kann eine Liste wie folgt als abstrakter Datentyp definiert werden: [2]
struct Node {
void *dataPointer;
Node *link;
};
struct List {
int count;
Node *head;
virtual int compare(List &other) = 0;
};
Listen in der objektorientierten Programmierung
In der objektorientierten Programmierung zeichnen sich Listen gemäß dem Prinzip der Datenkapselung 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 beispielsweise 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 Liste zugegriffen, wäre eine verkettete Liste eine schlechte Wahl, da dort n Operationen nötig sind, um das n-te 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,[3] und es werden unter anderem java.util.LinkedList[4] und java.util.ArrayList[5] als konkrete Implementierungen angeboten. In C++ werden Listen und Vektoren in der Standardbibliothek implementiert.
Beispiele
Nachfolgende Beispiele sind in C# verfasst. Dafür wird ein Knoten definiert, welcher eine Zahl und eine Nachfolgeknoten speichern kann.
class Knoten {
public int element = 0;
public Knoten folgeknoten = null;
}
Neues Element in Liste einfügen
static void elementEinfuegen(Knoten knoten, int neuesElement) {
while (knoten.folgeknoten != null)
knoten = knoten.folgeknoten;
knoten.folgeknoten = new Knoten();
knoten.folgeknoten.element = neuesElement;
}
Element suchen
static bool elementSuchen(Knoten knoten, int altesElement) {
while (knoten != null) {
if (altesElement == knoten.element)
return true;
knoten = knoten.folgeknoten;
}
return false;
}
Element aus Liste löschen
static void elementLoeschen(ref Knoten knoten, int altesElement) {
while (knoten != null && altesElement != knoten.element)
knoten = knoten.folgeknoten;
Knoten aktuell = knoten;
while (aktuell != null) {
if (aktuell.folgeknoten != null && altesElement == aktuell.folgeknoten.element)
aktuell.folgeknoten = aktuell.folgeknoten.folgeknoten;
else
aktuell = aktuell.folgeknoten;
}
}
Verwendung von Liste in objektorientierter Sprache
Dieses Beispiel zeigt die Verwendungen einer Liste in C++.
#include <algorithm>
#include <iostream>
#include <list>
using namespace std;
int main() {
// Initialisierung
auto liste = list<int>();
// am Anfang einfügen
liste.push_front(4);
liste.push_front(3);
// am Ende anfügen
liste.push_back(5);
liste.push_back(6);
// die Liste enthält 3 4 5 6
// Durchlaufen der Liste
for (int element: liste)
cout << element << " ";
// Entfernen aller Zahlen größer als 4
liste.erase(remove_if(liste.begin(), liste.end(), [](int x) { return x > 4; }), liste.end());
cout << endl;
for (int element: liste)
cout << element << " ";
// Sortieren
liste.sort();
// Löschen
liste.clear();
}
Siehe auch
Weblinks
- Einführung in verkettete Listen (englisch)
- Tutorial zu verketteten Listen in C# (englisch)
Einzelnachweise und Anmerkungen
- ↑ a b Der Einsatz eines Wächterzeigers oder Sentinels anstelle des Null-Zeigers kann einen Vergleich in der Suchschleife sparen.
- ↑ GeeksforGeeks: Abstract Data Types
- ↑ java.util.List Java API Specification
- ↑ java.util.LinkedList Java API Specification
- ↑ java.util.ArrayList Java API Specification