Doppelt verkettete Liste: C++, Python (Code Beispiel)
Was ist eine doppelt verkettete Liste?
In einer doppelt verknรผpften Liste verfรผgt jeder Knoten รผber Links sowohl zum vorherigen als auch zum nรคchsten Knoten. Jeder Knoten besteht aus drei Elementen: eines enthรคlt die Daten und zwei weitere sind die Zeiger des nรคchsten und des vorherigen Knotens. Diese beiden Zeiger helfen uns, von einem bestimmten Knoten aus vorwรคrts oder rรผckwรคrts zu gehen.
Hier ist die Grundstruktur der doppelt verknรผpften Liste.

Jede verknรผpfte Liste hat einen Kopf- und einen Endknoten. Der Kopfknoten hat keinen โvorherigenโ Knoten (vorheriger Zeiger) und der Endknoten hat keinen โnรคchstenโ Knoten.
Hier sind einige wichtige Begriffe fรผr eine doppelt verkettete Liste:
- Zurรผck: Jeder Knoten ist mit seinem vorherigen Knoten verknรผpft. Es wird als Zeiger oder Link verwendet.
- Nรคchster: Jeder Knoten ist mit seinem nรคchsten Knoten verknรผpft. Es wird als Zeiger oder Link verwendet.
- Datum: Dies wird verwendet, um Daten in einem Knoten zu speichern. โDatenโ kรถnnen andere enthalten Datenstrukturen im Inneren. Beispielsweise kรถnnen Zeichenfolgen, Wรถrterbรผcher, Sรคtze, Hashmaps usw. in den โDatenโ gespeichert werden.
Hier ist die Grundstruktur eines einzelnen Knotens in der doppelt verknรผpften Liste:
Operationen der doppelt verknรผpften Liste
Zu den Operationen einer doppelt verketteten Liste gehรถren das Hinzufรผgen und Lรถschen von Knoten, das Einfรผgen und Entfernen von Knoten sowie das Durchlaufen der verketteten Liste von oben nach unten.
Hier ist die Liste der Operationen, die wir mit der doppelt verketteten Liste implementieren kรถnnen:
- Einschub vorne
- Einfรผgung in den Schwanz oder letzten Knoten
- Einfรผgung nach einem Knoten
- Einfรผgung vor einem Knoten
- Lรถschung von vorne
- Lรถschung vom Schwanz
- Suchen und lรถschen Sie einen Knoten
- Von Kopf bis Schwanz queren
- Schwanz zum Kopf kreuzen
Wir werden uns die Implementierung und den Pseudocode fรผr alle oben genannten Operationen ansehen.
Einfรผgung vor der doppelt verknรผpften Liste
Das Einfรผgen vorn bedeutet, dass wir einen Knoten in der verknรผpften Liste erstellen und ihn am Anfang der verknรผpften Liste platzieren mรผssen.
Beispielsweise gibt es einen gegebenen Knoten โ15โ. Wir mรผssen dies zum Hauptknoten hinzufรผgen.
Fรผr diesen Vorgang sind zwei wichtige Bedingungen zu beachten:
- Der neue Knoten ist der Hauptknoten, wenn in der doppelt verknรผpften Liste kein Knoten vorhanden ist.
- Wenn bereits ein Kopfknoten vorhanden ist, wird der vorherige Kopfknoten durch den neuen Knoten ersetzt.
Hier ist der Pseudocode fรผr diese Operation:
function insertAtFront (ListHead, value): newNode = Node() newNode.value = value ListHead.prev = NewNode NewNode.next = ListHead newNode.prev = NULL return ListHead

Einfรผgung am Ende der doppelt verknรผpften Liste
โEinfรผgung am Endeโ bedeutet, dass wir einen Knoten in der verknรผpften Liste erstellen und ihn am Ende platzieren.
Um dies durchzufรผhren, kรถnnen wir zwei Methoden verwenden:
- Methode 1: Beginnen Sie mit dem Durchlaufen vom Kopf der doppelt verknรผpften Liste, bis das โnรคchsteโ null wird. Verknรผpfen Sie dann den neuen Knoten mit dem โnรคchstenโ.
- Methode 2: Nehmen Sie den letzten Knoten der doppelt verknรผpften Liste. Dann wird der โnรคchsteโ Knoten des letzten Knotens mit dem neuen Knoten verknรผpft. Jetzt wird der neue Knoten der Endknoten sein.
Hier ist der Pseudocode zum Einfรผgen am Endknoten:
function insertAtTail(ListHead, value): newNode = Node() newNode.value = value newNode.next = NULL while ListHead.next is not NULL: then ListHead = ListHead.next newNode.prev = ListHead ListHead.next = newNode return ListHead
Einfรผgung nach einem Knoten
Nehmen wir an, wir haben eine vorhandene doppelt verkettete Liste wie die folgende:
Wir mรถchten einen bestimmten Knoten einfรผgen, der nach dem Knoten mit dem Wert โ12โ verknรผpft werden soll.
Hier sind die Schritte dafรผr:
Schritt 1) Vom Kopf zum letzten Knoten durchlaufen. รberprรผfen Sie, welcher Knoten den Wert โ12โ hat.
Schritt 2) Erstellen Sie einen neuen Knoten und weisen Sie ihn als nรคchsten Zeiger des Knotens โ12โ zu. Der โnรคchsteโ Knoten des neuen Knotens wird 15 sein.
Hier ist der Pseudocode zum Einfรผgen eines Knotens nach einem Knoten in einer doppelt verknรผpften Liste:
function insertAfter(ListHead, searchItem, value): List = ListHead NewNode = Node() NewNode.value = value while List.value is not equal searchItem then List = ListHead.next List = List.next NewNode.next = List.next NewNode.prev = List List.next = NewNode
Einfรผgung vor einem Knoten
Dieser Vorgang รคhnelt eher dem โEinfรผgen nach einem Knotenโ. Dazu mรผssen wir nach dem Wert eines bestimmten Knotens suchen. Dann erstellen wir einen neuen Knoten und fรผgen ihn vor dem gesuchten Knoten ein.
Nehmen wir an, wir mรถchten einen bestimmten Knoten โ15โ vor dem Knoten โ12โ einfรผgen. Dann sind hier die Schritte dazu:
Schritt 1) Durchlaufen Sie die verknรผpfte Liste vom Kopfknoten zum Endknoten.
Schritt 2) รberprรผfen Sie, ob der nรคchste Zeiger des aktuellen Knotens den Wert โ12โ hat oder nicht.
Schritt 3) Fรผgen Sie den neuen Knoten als โnรคchstenโ Knoten des aktuellen Knotens ein.
Hier ist der Pseudocode zum Einfรผgen eines Knotens vor einem Knoten in einer doppelt verknรผpften Liste:
function insertBefore(ListHead, searchItem, value): List = ListHead NewNode = Node() NewNode.value = value while List.next.value is not equal searchItem then List = ListHead.next NewNode.next = List.next NewNode.prev = List List.next = NewNode
Lรถschen Sie den Kopf der doppelt verknรผpften Liste
Der Hauptknoten in der doppelt verknรผpften Liste, der keinen vorherigen Knoten hat. Der nรคchste Zeiger ist also der neue Kopfknoten, wenn wir den Kopfknoten lรถschen mรถchten. Wir mรผssen auch den von einem Knoten belegten Speicher freigeben, wรคhrend wir einen Knoten lรถschen.
Hier sind die Schritte zum Lรถschen des Hauptknotens:
Schritt 1) Weisen Sie dem aktuellen Hauptknoten eine Variable zu.
Schritt 2) Besuchen Sie den โnรคchstenโ Knoten des aktuellen Hauptknotens und machen Sie den โvorherigenโ Knoten (vorheriger Zeiger) zu โNULLโ. Das bedeutet, dass wir den zweiten Knoten vom ersten Knoten trennen.
Schritt 3) Geben Sie den vom vorherigen Hauptknoten belegten Speicher frei.
Hier ist der Pseudocode zum Lรถschen des Kopfes aus einer doppelt verknรผpften Liste:
function deleteHead(ListHead): PrevHead = ListHead ListHead = ListHead.next ListHead.prev = NULL PrevHead.next = NULL free memory(PrevHead) return ListHead
Nach jedem Lรถschvorgang muss der zugewiesene Speicher gelรถscht werden. Andernfalls bleibt der Speicher fรผr den gelรถschten Block wรคhrend der gesamten Laufzeit des Programms belegt. Keine andere Anwendung kann diesen Speicherabschnitt verwenden.
Lรถschen Sie das Ende der doppelt verknรผpften Liste.
Dieser Vorgang ist รคhnlich wie das Lรถschen des Kopfes. Hier mรผssen wir anstelle des Kopfes das Ende lรถschen. Um einen Knoten als Endeknoten zu identifizieren, sollten wir prรผfen, ob der nรคchste Zeiger oder der nรคchste Knoten null ist oder nicht. Nachdem wir das Ende gelรถscht haben, mรผssen wir den Speicher freigeben.
Dieser Vorgang wird auch als โLรถschung von der Rรผckseiteโ bezeichnet.
Hier sind die Schritte dazu:
Schritt 1) Durchlaufen Sie bis zum Endknoten der doppelt verknรผpften Liste.
Schritt 2) Weisen Sie dem Endknoten eine Variable oder einen Zeiger zu.
Schritt 3) Machen Sie den โnรคchstenโ Knoten zu NULL und geben Sie den Speicher des Endknotens frei.
Hier ist der Pseudocode zum Lรถschen des Endknotens:
function DeleteTail( ListHead ): head = ListHead while ListHead.next is not NULL: ListHead = ListHead.next Tail = ListHead.next ListHead.next = NULL free memory( Tail ) return head
Suchen und lรถschen Sie einen Knoten aus der doppelt verknรผpften Liste
Mit diesem Vorgang kรถnnen wir nach bestimmten Knotendaten suchen und diesen Knoten lรถschen. Wir mรผssen eine lineare Suche durchfรผhren, da die verknรผpfte Liste eine lineare Datenstruktur ist. Nach dem Lรถschen mรผssen wir auch den Speicher freigeben.
Hier sind die Schritte zum Suchen und Lรถschen eines Knotens in der doppelt verknรผpften Liste:
Schritt 1) Durchlaufen Sie die verknรผpfte Liste vom Kopf bis der Wert des Knotens dem Suchelement entspricht.
Schritt 2) Weisen Sie dem รผbereinstimmenden Knoten eine Variable โdeleteNodeโ zu.
Schritt 3) Weisen Sie den vorherigen Knoten des โdeleteNodeโ dem nรคchsten Knoten zu.
Schritt 4) Den Speicher des โdeleteNodeโ freigeben
Hier ist der Pseudocode zum Suchen und Lรถschen eines Knotens aus einer verknรผpften Liste:
function SearchAndDelete(ListHead, searchItem): head = ListHead while ListHead.next.value not equals searchItem: head = head.next deleteNode = head.next head.next = head.next.next head.next.prev = head deleteNode.next, deleteNode.next = NULL free memory(deleteNode) return ListHead
Durchlaufen Sie eine doppelt verknรผpfte Liste von vorne
Vom Kopfknoten aus durchlaufen und รผber den nรคchsten Knoten iterieren, bis wir โNULLโ finden. Wรคhrend wir jeden Knoten durchlaufen, kรถnnen wir den Wert des Knotens drucken. Hier sind die Schritte zum Queren von vorne (Vorwรคrtsrichtung):
Schritt 1) Weisen Sie dem aktuellen Hauptknoten einen Zeiger oder eine Variable zu.
Schritt 2) Iterieren Sie zum nรคchsten Knoten des Kopfes, bis Sie โNULLโ erhalten.
Schritt 3) Drucken Sie die Daten des Knotens in jeder Iteration.
Schritt 4) Geben Sie den Hauptknoten zurรผck.
Hier ist der Pseudocode zum Durchlaufen einer doppelt verknรผpften Liste von vorne:
function traverseFromFront(ListHead): head = ListHead while head not equals to NULL: print head.data head = head.next return ListHead
Hier ist die Rรผckgabe nicht zwingend erforderlich. Es empfiehlt sich jedoch, den Kopfknoten nach den Vorgรคngen zurรผckzugeben.
Durchlaufen Sie eine doppelt verknรผpfte Liste von hinten
Dieser Vorgang ist das Gegenteil der Traverse von vorne. Der Ansatz ist der gleiche, mit einem kleinen Unterschied. Wir mรผssen zum Endknoten und dann vom Endknoten zum vorderen Knoten traversieren.
Hier sind die Schritte zum Durchlaufen einer doppelt verknรผpften Liste von hinten:
Schritt 1) Durchqueren Sie, bis wir den Endknoten erreichen.
Schritt 2) Vom Endknoten aus werden wir durchlaufen, bis wir den vorherigen Knoten als โNULLโ erhalten. Der โprevโ (vorheriger Zeiger) ist fรผr den Kopfknoten null
Schritt 3) Bei jeder Iteration drucken wir die Knotendaten.
Hier ist der Pseudocode fรผr die Durchquerung von hinten:
function traverseFromBack(ListHead): head = ListHead while head not equals NULL: head = head.next tail = head while tail not equal to NULL: print tail.value tail = tail.prev return ListHead
Unterschied zwischen einfach und doppelt verknรผpfter Liste
Der Hauptunterschied zwischen der Single- und der Double Linked-Liste besteht in der Anzahl der Links.
Hier ist der Unterschied zwischen den Knoten einer einfach verknรผpften Liste und der Knotenstruktur der doppelt verknรผpften Liste:
| Feld | Einfach verknรผpfte Liste | Doppelt verknรผpfte Liste |
|---|---|---|
| Struktur | Einfach verknรผpfte Liste hat ein Datenfeld und einen Link zum nรคchsten Knoten. | Eine doppelt verknรผpfte Liste verfรผgt รผber ein Datenfeld und zwei Links. Eine fรผr den vorherigen Knoten und eine fรผr den nรคchsten Knoten. |
| Traversal | Es kann nur von Kopf bis Schwanz wandern. | Es kann sowohl vorwรคrts als auch rรผckwรคrts fahren. |
| Memory | Belegt weniger Speicher. | Sie belegt mehr Speicher als eine einfach verknรผpfte Liste. |
| Barierrefreiheit | Einfach verknรผpfte Listen sind weniger effizient, da sie nur einen Link zum nรคchsten Knoten verwenden. Fรผr den vorherigen Knoten gibt es keinen Link. | Doppelt verknรผpfte Listen sind effizienter als einfach verknรผpfte Listen. |
Doppelt verkettete Liste in C++
#include<iostream>
using namespace std;
struct node{
int data;
struct node *next;
struct node *prev;
};
void insertFront(node* &listHead, int value){
node* newNode = new node();
newNode->data = value;
newNode->prev = NULL;
newNode->next = NULL;
if(listHead!=NULL)
{
listHead->prev = newNode;
newNode->next = listHead;
}
listHead = newNode;
cout<<"Added "<<value<<" at the front"<<endl;
}
void insertEnd(node * &listHead, int value){
if(listHead==NULL)
{
insertFront(listHead,value);
}
node* newNode = new node();
newNode->data = value;
newNode->prev = NULL;
newNode->next = NULL;
node *head = listHead;
while(head->next!=NULL){
head = head->next;
}
head->next = newNode;
newNode->prev = head;
cout<<"Added "<<value<<" at the end"<<endl;
}
void insertAfter(node * &listHead, int searchValue,int value){
node* newNode = new node();
newNode->data = value;
newNode->prev = NULL;
newNode->next = NULL;
node *head = listHead;
while(head->next!=NULL && head->data!=searchValue){
head = head->next;
}
newNode->next = head->next;
head->next = newNode;
newNode->prev = head;
if(newNode->next !=NULL){
newNode->next->prev = newNode;
}
cout<<"Inserted "<<value<<" after node "<<searchValue<<endl;
}
void insertBefore(node * &listHead, int searchValue,int value){
node* newNode = new node();
newNode->data = value;
newNode->prev = NULL;
newNode->next = NULL;
node *head = listHead;
while(head->next!=NULL && head->next->data!=searchValue){
head = head->next;
}
newNode->next = head->next;
head->next = newNode;
newNode->prev = head;
if(newNode->next !=NULL){
newNode->next->prev = newNode;
}
cout<<"Inserted "<<value<<" before node "<<searchValue<<endl;
}
void traverseFromFront(node *listHead){
node* head = new node();
head = listHead;
cout<<"Traversal from head:\t";
while(head!=NULL){
cout<<head->data<<"\t ";
head = head->next;
}
cout<<endl;
}
void traverseFromEnd(node *listHead){
node* head = new node();
head = listHead;
cout<<"Traversal from head:\t";
while(head->next!=NULL){
head = head->next;
}
node *tail = head;
while(tail!=NULL){
cout<<tail->data<<"\t";
tail = tail->prev;
}
cout<<endl;
}
void searchAndDelete(node **listHead, int searchItem){
node* head = new node();
head = (*listHead);
while(head->next!=NULL && head->data!=searchItem){
head = head->next;
}
if(*listHead == NULL || head == NULL) return;
if((*listHead)->data == head->data){
*listHead = head->next;
}
if(head->next != NULL){
head->next->prev = head->prev;
}
if(head->prev != NULL){
head->prev->next = head->next;
}
free(head);
cout<<"Deleted Node\t"<<searchItem<<endl;
return;
}
int main(){
node *head = NULL;
insertFront(head,5);
insertFront(head,6);
insertFront(head,7);
insertEnd(head,9);
insertEnd(head,10);
insertAfter(head,5,11);
insertBefore(head,5,20);
traverseFromFront(head);
traverseFromEnd(head);
searchAndDelete(&head,7);
traverseFromFront(head);
traverseFromEnd(head);
}
Ausgang
Added 5 at the front Added 6 at the front Added 7 at the front Aded 9 at the end Added 10 at the end Inserted 11 after node 5 Inserted 20 before node 5 Traversal from head: 7 6 20 5 11 9 10 Traversal from head: 10 9 11 5 20 6 7 Deleted Node 7 Traversal from head: 6 20 5 11 9 10 Traversal from head: 10 9 11 5 20 6
Doppelt verkettete Liste in Python
class node:
def __init__(self,data = None, prev=None, next = None):
self.data = data
self.next = next
self.prev = prev
class DoublyLinkedList:
def __init__(self):
self.head = None
def insertFront(self, val):
newNode = node(data=val)
newNode.next = self.head
if self.head is not None:
self.head.prev = newNode
self.head = newNode
print("Added {} at the front".format(val))
def insertEnd(self,val):
newNode = node(data=val)
if self.head is None:
self.insertFront(val)
temp = self.head
while temp.next is not None:
temp = temp.next
temp.next = newNode
newNode.prev = temp
print("Added {} at the end".format(val))
def traverseFromFront(self):
temp = self.head
print("Traversing from head:\t",end="")
while temp:
print("{}\t".format(temp.data),end="")
temp = temp.next
print()
def traverseFromEnd(self):
temp = self.head
print("Traversing from Tail:\t",end="")
while temp.next is not None:
temp = temp.next
tail = temp
while tail is not None:
print("{}\t".format(tail.data),end="")
tail = tail.prev
print()
def insertAfter(self,searchItem, value):
newNode = node(data=value)
temp = self.head
while temp.next is not None and temp.data is not searchItem:
temp = temp.next
newNode.next = temp.next
temp.next = newNode
newNode.prev = temp
if newNode.next is not None:
newNode.next.prev = newNode
print("Inserted {} after node {}".format(value,searchItem))
def insertBefore(self,searchItem, value):
newNode = node(data=value)
temp = self.head
while temp.next is not None and temp.next.data is not searchItem:
temp = temp.next
newNode.next = temp.next
temp.next = newNode
newNode.prev = temp
if newNode.next is not None:
newNode.next.prev = newNode
print("Inserted {} before node {}".format(value,searchItem))
def searchAndDelete(self,searchItem):
temp = self.head
while temp.next is not None and temp.data is not searchItem:
temp = temp.next
if self.head is None or temp is None:
return
if self.head.data is temp.data:
self.head = temp.next
if temp.next is not None:
temp.next.prev = temp.prev
if temp.prev is not None:
temp.prev.next = temp.next
print("Deleted Node\t{}".format(searchItem))
return
doublyLinkedList = DoublyLinkedList()
doublyLinkedList.insertFront(5)
doublyLinkedList.insertFront(6)
doublyLinkedList.insertFront(7)
doublyLinkedList.insertEnd(9)
doublyLinkedList.insertEnd(10)
doublyLinkedList.insertAfter(5, 11)
doublyLinkedList.insertBefore(5, 20)
doublyLinkedList.traverseFromFront()
doublyLinkedList.traverseFromEnd()
doublyLinkedList.searchAndDelete(7)
doublyLinkedList.traverseFromFront()
doublyLinkedList.traverseFromEnd()
Ausgang
Added 5 at the front Added 6 at the front Added 7 at the front Added 9 at the end Added 10 at the end Inserted 11 after node 5 Inserted 20 before node 5 Traversing from head: 7 6 20 5 11 9 10 Traversing from Tail: 10 9 11 5 20 6 7 Deleted Node 7 Traversing from head: 6 20 5 11 9 10 Traversing from Tail: 10 9 11 5 20 6
Komplexitรคt einer doppelt verketteten Liste
Im Allgemeinen wird die Zeitkomplexitรคt in drei Typen unterteilt.
Sie sind:
- besten Case
- Durchschnittlicher Fall
- Schlimmsten Fall
Zeitliche Komplexitรคt im besten Fall fรผr doppelt verkettete Listen:
- Das Einfรผgen in Kopf oder Ende kostet O(1). Weil wir die verknรผpfte Liste nicht durchlaufen mรผssen. Der Kopf- und der Endzeiger kรถnnen uns mit einer Zeitkomplexitรคt von O(1) Zugriff auf den Kopf- und Endknoten geben.
- Das Lรถschen am Anfang oder am Ende kostet O(1).
- Die Suche nach einem Knoten hat eine Zeitkomplexitรคt von O(1), da der Zielknoten der Kopfknoten sein kann.
Zeitliche Komplexitรคt im Durchschnittsfall fรผr doppelt verkettete Listen:
- Das Einfรผgen am Anfang oder Ende hat eine Zeitkomplexitรคt von O(1).
- Das Lรถschen am Anfang oder Ende hat eine Zeitkomplexitรคt von O(1).
- Die Suche nach einem Knoten hat eine Zeitkomplexitรคt von O(n). Denn das Suchelement kann sich an beliebiger Stelle in der verknรผpften Liste befinden. Dabei ist โnโ die Gesamtzahl der in der verknรผpften Liste vorhandenen Knoten.
Die Zeitkomplexitรคt der doppelt verketteten Liste im schlimmsten Fall ist die gleiche wie im Durchschnittsfall.
Speicherkomplexitรคt einer doppelt verknรผpften Liste
Die Speicherkomplexitรคt betrรคgt O(n), wobei โnโ die Gesamtzahl der Knoten ist. Wรคhrend der Implementierung der verknรผpften Liste mรผssen wir den von uns verwendeten Speicher freigeben. Andernfalls kommt es bei einer grรถรeren verknรผpften Liste zu Speicherverlusten.
Zusammenfassung
- Eine verknรผpfte Liste ist eine Art lineare Datenstruktur, eine Sammlung von Daten, die linear dargestellt werden.
- Eine doppelt verknรผpfte Liste ist eine Art verknรผpfter Liste, bei der ein Knoten Verknรผpfungen sowohl zum vorherigen als auch zum nรคchsten Knoten aufweist.
- Eine doppelt verkettete Liste enthรคlt alle Operationen wie das Hinzufรผgen eines Knotens, das Lรถschen eines Knotens, das Einfรผgen eines Knotens nach oder vor einem anderen Knoten und das Durchlaufen der verketteten Liste von Anfang bis Ende.
- Eine doppelt verknรผpfte Liste verfรผgt รผber ein Datenfeld und zwei Links. Eine fรผr den vorherigen Knoten und eine fรผr den nรคchsten Knoten.
- Komplexitรคt einer doppelt verketteten Liste, bester Fall, durchschnittlicher Fall
- Und im schlimmsten Fall.



