1.2 Liste erstellen Informatik I - Übung 11 Liste, Queue und Stack Nehmen wir an, wir wollen eine Liste von 3 Knoten erstellen. Wir müssen dafür zuerst die Liste initialisieren und dann mit Hilfe einer Schleife die erstellten Knoten mit den gewünschten Werten füllen : Daniel Hentzen 1 [email protected] 2 3 14. Mai 2014 4 Node ∗ head ; // head p o i n t e r e r s t e l l e n Node ∗ newNode ; // p o i n t e r a u f neuen Knoten e r s t e l l e n i n t num ; // Wert head = NULL ; // head z e i g t am Anfang a u f N u l l e l e m e n t , w e i l noch ←k e i n Knoten e x i s t i e r t 5 6 7 8 9 1 Linked Lists 10 11 Eine Liste ist eine Ansammlung von verketteten Knoten. Jeder Knoten speichert zwei Komponenten : einen Wert und die Adresse (Pointer) des nächsten Knotens. 12 13 14 Zu jeder Liste gehört ein “head”-pointer. Dieser soll auf den ersten Knoten der Liste zeigen (= dessen Adresse speichern). 15 Der Pointer des letzten Knotens speichert die Adresse “NULL”, zeigt also auf das Nullelement. Dies zeigt das Ende der Liste an. f o r ( i n t i = 0 ; i < 3 ; i++) // 3 Knoten { cout << ” Enter number : ” ; cin >> num ; newNode = new Node ; newNode−>data = num ; newNode−>next = head ; head = newNode ; } Erklärung : • Zeile 11 : Mit new weisen wir Speicherplatz für einen neuen Knoten zu und speichern dessen Adresse im newNode pointer den wir in Zeile 2 erstellt haben. • Zeile 12 : Jetzt initialisieren wir die data-Komponente des Knotens auf den eingelesenen Wert. Bem. : Da newNode ein pointer auf ein Node ist, können wir nicht einfach mit dem . Operator auf die member-Variablen zugreifen. Stattdessen benutzen wir entweder (∗newNode).data (Dereferenzierung und dann Zugriff) oder den −> Operator, der Dereferenzierung und Zugriff in sich vereint. 1.1 Implementierung der Knoten • Zeile 13 : Wir übergeben die Adresse des nächsten Knotens an die next-Komponente des neuen Knotens. Da wir diesen vorne “einfädeln”, entspricht diese Adresse immer dem aktuellen head-Pointer. Die Knoten werden als structs implementiert : s t r u c t nodeType { i n t data ; nodeType ∗ next ; }; t y p e d e f s t r u c t nodeType Node ; // Node = nodeType ( a l i a s ) • Zeile 14 : Der head pointer zeigt im Moment auf das zweite Element, da wir ja ein neues “eingefädelt” haben. Darum setzen wir den head pointer wieder auf das erste Element, also auf den neuen Knoten. 1 Um einen neuen Knoten einzusetzen, benötigen wir einen pointer (current) auf den Knoten vor dem einzusetzenden Knoten. 1.3 Liste durchlaufen 1 1 2 3 4 5 6 7 2 Node ∗ current ; current = head ; // S t a r t w h i l e ( current != NULL ) { cout << current−>data << ” ” ; current = current−>next ; // n a e c h s t e r Knoten } 3 4 // n e u e r Knoten : Node ∗ newNode ; newNode = new Node ; newNode−>data = 3 7 ; 5 6 7 8 // E i n s e t z e n : newNode−>next = current−>next ; current−>next = newNode ; Erklärung : Erklärung : • Zeile 1 : Wir brauchen einen pointer der von Knoten zu Knoten springt. Wir nennen ihn current. • Zeile 2,3 : Speicherplatz für neuen Knoten zuweisen und Adresse im newNode pointer speichern. • Zeile 2 : Wir starten beim ersten Element, current muss also am Anfang dessen Adresse speichern. • Zeile 7 : Der next-Teil des neuen Knotens soll die Adresse des Knotens nach current speichern. • Zeile 3 : Wir haben gesehen dass die next-Komponente des letzten Knotens der Liste auf das Nullelement zeigt. Sobald das next des current-Knoten also NULL ist, sind wir beim letzten Knoten angekommen und wir brechen die Schleife ab. • Zeile 8 : Der next-Teil des current Knotens soll die Adresse des neuen Knotens speichern. Achtung auf Reihenfolge ! • Zeile 6 : Der current pointer wird auf den nächsten Knoten gesetzt, dessen Adresse im aktuellen Knoten gespeichert ist. 1.6 Knoten löschen 1.4 Element finden Node ∗ current ; current = head ; // S t a r t i n t element = 1 2 ; // Element , das w i r suchen w h i l e ( current != NULL ) { i f ( current−>data == element ) b r e a k ; // S c h l e i f e wird a b g e b r o c h e n Wir brauchen wieder einen current pointer auf den Knoten vor dem zu löschenden Knoten. Ausserdem brauchen wir einen pointer auf den zu löschenden Knoten selbst. current = current−>next ; // n a e c h s t e r Knoten } // R e s u l t a t : c u r r e n t z e i g t a u f g e f u n d e n e s Element 1 2 3 4 Node ∗ deleteNode ; deleteNode = current−>next ; current−>next = deleteNode−>next ; d e l e t e deleteNode ; Erklärung : • Zeile 2 : deleteNode soll auf den zu löschenden Knoten zeigen, muss also die Adresse vom Knoten hinter dem current-Knoten speichern. 1.5 Neuen Knoten einsetzen • Zeile 3 : Die next Komponente des current Knotens muss jetzt die Adresse des übernächsten Knotens speichern, das heisst muss gleich dem next des zu löschenden Knotens sein. • Zeile 4 : Erst jetzt können wir das Objekt mit delete löschen. 2 2.2 Element hinzufügen am Ende (einreihen) 1.7 Doubly linked Lists Hier hat jeder Knoten zwei pointers : einen next und einen previous pointer. 1 2 3 4 5 6 Node ∗ v = new Node ; s t r u c t nodeType { i n t data ; nodeType ∗ next ; nodeType ∗ prev ; }; t y p e d e f s t r u c t nodeType dNode ; tail−>next = v ; tail = v ; v−>next = NULL ; 2.3 Element löschen am Anfang 2 Queues Eine Queue (“Schlange”) ist eine Liste, in der man neue Knoten hinten anhängt und Knoten vorne löscht. Dieses Prinzip nennt man “First In First Out” (FIFO). Zusätzlich zum head pointer gibt es hier auch noch einen tail pointer um den letzten Knoten zu bezeichnen. Node ∗ v = head ; head = head−>next ; delete v ; 3 Stacks Stacks sind “one-sided” queues (Stapel). Man kann nur am Anfang hinzufügen (=push) und am Anfang löschen (=pop). Dieses Prinzip nennt man ”Last In First Out”(LIFO). 2.1 Implementierung der Knoten s t r u c t nodeType { i n t data ; nodeType ∗ next ; }; t y p e d e f s t r u c t nodeType Node ; Node ∗ head = NULL ; Node ∗ tail = NULL ; 3 3.1 Knoten hinzufügen : push Node ∗ v = new Node ; v−>next = head ; head = v ; 3.2 Knoten entfernen : pop Node ∗ v = head ; head = head−>next ; delete v ; 4