Doubly Linked Lists
Aufgabe
So funktioniert eine doppelt verkettete Liste
- Knoten: Die Liste besteht aus Knoten. Jeder Knoten speichert einen Wert (
value), einen Pointer auf den nächsten Knoten (next) und einen auf den vorherigen (prev). - Enden:
headzeigt auf den ersten Knoten,tailauf den letzten. Beim ersten Knoten istprevNULL, beim letzten istnextNULL. In einer leeren Liste sindheadundtailNULL. - Durchlaufen: Vorwärts geht es von
headübernextbisNULL, rückwärts vontailüberprevbisNULL. - Einfügen am Ende: Der neue Knoten bekommt
prev = tailundnext = NULL. War die Liste leer, wird er zuhead, sonst zeigt der bisher letzte Knoten mitnextauf ihn. Danach zeigttailauf den neuen Knoten. - Löschen: Vorgänger und Nachfolger des Knotens werden direkt verbunden:
nextdes Vorgängers zeigt auf den Nachfolger,prevdes Nachfolgers auf den Vorgänger. Fehlt der Vorgänger (erster Knoten), wird der Nachfolger zuhead; fehlt der Nachfolger (letzter Knoten), wird der Vorgänger zutail. Danach geben Sie den Knoten mitfreefrei. - Speicher: Jeder Knoten, der mit
mallocangelegt wurde, wird genau einmal mitfreefreigegeben.
Beispiel: Nach dem Einfügen von 4, 7 und 10 sieht die Liste so aus (head → 4, tail → 10):
| Knoten | prev | next |
|---|---|---|
| 4 | NULL | 7 |
| 7 | 4 | 10 |
| 10 | 7 | NULL |
Löschen von 7 (Knoten in der Mitte): next von 4 zeigt jetzt auf 10, prev von 10 auf 4; Knoten 7 wird freigegeben. head und tail bleiben, wie sie sind.
| Knoten | prev | next |
|---|---|---|
| 4 | NULL | 10 |
| 10 | 4 | NULL |
Vorwärts ausgegeben ergibt das 4 10, rückwärts 10 4. Würde man anschließend 4 löschen (den ersten Knoten), wäre 10 der einzige Knoten: head und tail zeigen beide auf 10, dessen prev und next sind NULL.
Demo
So sieht ein Lauf des fertigen Programms aus (hinter > und den Fragen stehen die Eingaben):
$ ./doublylinkedlist
(1) Zahl einfügen
(2) Liste vorwärts ausgeben
(3) Liste rückwärts ausgeben
(4) Wert suchen
(5) Wert löschen
(6) gesamte Liste löschen
(0) Programm beenden
> 1
Zahl: 5
> 1
Zahl: 9
> 2
Vorwärts: 5 9
> 3
Rückwärts: 9 5
> 4
Wert suchen: 9
9 wurde gefunden.
> 5
Wert löschen: 5
5 gelöscht.
> 2
Vorwärts: 9
> 0
Programm beendet.Aufgabenmaterial
Für diese Aufgabe werden Sie ein von uns zur Verfügung gestelltes Codegerüst vervollständigen. Legen Sie einen Ordner doublylinkedlist an und darin die Datei doublylinkedlist.c mit diesem Inhalt:
Codegerüst
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
// Menü nur einmal ausgeben
printf(
"(1) Zahl einfügen\n"
"(2) Liste vorwärts ausgeben\n"
"(3) Liste rückwärts ausgeben\n"
"(4) Wert suchen\n"
"(5) Wert löschen\n"
"(6) gesamte Liste löschen\n"
"(0) Programm beenden\n"
);
while (true)
{
// Benutzerauswahl abfragen
int choice = get_int("> ");
// Operationen auswerten
switch (choice)
{
case 1:
// Zahl einfügen
break;
case 2:
// Liste vorwärts ausgeben
break;
case 3:
// Liste rückwärts ausgeben
break;
case 4:
// Wert suchen
break;
case 5:
// Wert löschen
break;
case 6:
// gesamte Liste löschen
break;
case 0:
// gesamte Liste löschen
printf("Programm beendet.\n");
return 0;
default:
printf("Ungültige Auswahl. Bitte erneut versuchen.\n");
}
}
}Spezifikation
Datenstruktur. Definieren Sie die Struktur node und zwei globale Pointer auf den ersten und den letzten Knoten:
typedef struct node
{
int value;
struct node *next;
struct node *prev;
} node;
node *head = NULL; // zeigt auf den ersten Knoten
node *tail = NULL; // zeigt auf den letzten Knoten
Funktionen. Die Funktionen geben selbst nur dort etwas aus, wo es in der Tabelle steht; alle anderen Meldungen gibt main aus.
| Funktion | Aufgabe |
|---|---|
void insert_end(int value) | Legt mit malloc einen neuen Knoten an und hängt ihn ans Ende der Liste (Regel 4), auch wenn die Liste leer ist. |
bool search(int value) | Durchläuft die Liste ab head und gibt true zurück, wenn value darin vorkommt, sonst false. Gibt nichts aus. |
bool delete_value(int value) | Löscht den ersten Knoten (von head aus) mit diesem Wert (Regel 5) und gibt true zurück. Kommt der Wert nicht vor, bleibt die Liste unverändert, Rückgabe false. |
void print_forward(void) | Gibt Vorwärts: aus, dann die Werte von head bis tail, durch Leerzeichen getrennt, dann einen Zeilenumbruch. |
void print_backward(void) | Wie print_forward, aber mit Rückwärts: und von tail bis head. |
void clear_list(void) | Gibt alle Knoten mit free frei und setzt head und tail auf NULL. |
Beim Löschen müssen diese Fälle stimmen: Der Knoten ist der einzige, der erste, der letzte oder einer in der Mitte der Liste – oder der Wert kommt gar nicht vor.
Menü. main gibt das Menü einmal aus und fragt dann immer wieder mit > nach einer Auswahl:
| Auswahl | Eingabe | Ausgabe |
|---|---|---|
1 | Zahl: | – |
2 | – | Vorwärts: 5 9 |
3 | – | Rückwärts: 9 5 |
4 | Wert suchen: | 9 wurde gefunden. oder 9 nicht in der Liste. |
5 | Wert löschen: | 5 gelöscht. oder 5 nicht gefunden. |
6 | – | Liste geleert. |
0 | – | Programm beendet. – vorher wird die Liste freigegeben |
| sonst | – | Ungültige Auswahl. Bitte erneut versuchen. |
Die Zahlen in der Ausgabe sind Beispiele; eingesetzt wird jeweils der eingegebene Wert.
Hilfestellung
Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten. Versuchen Sie aber zunächst, selbst so weit wie möglich zu kommen.
Wo stehen node, head, tail und die Prototypen?
Schreiben Sie die Struktur node, die globalen Pointer head und tail und die Prototypen der sechs Funktionen über main. Die Funktionen selbst können dann unter main stehen:
void insert_end(int value);
bool search(int value);
bool delete_value(int value);
void print_forward(void);
void print_backward(void);
void clear_list(void);Wenn Sie in einem case eine Variable anlegen (etwa int value = get_int("Zahl: ");), setzen Sie den Inhalt des case in geschweifte Klammern { … }. Sonst meldet der Compiler einen Fehler.
Die Liste durchlaufen
search, delete_value und die beiden Ausgabefunktionen folgen demselben Muster: Ein Hilfs-Pointer startet an einem Ende und springt so lange weiter, bis er NULL ist.
for (node *current = head; current != NULL; current = current->next)
{
// current->value verarbeiten
}Rückwärts starten Sie bei tail und springen über current->prev. Wie das mit einfach verketteten Listen aussieht, zeigt das Short Verkettete Listen.
Implementierung von delete_value
Suchen Sie den Knoten wie in search. Haben Sie ihn gefunden (current), prüfen Sie beide Seiten getrennt:
- Hat
currenteinen Vorgänger (current->prev != NULL), dann bekommt dieser alsnextden Nachfolger voncurrent. Sonst istcurrentder erste Knoten, undheadwird zum Nachfolger. - Hat
currenteinen Nachfolger, dann bekommt dieser alsprevden Vorgänger voncurrent. Sonst istcurrentder letzte Knoten, undtailwird zum Vorgänger.
Diese zwei Prüfungen decken alle vier Fälle ab: Beim einzigen Knoten greifen beide „sonst“-Zweige, und head und tail werden NULL. Geben Sie current erst danach mit free frei und beenden Sie die Funktion mit return true.
Implementierung von clear_list
Nach free(current) dürfen Sie current->next nicht mehr lesen. Merken Sie sich den Nachfolger deshalb vorher:
node *current = head;
while (current != NULL)
{
node *next = current->next;
free(current);
current = next;
}Vergessen Sie nicht, head und tail danach auf NULL zu setzen – sonst zeigen sie auf freigegebenen Speicher.
Testen
Stellen Sie beim Testen Ihres Codes sicher, dass dieser mit folgenden Szenarien umgehen kann:
- Ausgabe einer leeren Liste (vorwärts und rückwärts: nur
Vorwärts:bzw.Rückwärts:) - Einfügen mehrerer Zahlen; vorwärts und rückwärts stehen dieselben Zahlen in umgekehrter Reihenfolge
- Suchen eines vorhandenen und eines nicht vorhandenen Werts
- Löschen des ersten, des letzten, eines mittleren und des einzigen Knotens; danach jeweils vorwärts und rückwärts ausgeben – so fallen falsch gesetzte
prev-Pointer auf - Löschen eines Werts, der nicht in der Liste steht
- Löschen der gesamten Liste (
6) und danach erneutes Einfügen - eine ungültige Menünummer
Korrektheit
Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:
check50 -l inf-zone/exercises/2026/doublylinkedlistSpeicher
Ob Ihr Programm allen Speicher freigibt, zeigt valgrind (siehe Vorlesung 4). Fügen Sie ein paar Zahlen ein, löschen Sie einige und beenden Sie das Programm mit 0:
valgrind ./doublylinkedlistStyle
Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit style50Prüfprogramm von CS50, das zeigt, wo Ihr Code von den Formatierungsregeln abweicht. In Grün zeigt es, was Sie ergänzen sollten, in Rot, was wegfallen soll. Glossar → zu analysieren:
style50 doublylinkedlist.cAbgeben
Geben Sie im Ordner doublylinkedlist ab:
inf upload doublylinkedlistDanach sehen Sie, welche Tests Ihr Programm besteht und ob die Übung für die Bonuspunkte zählt. Wie Sie inf installieren und sich anmelden, steht unter Abgeben mit inf.
Diese Seite als Markdown: ansehen herunterladen