Datenstrukturen Mariano Zelke Sommersemester 2012 Kapitel 3: Elementare Datenstrukturen Mariano Zelke Datenstrukturen 2/18 Einfach verkettete Listen Eine Zeiger-Implementierung von einfach verketteten Listen, also Listen mit Vorwärtszeigern. //Deklarationsdatei liste.h für einfach verkettete Listen. enum boolean {False, True}; class liste{ private: typedef struct Element { int data; Element *next; }; //Kann auch anderer Datentyp sein Element *head, *current; public: liste( ) // Konstruktor { head = new Element; current = head; head->next = 0; } Mariano Zelke Datenstrukturen 3/18 Public: Die weiteren Operationen I I I I I I I I I void insert(int data); // Ein neues Element mit Wert data // wird nach dem gegenwärtigen Element eingefügt. Der Wert von // current ist unverändert. void remove( ); // Das dem gegenwärtigen Element folgende // Element wird entfernt. Der Wert von current ist unverändert. void next( ); // Das nächste Element wird aufgesucht. Dazu // wird current um eine Position nach rechts bewegt. void movetofront( ); // current erhält den Wert head. boolean end( ); // Zeigt current auf das letzte Element? boolean empty( ); // Ist die Liste leer? int read( ); // Gib das Feld data des nächsten Elements aus. void write(int wert); // Überschreibe das Feld data des // nächsten Elements mit der Zahl wert. void search(int wert); // Suche rechts von der gegenwärtigen // Zelle nach der ersten Zelle z mit Datenfeld wert. Der Zeiger // current wird auf den Vorgänger von z zeigen. } Mariano Zelke Datenstrukturen 4/18 Eigenschaften I Jede Liste besitzt stets eine leere Kopfzelle: liste() erzeugt diese Zelle, nachfolgende Einfügungen können nur nach der Kopfzelle durchgeführt werden. I Alle Operationen können leicht in konstanter Zeit O(1) unterstützt werden, nur die Funktion search benötigt möglicherweise Zeit proportional zur Länge der Liste. The good and the bad Die Größe der Liste ist proportional zur Anzahl der gespeicherten Elemente: Die Liste passt sich der darzustellenden Menge an. Die Suche dauert viel zu lange. Mariano Zelke Datenstrukturen 5/18 Die Addition dünn besetzter Matrizen A und B sind Matrizen mit n Zeilen und m Spalten. Berechne die Summe C = A + B. I Das Programm for (i=0 ; i < n; i++) for (j=0 ; j < m; j++) C[i,j] = A[i,j] + B[i,j]; bestimmt C in Zeit O(n · m). I Ziel: Bestimme C in Zeit O(a + b), wobei a und b die Anzahl der von Null verschiedenen Einträge von A und B ist. Mariano Zelke Datenstrukturen 6/18 Listendarstellung von Matrizen I Stelle A und B durch einfach verkettete Listen LA und LB in Zeilenordnung dar. Jedes Listenelement speichert neben dem Wert eines Eintrags auch die Zeile und die Spalte des Eintrags. 0 9 0 Die Liste LA in Zeilenordnung für A = 7 0 5 ist 0 0 6 *head I data: *next Mariano Zelke data: (1,2,9) *next data: (2,1,7) *next data: (2,3,5) *next Datenstrukturen data: (3,3,6) *next 7/18 Der Algorithmus (1) Beginne jeweils am Anfang der Listen LA und LB . (2) Solange beide Listen nicht leer sind, wiederhole (a) das gegenwärtige Listenelement von LA (bzw. LB ) habe die Koordinaten (iA , jA ) (bzw. (iB , jB )). (b) Wenn iA < iB (bzw. iA > iB ), dann füge das gegenwärtige Listenelement von LA (bzw. LB ) in die Liste LC ein und gehe zum nächsten Listenelement von LA (bzw. LB ). (c) Wenn iA = iB und jA < jB (bzw. jA > jB ), dann füge das gegenwärtige Listenelement von LA (bzw. LB ) in die Liste LC ein und gehe zum nächsten Listenelement von LA (bzw. LB ). (d) Wenn iA = iB und jA = jB , dann addiere die beiden Einträge und füge die Summe in die Liste LC ein. Die Zeiger in beiden Listen werden nach rechts bewegt. (3) Wenn die Liste LA (bzw. LB ) leer ist, kann der Rest der Liste LB (bzw. LA ) an die Liste LC angehängt werden. Mariano Zelke Datenstrukturen 8/18 Beispiel 0 4 0 0 0 0 A = 0 0 2 , B = 7 0 5 , Berechne C = A + B 1 0 0 0 0 0 *current *head Listendarstellung von A : data: *next data: (1,2,4) *next data: *next data: (2,1,7) *next data: (2,3,5) *next *current *head Listendarstellung von C : data: *next Erzeugt in Schritt Mariano Zelke data: (3,1,1) *next *current *head Listendarstellung von B : data: (2,3,2) *next data: (1,2,4) *next (2)(b) data: (2,1,7) *next data: (2,3,7) *next data: (3,1,1) *next (2)(c) (2)(d) (3) Datenstrukturen 9/18 Deques, Stacks und Queues I Der abstrakte Datentyp Stack unterstützt die Operationen I I I Anwendung: nicht-rekursive Implementierung rekursiver Programme, Druckerwarteschlange, pipe, etc. Eine Queue modelliert das Konzept einer Warteschlange und unterstützt die Operationen I I I push : Füge einen Schlüssel ein. pop : Entferne den jüngsten Schlüssel. enqueue : Füge einen Schlüssel ein. dequeue: Entferne den ältesten Schlüssel. Stacks und Queues werden von einem Deque (Double-ended queue) verallgemeinert. Die folgenden Operationen werden unterstützt: I I I I insertleft : Füge einen Schlüssel am linken Ende ein. insertright : Füge einen Schlüssel am rechten Ende ein. removeleft : Entferne den Schlüssel am linken Ende. removeright : Entferne den Schlüssel am rechten Ende. Alle drei Strukturen Stack, Queue und Deque unterstützen zusätzlich die Operation empty, die jeweils auf Leerheit testet. Mariano Zelke Datenstrukturen 10/18 Die Implementierung eines Deque I I I Benutze ein an beiden Enden zusammengeschweißtes“ 1-dimen” sionales Array. Der Inhalt des Deques wird jetzt entsprechend den Operationen über den entstandenen Ring“ geschoben. ” Benutze zwei Positionsvariable left und right: left steht jeweils vor dem linken Ende und right jeweils auf dem rechten Ende. Forderung: Die Zelle mit Index left ist stets leer und anfänglich ist left = right. Beispiel: Array der Länge 4: nach insertleft(1): Anfangs: nach insertleft(2): left right 1 right nach removeright(): 2 1 right nach removeright(): left left 2 left right left right Mariano Zelke Datenstrukturen 11/18 Eine Implementierung von Listen durch Arrays Eine einfach verkettete Liste bestehe aus Zellen mit einem Integer-Datenfeld und einem Vorwärtszeiger. Zu keinem Zeitpunkt möge die Liste mehr als n Elemente besitzen. I Daten und Zeiger seien Arrays der Größe n. I Wenn ein Listenelement den Index i erhält“, dann ist Daten[i] der ” Wert des Listenelements und Zeiger[i] der Index des rechten Nachbarn. Für die Speicherverwaltung benutze eine zusätzliche Datenstruktur Frei, die die Menge der freien Indizes verwaltet. I Zu Anfang ist Frei = {0, . . . , n − 1}. Mariano Zelke Datenstrukturen 12/18 Beispiel Die einfach verkettete Liste *head data: *next data: 4 *next data: 9 *next data: 3 *next kann dargestellt werden durch Daten 0 0 9 0 0 3 0 4 Zeiger 7 0 5 0 0 -1 0 2 Frei: Mariano Zelke {1,3,4,6} Datenstrukturen 13/18 Eine Implementierung von Listen durch Arrays (cont.) I Wenn ein Element mit Wert w nach einem Element mit Index i einzufügen ist: Wenn Frei nicht-leer ist, dann entnimm einen freien Index j daraus und setze Daten[j] := w , Zeiger[j] := Zeiger[i] und Zeiger[i] := j. I Wenn ein Element zu entfernen ist, dessen Vorgänger den Index i besitzt: Füge j := Zeiger[i] in die Datenstruktur Frei ein und setze Zeiger[i] := Zeiger[j]. I Welche Datenstruktur ist für Frei zu wählen? Jede Datenstruktur, die es erlaubt, Elemente einzufügen und zu entfernen, tut’s: Ein Stack, eine Queue oder ein Deque ist OK. Mariano Zelke Datenstrukturen 14/18 Beispiel Füge Listenelement ein: *head data: *next data: 4 *next data: 9 *next Daten 0 0 9 0 8 3 0 4 Zeiger 7 0 4 0 5 -1 0 2 Frei: Mariano Zelke data: 8 *next data: 3 *next {1,3,4,6} Datenstrukturen 15/18 Beispiel Entferne Listenelement: *head data: *next data: 4 *next data: 9 *next Daten 0 0 9 0 8 3 0 4 Zeiger 7 0 4 0 5 -1 0 4 Frei: Mariano Zelke data: 8 *next data: 3 *next {1,3,6,2} Datenstrukturen 15/18 Bäume (Link) Bäume: Hilfsmittel bei der hierarchischen Strukturierung von Daten. I Bäume als konzeptionelle Hilfsmittel für I I I I von einem Benutzer angelegte Verzeichnisse, für die Veranschaulichung rekursiver Programme, ... Bäume als Datenstrukturen zur Unterstützung von Algorithmen: I I I I Mariano Zelke in der Auswertung von arithmetischen Ausdrücken, in der Syntaxerkennung mit Hilfe von Syntaxbäumen als Datenstrukturen zur Lösung von Suchproblemen, ... Datenstrukturen 16/18 Was ist ein Baum? Ein Baum T wird durch eine Knotenmenge V und eine Kantenmenge E ⊆ V × V dargestellt. Eine gerichtete Kante (i, j) führt von i nach j. Wann ist T ein Baum? I T muss genau einen Knoten r besitzen, in den keine Kante hineinführt. r heißt die Wurzel von T . I In jeden Knoten darf höchstens eine Kante hineinführen, I und jeder Knoten muss von der Wurzel aus erreichbar sein. Eine disjunkte Vereinigung von Bäumen heißt Wald. Mariano Zelke Datenstrukturen 17/18 Zentrale Begriffe I Wenn (v , w ) eine Kante ist, dann nennen wir v den Vater von w und sagen, dass w ein Kind von v ist. I I Ausgangsgrad (v ) ist die Anzahl der Kinder von v . I I I Einen Knoten b mit Ausgangsgrad (b) = 0 bezeichnen wir als Blatt. T heißt k-när, falls der Ausgangsgrad aller Knoten höchstens k ist. Für k = 2 sprechen wir von binären Bäumen. Ein Weg von v nach w ist eine Folge (v0 , . . . , vm ) von Knoten mit v0 = v , vm = w und (vi , vi+1 ) ∈ E für alle i (0 ≤ i < m). I I I I I Die anderen Kinder von v heißen Geschwister von w . v ist ein Vorfahre von w und w ein Nachfahre von v . Die Länge des Weges ist m, die Anzahl der Kanten des Weges. Tiefe(v ) ist die Länge des (eindeutigen) Weges von r nach v . Höhe(v ) ist die Länge des längsten Weges von v zu einem Blatt. T heißt geordnet, falls für jeden Knoten eine Reihenfolge der Kinder vorliegt. Mariano Zelke Datenstrukturen 18/18