[A]Algorithmen/Datenstrukturen: Einfach-/Doppeltverkettete Listen
-
Also der Code deiner Rekursiven Liste gefällt mir nicht. (Das ist nur meine Meinung)
Ausserdem sieht das mehr nach nem Single Type Tuple aus
Eventuell kannst du anstatt der Rekursiven Liste eine vereinfachte implementation eines tuple zeigen.
Ich denke das es mit den TypListen am besten zusammen passt
Ach ja und warum ist eigentlich dein "int length_;" signed? Gibt es auch negative längen?
Besser wäre du würdest einen unsigned datentypen wie z.B. size_t nehmen.
BR
VinzenzPS: Ich habs nur kurz überflogen und das ist was mir so aufgefallen ist. Ich denke das ein paar Teile des Artikels der Code Qualität wegen etwas Überarbeitung brauchen könnten.
-
Vielleicht doch nochmal über eine zyklische Implementierung mit Dummy-Head nachdenken? Ich denke, das könnte *den* Unterschied zu den 1000 Tutorials, die es zum Thema schon gibt, darstellen. Insbesondere, weil zum Beispiel viele STL-Implementierungen das benutzen und eben nicht die (imo leider) weitverbreitete null-terminierte Variante.
-
evilissimo schrieb:
Also der Code deiner Rekursiven Liste gefällt mir nicht. (Das ist nur meine Meinung)
Ausserdem sieht das mehr nach nem Single Type Tuple aus
Eventuell kannst du anstatt der Rekursiven Liste eine vereinfachte implementation eines tuple zeigen.
Ich denke das es mit den TypListen am besten zusammen passt
Ach ja und warum ist eigentlich dein "int length_;" signed? Gibt es auch negative längen?
Besser wäre du würdest einen unsigned datentypen wie z.B. size_t nehmen.
BR
VinzenzPS: Ich habs nur kurz überflogen und das ist was mir so aufgefallen ist. Ich denke das ein paar Teile des Artikels der Code Qualität wegen etwas Überarbeitung brauchen könnten.
Danke fürs Feedback
Mit der Überarbeitung hast du recht, dass mit den size_t wird natürlich auch noch geändert...
-
Jester schrieb:
Vielleicht doch nochmal über eine zyklische Implementierung mit Dummy-Head nachdenken? Ich denke, das könnte *den* Unterschied zu den 1000 Tutorials, die es zum Thema schon gibt, darstellen. Insbesondere, weil zum Beispiel viele STL-Implementierungen das benutzen und eben nicht die (imo leider) weitverbreitete null-terminierte Variante.
Jup, dass ist ne Überlegung wert... Da mach ich mich am WE dran...
Gruß
Tobi
-
So, auf Anregung von evilissimo und Jester hab ich den Code nochmal überarbeitet...
Ich bitte um Feedback zur einfach verketteten Liste.
-
Inhaltsverzeichnis
-
Die einfach verkettete Liste
-
Definition
-
Einfügen
-
Finden
-
Holen
-
Löschen
-
Destruktor und die Listenlänge
-
Die doppelt verkettete Liste
-
Typlisten
-
Anwendungsgebiete
1 Die einfach verkettete Liste
Eine einfach verkettete Liste besteht aus Knoten die vorwärts miteinander verkettet sind. In den meisten Implementierungen, die man in Tutorials findet wird die Liste null terminiert. In diesem Artikel verwenden wir zur Endeerkennung ein anderes Verfahren.
Die folgenden Codefragmente sind in C++ geschrieben.
1.1 Definition der Liste
template <typename data_t> class singleLinkedList { public: singleLinkedList() : head(new singleLinkedNode<data_t>()) , length(0) { // Constructor erzeugt Dummy head head->next = head; } protected: template <typename data_t> class singleLinkedNode {}; // Es darf nur Listknoten mit dem selben Typ wie in der Liste geben template<> class singleLinkedNode<data_t> { public: singleLinkedNode() : next(NULL) {} singleLinkedNode(data_t data) : data(data), next(NULL) {} singleLinkedNode* next; data_t data; }; private: singleLinkedNode<data_t>* head; size_t length; };
Der Konstruktor der Liste erzeugt einen dummy head, der bei dieser Implementierung als Endeerkennung dient. Die Listdefinition ist außerdem zyklisch, da der "dummy" head wieder auf sich selbst zeigt.
Die Definition der Knoten wird in der Listenklasse versteckt, da die Knotendefinition von außen nicht sichtbar sein muss.
Das Einfügen in die Liste ist, da wir am Anfang der Liste einfügen schnell erledigt.
1.2 Einfügen eines Elements in die Liste
void insertItem(data_t d) { singleLinkedNode<data_t> *tmp = new singleLinkedNode<data_t>(d); //Neue Node einhängen tmp->next = head->next; head->next = tmp; //Länge erhöhen length++; }
Wie man im Code sieht, fügt man also immer vor dem head ein.
1.3 Finden eines Elements n der Liste
int findItem(data_t d) { int index = 0; head->data = d; singleLinkedNode<data_t> *tmp = head->next; // Das Element vor dem zu suchenden Element finden while(tmp->data != d) { tmp = tmp->next; ++index; } return ( index == length ? -1 : index); }
Man speichert das zu suchende Element, im head. Findet man das Element im head, ist die Länge zu groß. Genau auf diese Bedingung wird beim return geprüft.
1.4 Holen eines Elements aus der Liste
data_t* getItem(int index) { singleLinkedNode<data_t> *tmp = head; // Ausserhalb der Liste if(index < 0 || index >= static_cast<int>(length)) return NULL; do { tmp = tmp->next; --index; } while(index >= 0); return &(tmp->data); }
Geholt wird ein Element über den Index (siehe findElement). Dabei ist immer zu beachten, dass wir am Beginn der Liste einfügen.
In der Funktion selbst prüfen wir den Range des Index. Ist der außerhalb der Liste, springen wir aus der Funktion.
Ansonsten holen wir index mal das nächste Element. Wegen dem Sonderfall index = 0 ist die Schleife eine do while Schleife.
1.5 Löschen eines Elements aus der Liste
void deleteItem(data_t d) { singleLinkedNode<data_t>*del, *tmp = head; // Zu löschendes Element suchen while(tmp->next != head && tmp->next->data != d) tmp = tmp->next; // Nicht gefunden return (könnte ein bool sein, dann return false) if(tmp->next == head) return; // Das zu löschende Element holen del = tmp->next; // Zeiger verbiegen tmp->next = tmp->next->next; // und löschen delete del; --length; }
Wir suchen zunächst das Element und merken uns den Vorgänger davon. Finden wir das Element nicht, springen wir aus der Funktion. Anderfalls verbiegen wir die Zeiger und löschen das Element.
1.6 Destruktor und die Listenlänge
~singleLinkedList() { // Destruktor zerstört alle Elemente in der liste incl. dummy head singleLinkedNode<data_t> *del = head->next, *tmp = head->next->next; // Solange Elemente in der Liste while(del != head) { //lösche gefundenes Element und gehe zum nächsten delete del; del = tmp; // Schützt vor weiterleitung auf ein gelöschtes Element if(tmp != head) tmp = tmp->next; } delete del; length = 0; } const size_t getLength() { return length; }
Im Destruktor löschen wir alle verbliebenen Elemente der Liste einschließlich dem head.
Die Längenfunktion ist ein simpler return der aktuellen Länge.
Bilder von der Neuimplementierung werden noch nachgeliefert....
Bitte um Feedback zum Code.
-
-
Zur Suche:
wie wär's mit folgender Implementierung
int findItem(data_t d) { head->data = d; // sentinel plazieren singleLinkedNode<data_t> * p = head->next; int pos = 0; while(p->data != d){ p = p->next; ++pos; } return pos; }
bzw. am schluß noch abprüfen, ob die zahl zu groß ist und -1 zurückgeben. Wenn man Iteratoren implementiert und den Iterator, der auf den head zeigt als end benutzt, dann kriegt man damit sogar automatisch den end-iterator zurück, wenn das datum nicht in der liste ist.
-
Stimmt, die Idee mit dem Sentinel ist nicht übel... Bau ich gleich noch mit ein...
Gruß
Tobi
-
Inhaltsverzeichnis
-
Die einfach verkettete Liste
-
Definition
-
Einfügen
-
Finden
-
Holen
-
Löschen
-
Destruktor und die Listenlänge
-
Die doppelt verkettete Liste
-
Typlisten
-
Anwendungsgebiete
1 Die einfach verkettete Liste
Eine einfach verkettete Liste besteht aus Knoten die vorwärts miteinander verkettet sind. In den meisten Implementierungen, die man in Tutorials findet wird die Liste null terminiert. In diesem Artikel verwenden wir zur Endeerkennung ein anderes Verfahren.
Die folgenden Codefragmente sind in C++ geschrieben.
1.1 Definition der Liste
template <typename data_t> class singleLinkedList { public: singleLinkedList() : head(new singleLinkedNode<data_t>()) , length(0) { // Constructor erzeugt Dummy head head->next = head; } protected: template <typename data_t> class singleLinkedNode {}; // Es darf nur Listknoten mit dem selben Typ wie in der Liste geben template<> class singleLinkedNode<data_t> { public: singleLinkedNode() : next(NULL) {} singleLinkedNode(data_t data) : data(data), next(NULL) {} singleLinkedNode* next; data_t data; }; private: singleLinkedNode<data_t>* head; size_t length; };
Der Konstruktor der Liste erzeugt einen dummy head, der bei dieser Implementierung als Endeerkennung dient. Die Listdefinition ist außerdem zyklisch, da der "dummy" head wieder auf sich selbst zeigt.
Die Definition der Knoten wird in der Listenklasse versteckt, da die Knotendefinition von außen nicht sichtbar sein muss.
Das Einfügen in die Liste ist, da wir am Anfang der Liste einfügen schnell erledigt.
1.2 Einfügen eines Elements in die Liste
void insertItem(data_t d) { singleLinkedNode<data_t> *tmp = new singleLinkedNode<data_t>(d); //Neue Node einhängen tmp->next = head->next; head->next = tmp; //Länge erhöhen length++; }
Wie man im Code sieht, fügt man also immer vor dem head ein.
1.3 Finden eines Elements n der Liste
int findItem(data_t d) { int index = 0; head->data = d; singleLinkedNode<data_t> *tmp = head->next; // Das Element vor dem zu suchenden Element finden while(tmp->data != d) { tmp = tmp->next; ++index; } return ( index == length ? -1 : index); }
Man speichert das zu suchende Element, im head. Findet man das Element im head, ist die Länge zu groß. Genau auf diese Bedingung wird beim return geprüft.
1.4 Holen eines Elements aus der Liste
data_t* getItem(int index) { singleLinkedNode<data_t> *tmp = head; // Ausserhalb der Liste if(index < 0 || index >= static_cast<int>(length)) return NULL; do { tmp = tmp->next; --index; } while(index >= 0); return &(tmp->data); }
Geholt wird ein Element über den Index (siehe findElement). Dabei ist immer zu beachten, dass wir am Beginn der Liste einfügen.
In der Funktion selbst prüfen wir den Range des Index. Ist der außerhalb der Liste, springen wir aus der Funktion.
Ansonsten holen wir index mal das nächste Element. Wegen dem Sonderfall index = 0 ist die Schleife eine do while Schleife.
1.5 Löschen eines Elements aus der Liste
void deleteItem(data_t d) { singleLinkedNode<data_t>*del, *tmp = head; // Zu löschendes Element suchen while(tmp->next != head && tmp->next->data != d) tmp = tmp->next; // Nicht gefunden return (könnte ein bool sein, dann return false) if(tmp->next == head) return; // Das zu löschende Element holen del = tmp->next; // Zeiger verbiegen tmp->next = tmp->next->next; // und löschen delete del; --length; }
Wir suchen zunächst das Element und merken uns den Vorgänger davon. Finden wir das Element nicht, springen wir aus der Funktion. Anderfalls verbiegen wir die Zeiger und löschen das Element.
1.6 Destruktor und die Listenlänge
~singleLinkedList() { // Destruktor zerstört alle Elemente in der liste incl. dummy head singleLinkedNode<data_t> *del = head->next, *tmp = head->next->next; // Solange Elemente in der Liste while(del != head) { //lösche gefundenes Element und gehe zum nächsten delete del; del = tmp; // Schützt vor weiterleitung auf ein gelöschtes Element if(tmp != head) tmp = tmp->next; } delete del; length = 0; } const size_t getLength() { return length; }
Im Destruktor löschen wir alle verbliebenen Elemente der Liste einschließlich dem head.
Die Längenfunktion ist ein simpler return der aktuellen Länge.
Bilder von der Neuimplementierung werden noch nachgeliefert....
Bitte um Feedback zum Code.
-
-
Inhaltsverzeichnis
-
Die einfach verkettete Liste
-
Definition
-
Einfügen
-
Finden
-
Holen
-
Löschen
-
Destruktor und die Listenlänge
-
Die doppelt verkettete Liste
-
Definition
-
Einfügen
-
Finden
-
Löschen
-
Destruktor und die Listenlänge
-
Typlisten
-
Rekursive Datentypen
-
Listen von Datentypen
-
Operationen auf Typlisten
-
Anwendungsgebiete
1 Die einfach verkettete Liste
Eine einfach verkettete Liste besteht aus Knoten die vorwärts miteinander verkettet sind. In den meisten Implementierungen, die man in Tutorials findet wird die Liste null terminiert. In diesem Artikel verwenden wir zur Endeerkennung ein anderes Verfahren.
Die folgenden Codefragmente sind in C++ geschrieben.
1.1 Definition der Liste
template <typename data_t> class singleLinkedList { public: singleLinkedList() : head(new singleLinkedNode<data_t>()) , length(0) { // Constructor erzeugt Dummy head head->next = head; } protected: template <typename data_t> class singleLinkedNode {}; // Es darf nur Listknoten mit dem selben Typ wie in der Liste geben template<> class singleLinkedNode<data_t> { public: singleLinkedNode() : next(NULL) {} singleLinkedNode(data_t data) : data(data), next(NULL) {} singleLinkedNode* next; data_t data; }; private: singleLinkedNode<data_t>* head; size_t length; };
Der Konstruktor der Liste erzeugt einen dummy head, der bei dieser Implementierung als Endeerkennung dient. Die Listdefinition ist außerdem zyklisch, da der "dummy" head wieder auf sich selbst zeigt.
Die Definition der Knoten wird in der Listenklasse versteckt, da die Knotendefinition von außen nicht sichtbar sein muss.
Das Einfügen in die Liste ist, da wir am Anfang der Liste einfügen schnell erledigt.
1.2 Einfügen eines Elements in die Liste
void insertItem(data_t d) { singleLinkedNode<data_t> *tmp = new singleLinkedNode<data_t>(d); //Neue Node einhängen tmp->next = head->next; head->next = tmp; //Länge erhöhen length++; }
Wie man im Code sieht, fügt man also immer vor dem head ein.
1.3 Finden eines Elements n der Liste
int findItem(data_t d) { int index = 0; head->data = d; singleLinkedNode<data_t> *tmp = head->next; // Das Element vor dem zu suchenden Element finden while(tmp->data != d) { tmp = tmp->next; ++index; } return ( index == length ? -1 : index); }
Man speichert das zu suchende Element, im head. Findet man das Element im head, ist die Länge zu groß. Genau auf diese Bedingung wird beim return geprüft.
1.4 Holen eines Elements aus der Liste
data_t* getItem(int index) { singleLinkedNode<data_t> *tmp = head; // Ausserhalb der Liste if(index < 0 || index >= static_cast<int>(length)) return NULL; do { tmp = tmp->next; --index; } while(index >= 0); return &(tmp->data); }
Geholt wird ein Element über den Index (siehe findElement). Dabei ist immer zu beachten, dass wir am Beginn der Liste einfügen.
In der Funktion selbst prüfen wir den Range des Index. Ist der außerhalb der Liste, springen wir aus der Funktion.
Ansonsten holen wir index mal das nächste Element. Wegen dem Sonderfall index = 0 ist die Schleife eine do while Schleife.
1.5 Löschen eines Elements aus der Liste
void deleteItem(data_t d) { singleLinkedNode<data_t>*del, *tmp = head; // Zu löschendes Element suchen while(tmp->next != head && tmp->next->data != d) tmp = tmp->next; // Nicht gefunden return (könnte ein bool sein, dann return false) if(tmp->next == head) return; // Das zu löschende Element holen del = tmp->next; // Zeiger verbiegen tmp->next = tmp->next->next; // und löschen delete del; --length; }
Wir suchen zunächst das Element und merken uns den Vorgänger davon. Finden wir das Element nicht, springen wir aus der Funktion. Anderfalls verbiegen wir die Zeiger und löschen das Element.
1.6 Destruktor und die Listenlänge
~singleLinkedList() { // Destruktor zerstört alle Elemente in der liste incl. dummy head singleLinkedNode<data_t> *del = head->next, *tmp = head->next->next; // Solange Elemente in der Liste while(del != head) { //lösche gefundenes Element und gehe zum nächsten delete del; del = tmp; // Schützt vor weiterleitung auf ein gelöschtes Element if(tmp != head) tmp = tmp->next; } delete del; length = 0; } const size_t getLength() { return length; }
Im Destruktor löschen wir alle verbliebenen Elemente der Liste einschließlich dem head.
Die Längenfunktion ist ein simpler return der aktuellen Länge.
2 Die doppelt verkettete Liste
Die doppelt verkettete Liste hat zusätzlich zum next Zeiger auch einen back zeiger.
2.1 Definition
template <typename data_t> class doubleLinkedList { public: doubleLinkedList() : head(new doubleLinkedNode<data_t>()) , length(0) { // Constructor erzeugt Dummy head head->next = head; head->back = head; } protected: template <typename data_t> class doubleLinkedNode {}; // Es darf nur Listknoten mit dem selben Typ wie in der Liste geben template<> class doubleLinkedNode<data_t> { public: doubleLinkedNode() : next(NULL) {} doubleLinkedNode(data_t data) : data(data), next(NULL), back(NULL) {} doubleLinkedNode* next; doubleLinkedNode* back; data_t data; }; private: doubleLinkedNode<data_t>* head; size_t length; };
Die Definition ist von der einfach verketteten Liste schon bekannt. Einzig der back zeiger in der doubleLinkedNode ist hinzugekommen.
2.2 Einfügen die Liste
void insertItem(data_t d) { doubleLinkedNode<data_t> *tmp = new doubleLinkedNode<data_t>(d); tmp->next = head; tmp->back = head->back; head->back->next = tmp; head->back = tmp; this->length++; }
Beim Einfügen (hier ein back insert) wird zunächst das neue Element mit der Liste verknüpft. Danach wird Über den back Zeiger das Element in die Liste hinzugefügt.
[Bild]
2.3 Finden eines Elements in der Liste
data_t* findItem(data_t d) { doubleLinkedNode<data_t> *forward = head->next, *backward=head->back; // Das Element vor dem zu suchenden Element finden while(forward->data != d && backward->data != d) { if(forward->next == backward) return NULL; forward = forward->next; backward = backward->back; } return (forward->data == d) ? &(forward->data) : &(backward->data); }
Im Gegensatz zur Implementierung bei der einfach verketteten Liste erledigen wir in dieser Implementierung das finden und holen aus der Liste in einer Funktion.
Das gesuchte Element wird von hinten und vorne gesucht. Findet man das Element gibt man einen Zeiger auf die Daten im Knoten zurück, ansonsten wird ein Null Zeiger zurückgegeben.
[Bild]
2.4 Löschen aus der Liste
bool deleteItem(data_t item) { doubleLinkedNode<data_t>*del, *forward = head->next, *backward=head->back; // Das Element vor dem zu suchenden Element finden while(forward->data != item && backward->data != item) { if(forward->next == backward) return false; forward = forward->next; backward = backward->back; } del = (item == forward->data) ? forward : backward; del->back->next = del->next; del->next->back = del->back; return true; }
Wiederum suchen wir das Element. Finden wir das Element hängen wir es aus und löschen es. Desweiteren geben wir true zurück. Wird das Element nicht gefunden wird false zurückgegeben.
[bild]
2.5 Destruktor und Längenfunktion
~doubleLinkedList() { // Destruktor zerstört alle Elemente in der liste incl. dummy head doubleLinkedNode<data_t> *del = head->next; // Solange Elemente in der Liste while(del != head) { //lösche gefundenes Element und gehe zum nächsten del = del->next; delete del->back; } delete del; length = 0; }
Der Destruktor gibt den restlichen reservierten Speicher frei. Die Längen funktion kann aus der singleLinkedList entnommen werden.
3 Typlisten
3.1 Rekursive Datentypen
Nun kann man Listen auch als Rekursiven Datentyp auffassen.
Sie besteht aus einem Kopf und einer Restliste.Diese Struktur kann man über Tupel realisieren.
Bitte um Feedback zum Code.
-