Doppelt verkettete Liste

Verfolge Schritt für Schritt eine doppelt verkettete C++-Liste: Einfügen an beiden Enden, Löschen, Umkehren und die Rule of Five.

Alles wird in diesem Browser simuliert; nichts wird an einen Server gesendet.

Operationen

Der Konstruktor wird aufgerufen

Der Konstruktor wird aufgerufen

l1Nicht konstruiert
∅
  • Aktuell
  • Zeiger

Schritt 1 / 34

l1
l2
l3

Code

#include <iostream>
#include <utility>
class Nodo { // Ein Knoten enthält die Daten und Zeiger auf den vorherigen und den nächsten
private: // Private Attribute: nur die Methoden greifen darauf zu
int dato_;
Nodo* anterior_;
Nodo* siguiente_;
public:
Nodo(int dato, Nodo* anterior = nullptr, Nodo* siguiente = nullptr) {
dato_ = dato;
anterior_ = anterior;
siguiente_ = siguiente;
}
int getDato() const { return dato_; }
void setDato(int dato) { dato_ = dato; }
Nodo* getAnterior() const { return anterior_; }
void setAnterior(Nodo* anterior) { anterior_ = anterior; }
Nodo* getSiguiente() const { return siguiente_; }
void setSiguiente(Nodo* siguiente) { siguiente_ = siguiente; }
};
class Lista {
private:
Nodo* cabeza_; // Erster Knoten der Liste
Nodo* cola_; // Letzter Knoten der Liste
int n_; // Anzahl der Knoten
public:
Lista() { // Standardkonstruktor: erzeugt eine leere Liste
cabeza_ = nullptr;
cola_ = nullptr;
n_ = 0;
}
~Lista() { // Destruktor: gibt alle Knoten frei
while (cabeza_ != nullptr) {
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
}
Lista(const Lista& origin) { // Kopierkonstruktor: erstellt eine tiefe Kopie
cabeza_ = nullptr;
cola_ = nullptr;
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarAlFinal(aux->getDato()); // Das Einfügen am Ende erhält die ursprüngliche Reihenfolge
aux = aux->getSiguiente();
}
}
Lista& operator=(const Lista& origin) { // Kopierzuweisung: gibt die alten Knoten frei und kopiert dann
if (this != &origin) { // Schützt vor Selbstzuweisung
while (cabeza_ != nullptr) { // Gibt die Knoten frei, die die Liste bereits hat
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarAlFinal(aux->getDato());
aux = aux->getSiguiente();
}
}
return *this;
}
Lista(Lista&& origin) noexcept { // Verschiebekonstruktor: übernimmt die Knoten der Quelle
cabeza_ = origin.cabeza_;
cola_ = origin.cola_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Lässt die Quelle leer zurück
origin.cola_ = nullptr;
origin.n_ = 0;
}
Lista& operator=(Lista&& origin) noexcept { // Verschiebezuweisung: gibt die alten Knoten frei und übernimmt dann die neuen
if (this != &origin) { // Schützt vor Selbstzuweisung
while (cabeza_ != nullptr) { // Gibt die Knoten frei, die die Liste bereits hat
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
cabeza_ = origin.cabeza_;
cola_ = origin.cola_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Lässt die Quelle leer zurück
origin.cola_ = nullptr;
origin.n_ = 0;
}
return *this;
}
int getTamano() const { return n_; }
Nodo* getCabeza() const { return cabeza_; }
Nodo* getCola() const { return cola_; }
void insertarElemento(int valor) { // Am Anfang einfügen: O(1)
Nodo* nodo = new Nodo(valor);
n_ += 1;
nodo->setSiguiente(cabeza_);
if (cabeza_ != nullptr) {
cabeza_->setAnterior(nodo);
} else {
cola_ = nodo;
}
cabeza_ = nodo;
}
void insertarAlFinal(int valor) { // Am Ende einfügen: O(1) dank cola_
Nodo* nodo = new Nodo(valor);
n_ += 1;
nodo->setAnterior(cola_);
if (cola_ != nullptr) {
cola_->setSiguiente(nodo);
} else {
cabeza_ = nodo;
}
cola_ = nodo;
}
void imprimirLista() const { // Von Kopf bis Ende ausgeben
Nodo* actual = cabeza_;
while (actual != nullptr) {
std::cout << actual->getDato() << " ";
actual = actual->getSiguiente();
}
std::cout << std::endl;
}
void imprimirListaAtras() const { // Vom Ende bis zum Kopf ausgeben
Nodo* actual = cola_;
while (actual != nullptr) {
std::cout << actual->getDato() << " ";
actual = actual->getAnterior();
}
std::cout << std::endl;
}
void eliminarNodo(int indice) { // Löscht den Knoten am angegebenen Index
if (indice < 0 || indice >= n_) return; // Ungültiger Index: nichts passiert
Nodo* eliminar = cabeza_;
for (int i = 0; i < indice; i++) {
eliminar = eliminar->getSiguiente();
}
if (eliminar->getAnterior() != nullptr) {
eliminar->getAnterior()->setSiguiente(eliminar->getSiguiente());
} else {
cabeza_ = eliminar->getSiguiente();
}
if (eliminar->getSiguiente() != nullptr) {
eliminar->getSiguiente()->setAnterior(eliminar->getAnterior());
} else {
cola_ = eliminar->getAnterior();
}
delete eliminar;
n_--;
}
void invertirLista() { // Kehrt die Liste in place um: jeder Knoten tauscht anterior_ und siguiente_
Nodo* actual = cabeza_;
Nodo* temporal = nullptr;
while (actual != nullptr) {
temporal = actual->getAnterior();
actual->setAnterior(actual->getSiguiente());
actual->setSiguiente(temporal);
actual = actual->getAnterior(); // anterior_ ist jetzt das alte siguiente_
}
temporal = cabeza_; // Zum Schluss werden Kopf und Ende getauscht
cabeza_ = cola_;
cola_ = temporal;
}
};
int main() {
Lista l1;
l1.insertarElemento(10);
l1.insertarElemento(20);
l1.insertarAlFinal(5);
l1.insertarElemento(30);
Lista l2;
l2.insertarElemento(100);
l2 = l1; // Kopierzuweisung (ohne swap)
l2.imprimirLista();
l2.imprimirListaAtras();
Lista l3;
l3 = std::move(l1); // Verschiebezuweisung (ohne swap)
l3.invertirLista();
l3.imprimirLista();
return 0;
}

Dieser Visualisierer für doppelt verkettete Listen zeigt, wie jeder C++-Knoten einen Zeiger auf den Vorgänger und einen auf den Nachfolger speichert und wie die Liste cabeza_, cola_ und n_ pflegt. Drei Listen, l1, l2 und l3, teilen sich die Zeichenfläche, damit du am Kopf oder am Ende einfügen, nach Index löschen, die Liste umkehren und vorwärts wie rückwärts ausgeben kannst. Probiere danach die fünf speziellen Member der Rule of Five: Destruktor, Kopierkonstruktor, Kopierzuweisung, Verschiebekonstruktor und Verschiebezuweisung. Jede Operation läuft Schritt für Schritt mit hervorgehobener C++-Zeile, simulierten Adressen und dem losgelösten Knoten, während beide Zeiger neu verbunden werden. Alles wird in deinem Browser simuliert.

So funktioniert es

  1. Wähle ein Beispiel aus der Liste oder beginne mit dem ersten, das eine Liste aufbaut und wieder zerstört.
  2. Füge einer der drei Listen Operationen hinzu: am Kopf oder am Ende einfügen, nach Index löschen, umkehren oder in beide Richtungen ausgeben.
  3. Wähle neben jeder Zeile die Quellliste für Kopien und Verschiebungen und gib bei Bedarf einen Wert oder Index ein.
  4. Drücke Wiedergabe oder gehe Schritt für Schritt vor und folge der hervorgehobenen C++-Zeile, den beiden Zeigern jedes Knotens und ihren Adressen.

Häufig gestellte Fragen

Was ist eine doppelt verkettete Liste?
Eine Liste, in der jeder Knoten einen Zeiger auf den vorherigen und einen auf den nächsten Knoten speichert. So lässt sie sich in beide Richtungen durchlaufen, und mit einem Zeiger auf das Ende kann man in O(1) am Ende einfügen.
Was bringt es, cola_ zu speichern?
Mit cola_ fügt man am Ende ein, ohne die Liste zu durchlaufen, und kann rückwärts ausgeben, indem man beim letzten Knoten beginnt.
Was ist der Unterschied zwischen Kopieren und Verschieben einer Liste?
Eine Kopie erzeugt neue Knoten mit neuen Adressen und lässt die Quelle unverändert. Ein Verschieben übernimmt die Knoten der Quelle, behält ihre Adressen und lässt die Quelle leer zurück, mit cabeza_ und cola_ auf nullptr.
Wird der C++-Code wirklich kompiliert?
Nein. Die Seite simuliert jede Operation in deinem Browser und hebt die passende Codezeile hervor, es wird also nichts an einen Server gesendet.

Verwandte Tools