[A]Algorithmen/Datenstrukturen: Einfach-/Doppeltverkettete Listen
-
Inhaltsverzeichnis
- Die einfach verkettete Liste
- Die doppelt verkettete Liste
- Typlisten
- Anwendungsgebiete
1 Die einfachverkettete Liste
Eine Liste besteht zunächst einmal aus Knoten die miteinander verknüpft werden. Eine Knotendefinition könnte so aussehen.
template <typename data_t> class singleLinkedNode { public: singleLinkedNode() : next_(0) {} singleLinkedNode(data_t data) : next_(0), data_(data) {} singleLinkedNode<data_t>* next_; data_t data_; };
Wie man hier sieht, gibt es nur einen Next Zeiger. Das bedeutet dass der Knoten seinen Nachfolger, nicht aber seinen Vorgänger kennt.
Die Liste die diese Knotenstruktur verwaltet definiert einen head Zeiger sowie einen current/end Zeiger. Diese beiden Zeiger erlauben ein schnelles Einfügen am Ende bzw. am Anfang der Liste.
template <typename data_t> class singleLinkedList { public: // Constructor for the list singleLinkedList() { head_ = 0; current_ = 0; } private: singleLinkedNode<data_t>* head_; singleLinkedNode<data_t>* current_; };
Der Konstruktor der Liste setzt die beiden Element auf Null.
Als nächstes benötigen wir eine insert Funktion. Die Funktion fügt Elemente am Ende der Liste ein.
// Construtor // ... // ... // insert something into list void insert(data_t data) { if(head_== 0) { // list is empty // insert first element and point head and current to the new element head_ = new singleLinkedNode<data_t>(data); current_ = head_; } else { // something is in the list // insert a element at the end singleLinkedNode<data_t>* temp = new singleLinkedNode<data_t>(data); current_->next_ = temp; current_ = temp; /*** if you want to insert the element at the beginning do it like this head_ = temp; temp->next_ = data; ***/ } } // private Sachen
Die Funktion prüft ab ob die Liste leer ist. Wenn dass der Fall ist, wird ein neues Element in die Liste eingefügt, head_ wird auf das neue Element gebogen und current_ zeigt ebenfalls auf das Element.
Ist die Liste nicht leer wird ein neues Objekt erzeugt und current_->next_ wird auf das neue Element temp gebogen (Die Verknüpfung). Danach wird der eigentliche current_ Zeiger auf das neue Element gebogen.
Einfügen in eine leere Liste
Der Anfangszustand ist mit (1) im Bild markiert. Nach dem Einfügen sieht es aus wie in Zustand (2).
Einfügen in eine nicht leere Liste.
Der Anfangszustand (1) zeigt eine nicht leere Liste mit 2 Elementen. Zum Einfügen eines neuen Elements verbiegen wir den next Link des current/end Zeiger auf das neue Element und dann den current/end Zeiger selbst. Das verschafft uns den Endzustand (2). Zum Einfüge am Beginn der Liste kann man das ganze Spiel auch mit dem Head Zeiger betreiben. Wie dass funktioniert steht im Quelltext.
Soweit so gut. Da man hier aber mit new operiert, muss irgendwann einmal der Speicher aufgeräumt werden. Das erledigen wir im Destruktor der so aussehen könnte.
~singleLinkedList() { singleLinkedNode<data_t>* run, *del; run = head_; while(run != NULL) { del = run; run = run->next_; delete del; } }
Der Destruktor läuft durch die Liste und löscht der Reihe nach ihre Elemente.
Als nächstes wäre es interessant zu sehen ob die ganze Konstruktion überhaupt funktioniert. Dazu schreiben wir eine ListOutput Funktion.
// Konstruktor // insert // Destructor // .. // .. void ListOutput() { singleLinkedNode<data_t>* run = head_; while(run != NULL) { std::cout << run->data_ << std::endl; run = run->next_; } } // private Deklarationen // ...
Im Prinzip funktioniert die Ausgabefunktion wie der Destruktor. Wir laufen wieder durch die Liste. Nur diesmal geben wir die in der Liste vorhanden Daten aus.
Ein mögliches Testprogramm könnte so aussehen.
int main(void) { singleLinkedList<int> list; list.insert(1); list.insert(2); list.ListOutput(); std::cin.get(); return 0; }
Natürlich braucht eine Liste noch mehr Funktionalität. Dazu gehören unter anderem Funktionen wie delete, deleteAt, find und getAt die wir als nächstes Implementieren.
Um die Funktion delete zu implementieren schreiben wir uns zunächst ein Hilfsfunktion die uns das Element vor dem zu Löschenden zurückliefert. Dieses element brauchen wir um die Zeiger nach bzw. vor dem Löschen neu setzen zu können.
private: singleLinkedNode<data_t>* findItemBefore(data_t data) { singleLinkedNode<data_t>* run = head_; if(head_->data_ == data) { // Wenn head gesucht ist, head aushängen head_ = head_->next_; run->next_ = run; } else { while(run->next_ != NULL && run->next_->data_ != data) run = run->next_; } return run; }
Zunächst prüfen wir ob head_ das zu löschende Element ist. Wenn das der Fall ist, schieben wir head auf das nächste Element und lassen run->next_ auf run zeigen. Damit ist das erste Element der Liste abgekoppelt und kann gelöscht werden.
Ansonsten laufen wir durch die Liste und schauen ob das nächste Element das gesuchte ist. Finden wir es nicht, steht run auf current_.
Warum liefern wir den current_ Zeiger zurück. Nunja, wir suchen schließlich das Element vor dem zu Löschenden. Und wenn wir dass zu löschende nicht finden löschen wir nichts, da current_->next_ auf NULL zeigt.
Den head_ aus der Liste auskoppeln
Die roten Pfeile verbildlichen das Auskoppeln des Kopfes der Liste. Da der run Zeiger nun mit next_ auf sich selbst zeigt, funktioniert der Code in der delete Funktion mit dem Kopf der Liste ebenso wie mit einem Element in der Liste.
bool deleteItem(data_t data) { singleLinkedNode<data_t> *del, *temp; temp = findItemBefore(data); del = temp->next_; if(del == NULL) return false; temp->next_ = del->next_; if (current_ == del) current_ = temp; delete del; return true; }
Wir holen uns einen Zeiger auf das elment vor dem zu löschendem Element. Danach holen wir uns den Zeiger auf das zu löschende Element. Ist dieser NULL wurde das Element nicht gefunden und wir springen mit false aus der Funktion. Wenn nicht biegen wir die Zeiger um. Ist dass zu löschende Element dass current/end Element, schieben wir current_ um einen Position nach vorne. Zum Schluss wir der Speicher des zulöschenden Elements mit delete freigegeben.
Löschen in der Liste
FRAGE AN ALLE
Soll ich nen rekursiven Aufbau von Listen auch machen? So was in der Art wie
hier meine ich.template <typename data_> class MyList { public: MyList(data_ data, MyList<data_>* Tail = NULL) : head(data), tail(Tail){} MyList<data_>& insert(MyList<data_>& Tail) { tail = &Tail; return *this; } void Output() { MyList<data_>* run = this; while(run != NULL) { std::cout << run->head << std::endl; run = run->tail; } } data_ head; MyList* tail; }; int main() { MyList<int> a(0); MyList<int> b(1); MyList<int> c(2); a.insert(b.insert(c)); //a.Output(); MyList<int> d(3, &a); d.Output(); std::cin.get(); return 0; }
Typlisten werden ja "ähnlich" aufgebaut...
FRAGE ENDE
-
Inhaltsverzeichnis
- Die einfach verkettete Liste
- Die doppelt verkettete Liste
- Typlisten
- Anwendungsgebiete
1 Die einfachverkettete Liste
Eine Liste besteht zunächst einmal aus Knoten die miteinander verknüpft werden. Eine Knotendefinition könnte so aussehen.
template <typename data_t> class singleLinkedNode { public: singleLinkedNode() : next_(0) {} singleLinkedNode(data_t data) : next_(0), data_(data) {} singleLinkedNode<data_t>* next_; data_t data_; };
Wie man hier sieht, gibt es nur einen Next Zeiger. Das bedeutet dass der Knoten seinen Nachfolger, nicht aber seinen Vorgänger kennt.
Die Liste die diese Knotenstruktur verwaltet definiert einen head Zeiger sowie einen current/end Zeiger. Diese beiden Zeiger erlauben ein schnelles Einfügen am Ende bzw. am Anfang der Liste.
template <typename data_t> class singleLinkedList { public: // Constructor for the list singleLinkedList() { head_ = 0; current_ = 0; length_ = 0; } private: singleLinkedNode<data_t>* head_; singleLinkedNode<data_t>* current_; int length_; };
Der Konstruktor der Liste setzt die beiden Element auf Null.
Als nächstes benötigen wir eine insert Funktion. Die Funktion fügt Elemente am Ende der Liste ein.
// Construtor // ... // ... // insert something into list void insertItem(data_t data) { if(head_== 0) { // list is empty // insert first element and point head and current to the new element head_ = new singleLinkedNode<data_t>(data); current_ = &(*head_); } else { // something is in the list // insert a element at the end singleLinkedNode<data_t>* temp = new singleLinkedNode<data_t>(data); current_->next_ = temp; current_ = temp; /*** if you want to insert the element at the beginning do it like this head = temp; temp->next = data; ***/ } ++length_; // private Sachen
Die Funktion prüft ab ob die Liste leer ist. Wenn dass der Fall ist, wird ein neues Element in die Liste eingefügt, head_ wird auf das neue Element gebogen und current_ zeigt ebenfalls auf das Element.
Ist die Liste nicht leer wird ein neues Objekt erzeugt und current_->next_ wird auf das neue Element temp gebogen (Die Verknüpfung). Danach wird der eigentliche current_ Zeiger auf das neue Element gebogen.
Einfügen in eine leere Liste
Der Anfangszustand ist mit (1) im Bild markiert. Nach dem Einfügen sieht es aus wie in Zustand (2).
Einfügen in eine nicht leere Liste.
Der Anfangszustand (1) zeigt eine nicht leere Liste mit 2 Elementen. Zum Einfügen eines neuen Elements verbiegen wir den next Link des current/end Zeiger auf das neue Element und dann den current/end Zeiger selbst. Das verschafft uns den Endzustand (2). Zum Einfüge am Beginn der Liste kann man das ganze Spiel auch mit dem Head Zeiger betreiben. Wie dass funktioniert steht im Quelltext.
Soweit so gut. Da man hier aber mit new operiert, muss irgendwann einmal der Speicher aufgeräumt werden. Das erledigen wir im Destruktor der so aussehen könnte.
~singleLinkedList() { singleLinkedNode<data_t>* run, *del; run = head_; while(run != NULL) { del = run; run = run->next_; delete del; } }
Der Destruktor läuft durch die Liste und löscht der Reihe nach ihre Elemente.
Als nächstes wäre es interessant zu sehen ob die ganze Konstruktion überhaupt funktioniert. Dazu schreiben wir eine ListOutput Funktion.
// Konstruktor // insert // Destructor // .. // .. void ListOutput() { singleLinkedNode<data_t>* run = head_; while(run != NULL) { std::cout << run->data_ << std::endl; run = run->next_; } } // private Deklarationen // ...
Im Prinzip funktioniert die Ausgabefunktion wie der Destruktor. Wir laufen wieder durch die Liste. Nur diesmal geben wir die in der Liste vorhanden Daten aus.
Ein mögliches Testprogramm könnte so aussehen.
int main(void) { singleLinkedList<int> list; list.insert(1); list.insert(2); list.ListOutput(); std::cin.get(); return 0; }
Natürlich braucht eine Liste noch mehr Funktionalität. Dazu gehören unter anderem Funktionen wie delete, deleteAt, find und getAt die wir als nächstes Implementieren.
Um die Funktion delete zu implementieren schreiben wir uns zunächst ein Hilfsfunktion die uns das Element vor dem zu Löschenden zurückliefert. Dieses element brauchen wir um die Zeiger nach bzw. vor dem Löschen neu setzen zu können.
private: singleLinkedNode<data_t>* findItemBefore(data_t data, int* countPos = NULL) { int counter = 0; singleLinkedNode<data_t>* run = head_; if(head_->data_ == data) { // Wenn head gesucht ist, head aushängen head_ = head_->next_; run->next_ = run; } else { while(run->next_ != NULL && run->next_->data_ != data) { run = run->next_; ++counter; } if(countPos != NULL) *countPos = counter+1; } return run; }
Zunächst prüfen wir ob head_ das zu löschende Element ist. Wenn das der Fall ist, schieben wir head auf das nächste Element und lassen run->next_ auf run zeigen. Damit ist das erste Element der Liste abgekoppelt und kann gelöscht werden.
Ansonsten laufen wir durch die Liste und schauen ob das nächste Element das gesuchte ist. Finden wir es nicht, steht run auf current_.
Warum liefern wir den current_ Zeiger zurück. Nunja, wir suchen schließlich das Element vor dem zu Löschenden. Und wenn wir dass zu löschende nicht finden löschen wir nichts, da current_->next_ auf NULL zeigt.
Den head_ aus der Liste auskoppeln
Die roten Pfeile verbildlichen das Auskoppeln des Kopfes der Liste. Da der run Zeiger nun mit next_ auf sich selbst zeigt, funktioniert der Code in der delete Funktion mit dem Kopf der Liste ebenso wie mit einem Element in der Liste.
bool deleteItem(data_t data) { singleLinkedNode<data_t> *del, *temp; temp = findItemBefore(data); del = temp->next_; if(del == NULL) return false; temp->next_ = del->next_; if (current_ == del) current_ = temp; delete del; --length_; return true; }
Wir holen uns einen Zeiger auf das elment vor dem zu löschendem Element. Danach holen wir uns den Zeiger auf das zu löschende Element. Ist dieser NULL wurde das Element nicht gefunden und wir springen mit false aus der Funktion. Wenn nicht biegen wir die Zeiger um. Ist dass zu löschende Element dass current/end Element, schieben wir current_ um einen Position nach vorne. Zum Schluss wir der Speicher des zulöschenden Elements mit delete freigegeben.
Löschen in der Liste
Als Letztes beschäftigen wir uns mit den Funktionen zum finden und holen der Elemente aus der Liste.
int findItem(data_t data) { singleLinkedNode<data_t> *finder; int count = 0; finder = findItemBefore(data, &count); finder = finder->next_; return count; }
Die find Funktion bedient sich der findItemBefore Methode um dass Gesuchte Element zu finden. Als Rückgabewerte wird der Index zurückgeliefert. Bei einem nicht auffinden des Elements wird die Länge der Liste zurückgeliefert.
data_t* getItem(int index) { if(index >= length_) return NULL; singleLinkedNode<data_t>* finder = head_; int pos = 0; while(pos != index) { finder = finder->next_; ++pos; } return &finder->data_; }
Die getItem Methode liefert einen Zeiger auf das gefundene Element an der Stelle Index. Sollte der index größer oder gleich der Länge der Liste sein wird NULL zurückgeliefert.
...
FRAGE AN ALLE
Soll ich nen rekursiven Aufbau von Listen auch machen? So was in der Art wie
hier meine ich.template <typename data_> class MyList { public: MyList(data_ data, MyList<data_>* Tail = NULL) : head(data), tail(Tail){} MyList<data_>& insert(MyList<data_>& Tail) { tail = &Tail; return *this; } void Output() { MyList<data_>* run = this; while(run != NULL) { std::cout << run->head << std::endl; run = run->tail; } } data_ head; MyList* tail; }; int main() { MyList<int> a(0); MyList<int> b(1); MyList<int> c(2); a.insert(b.insert(c)); //a.Output(); MyList<int> d(3, &a); d.Output(); std::cin.get(); return 0; }
Typlisten werden ja "ähnlich" aufgebaut...
FRAGE ENDE
-
Inhaltsverzeichnis
- Die einfach verkettete Liste
- Die doppelt verkettete Liste
- Typlisten
- Anwendungsgebiete
1 Die einfachverkettete Liste
Eine Liste besteht zunächst einmal aus Knoten die miteinander verknüpft werden. Eine Knotendefinition könnte so aussehen.
template <typename data_t> class singleLinkedNode { public: singleLinkedNode() : next_(0) {} singleLinkedNode(data_t data) : next_(0), data_(data) {} singleLinkedNode<data_t>* next_; data_t data_; };
Wie man hier sieht, gibt es nur einen Next Zeiger. Das bedeutet dass der Knoten seinen Nachfolger, nicht aber seinen Vorgänger kennt.
Die Liste die diese Knotenstruktur verwaltet definiert einen head Zeiger sowie einen current/end Zeiger. Diese beiden Zeiger erlauben ein schnelles Einfügen am Ende bzw. am Anfang der Liste.
template <typename data_t> class singleLinkedList { public: // Constructor for the list singleLinkedList() { head_ = 0; current_ = 0; length_ = 0; } private: singleLinkedNode<data_t>* head_; singleLinkedNode<data_t>* current_; int length_; };
Der Konstruktor der Liste setzt die beiden Element auf Null.
Als nächstes benötigen wir eine insert Funktion. Die Funktion fügt Elemente am Ende der Liste ein.
// Construtor // ... // ... // insert something into list void insertItem(data_t data) { if(head_== 0) { // list is empty // insert first element and point head and current to the new element head_ = new singleLinkedNode<data_t>(data); current_ = &(*head_); } else { // something is in the list // insert a element at the end singleLinkedNode<data_t>* temp = new singleLinkedNode<data_t>(data); current_->next_ = temp; current_ = temp; } ++length_; // private Sachen
Die Funktion prüft ab ob die Liste leer ist. Wenn dass der Fall ist, wird ein neues Element in die Liste eingefügt, head_ wird auf das neue Element gebogen und current_ zeigt ebenfalls auf das Element.
Ist die Liste nicht leer wird ein neues Objekt erzeugt und current_->next_ wird auf das neue Element temp gebogen (Die Verknüpfung). Danach wird der eigentliche current_ Zeiger auf das neue Element gebogen.
Einfügen in eine leere Liste
Der Anfangszustand ist mit (1) im Bild markiert. Nach dem Einfügen sieht es aus wie in Zustand (2).
Einfügen in eine nicht leere Liste.
Der Anfangszustand (1) zeigt eine nicht leere Liste mit 2 Elementen. Zum Einfügen eines neuen Elements verbiegen wir den next Link des current/end Zeiger auf das neue Element und dann den current/end Zeiger selbst. Das verschafft uns den Endzustand (2). Zum Einfüge am Beginn der Liste kann man das ganze Spiel auch mit dem Head Zeiger betreiben.
Soweit so gut. Da man hier aber mit new operiert, muss irgendwann einmal der Speicher aufgeräumt werden. Das erledigen wir im Destruktor der so aussehen könnte.
~singleLinkedList() { singleLinkedNode<data_t>* run, *del; run = head_; while(run != NULL) { del = run; run = run->next_; delete del; } }
Der Destruktor läuft durch die Liste und löscht der Reihe nach ihre Elemente.
Als nächstes wäre es interessant zu sehen ob die ganze Konstruktion überhaupt funktioniert. Dazu schreiben wir eine ListOutput Funktion.
// Konstruktor // insert // Destructor // .. // .. void ListOutput() { singleLinkedNode<data_t>* run = head_; while(run != NULL) { std::cout << run->data_ << std::endl; run = run->next_; } } // private Deklarationen // ...
Im Prinzip funktioniert die Ausgabefunktion wie der Destruktor. Wir laufen wieder durch die Liste. Nur diesmal geben wir die in der Liste vorhanden Daten aus.
Ein mögliches Testprogramm könnte so aussehen.
int main(void) { singleLinkedList<int> list; list.insert(1); list.insert(2); list.ListOutput(); std::cin.get(); return 0; }
Natürlich braucht eine Liste noch mehr Funktionalität. Dazu gehören unter anderem Funktionen wie delete, deleteAt, find und getAt die wir als nächstes Implementieren.
Um die Funktion delete zu implementieren schreiben wir uns zunächst ein Hilfsfunktion die uns das Element vor dem zu Löschenden zurückliefert. Dieses element brauchen wir um die Zeiger nach bzw. vor dem Löschen neu setzen zu können.
private: singleLinkedNode<data_t>* findItemBefore(data_t data, int* countPos = NULL) { int counter = 0; singleLinkedNode<data_t>* run = head_; if(head_->data_ == data) { // Wenn head gesucht ist, head aushängen head_ = head_->next_; run->next_ = run; } else { while(run->next_ != NULL && run->next_->data_ != data) { run = run->next_; ++counter; } if(countPos != NULL) *countPos = counter+1; } return run; }
Zunächst prüfen wir ob head_ das zu löschende Element ist. Wenn das der Fall ist, schieben wir head auf das nächste Element und lassen run->next_ auf run zeigen. Damit ist das erste Element der Liste abgekoppelt und kann gelöscht werden.
Ansonsten laufen wir durch die Liste und schauen ob das nächste Element das gesuchte ist. Finden wir es nicht, steht run auf current_.
Warum liefern wir den current_ Zeiger zurück. Nunja, wir suchen schließlich das Element vor dem zu Löschenden. Und wenn wir dass zu löschende nicht finden löschen wir nichts, da current_->next_ auf NULL zeigt.
Den head_ aus der Liste auskoppeln
Die roten Pfeile verbildlichen das Auskoppeln des Kopfes der Liste. Da der run Zeiger nun mit next_ auf sich selbst zeigt, funktioniert der Code in der delete Funktion mit dem Kopf der Liste ebenso wie mit einem Element in der Liste.
bool deleteItem(data_t data) { singleLinkedNode<data_t> *del, *temp; temp = findItemBefore(data); del = temp->next_; if(del == NULL) return false; temp->next_ = del->next_; if (current_ == del) current_ = temp; delete del; --length_; return true; }
Wir holen uns einen Zeiger auf das elment vor dem zu löschendem Element. Danach holen wir uns den Zeiger auf das zu löschende Element. Ist dieser NULL wurde das Element nicht gefunden und wir springen mit false aus der Funktion. Wenn nicht biegen wir die Zeiger um. Ist dass zu löschende Element dass current/end Element, schieben wir current_ um einen Position nach vorne. Zum Schluss wir der Speicher des zulöschenden Elements mit delete freigegeben.
Löschen in der Liste
Als Letztes beschäftigen wir uns mit den Funktionen zum finden und holen der Elemente aus der Liste.
int findItem(data_t data) { singleLinkedNode<data_t> *finder = head_; int count = 0; while(finder != NULL && finder->data_ != data) { finder = finder->next_; ++count; } return count; }
Als Rückgabewerte wird der Index zurückgeliefert. Bei einem nicht auffinden des Elements wird die Länge der Liste zurückgeliefert.
data_t* getItem(int index) { if(index >= length_) return NULL; singleLinkedNode<data_t>* finder = head_; int pos = 0; while(pos != index) { finder = finder->next_; ++pos; } return &finder->data_; }
Die getItem Methode liefert einen Zeiger auf das gefundene Element an der Stelle Index. Sollte der index größer oder gleich der Länge der Liste sein wird NULL zurückgeliefert.
Doppelt verkettete Listen
Wenn man zu den einfach verketteten Listen einen Zeiger auf das letzte Element hinzufügt, erhält man doppelt verkettete Listen.
[bild]
Die Definiton eines Knoten könnte so aussehen.
template <typename data_t> class doubleLinkedNode { public: doubleLinkedNode() : next_(0), back_(0){} doubleLinkedNode(data_t data) : next_(0), back_(0), data_(data) {} doubleLinkedNode<data_t> *next_, *back_; data_t data_; };
Sie unterscheidet sich von der einfach verketteten Liste durch den back_ Zeiger auf das vorherige Element.
Die Definition der Liste ist wieder die gleiche wie bei der einfach verktetteten Liste.
template<typename data_t> class doubleLinkedList { public: doubleLinkedList() { head_ = 0; current_ = 0; length_ = 0; } private: doubleLinkedNode<data_t>* head_; doubleLinkedNode<data_t>* current_; int length_;
Auch das einfügen in die Liste funktioniert ähnlich wie in die einfach verkettete Liste.
void insertItem(data_t data) { if(head_ == 0) { // list is empty // insert first element and point head and current to the new element head_ = new doubleLinkedNode<data_t>(data); current_ = head_; } else { // something is in the list // insert a element at the end doubleLinkedNode<data_t>* temp = new doubleLinkedNode<data_t>(data); current_->next_ = temp; temp->back_ = current_; current_ = temp; } ++length_; }
Nur dass wir hier den back_ Zeiger noch mit verwalten müssen. Wir fügen wie schon bei der einfach verketteten Liste am Ende der Liste ein.
Die Zerstörung der Elemente im Destruktor läuft wieder wie in der einfach verketteten Liste.
~doubleLinkedList() { doubleLinkedNode<data_t>* run, *del; run = head_; while(run != NULL) { del = run; run = run->next_; delete del; } }
Die findItemBefore Methode brauchen wir hier nicht, da wir nun eine Rückwärtszeiger haben.
Daher machen wir uns gleich an die normale findItem Methode.
...
FRAGE AN ALLE
Soll ich nen rekursiven Aufbau von Listen auch machen? So was in der Art wie
hier meine ich.template <typename data_> class MyList { public: MyList(data_ data, MyList<data_>* Tail = NULL) : head(data), tail(Tail){} MyList<data_>& insert(MyList<data_>& Tail) { tail = &Tail; return *this; } void Output() { MyList<data_>* run = this; while(run != NULL) { std::cout << run->head << std::endl; run = run->tail; } } data_ head; MyList* tail; }; int main() { MyList<int> a(0); MyList<int> b(1); MyList<int> c(2); a.insert(b.insert(c)); //a.Output(); MyList<int> d(3, &a); d.Output(); std::cin.get(); return 0; }
Typlisten werden ja "ähnlich" aufgebaut...
FRAGE ENDE
-
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.
-