11. Elementare Datenstrukturen Definition 11.1: Eine dynamische Menge ist gegeben durch eine oder mehrer Mengen von Objekten sowie Operationen auf diesen Mengen und den Objekten der Mengen. Dynamische Mengen werden auch abstrakte Datentypen (ADT) genannt. Definition 11.2: 1. Werden auf einer Menge von Objekten die Operationen Einfügen, Entfernen und Suchen betrachtet, so spricht man von einem Wörterbuch. 2. Werden auf einer Menge von Objekten die Operationen Einfügen, Entfernen des Maximums und Suchen des Maximums betrachtet, so spricht man von einer Prioritätswarteschlange. 1
Datenstrukturen Definition 11.3: Eine Datenstrukur ist eine Implementierung einer dynamischen Menge, so dass die für die dynamische Menge definierten Operationen effizient unterstützt werden. 2
Objekte in dynamischen Mengen (1) Objekte bestehen aus verschiedenen Feldern. Ein Feld speichert den Schlüsseln des Objekts. Identifikation eines Objekts erfolgt über Schlüssel. Schlüssel von Objekten sind nicht notwendig paarweise verschieden. Falls Schlüssel paarweise verschieden sind, ist Menge von Objekten identisch zur Menge von Schlüsseln. 3
Objekte in dynamischen Mengen (2) Weitere Felder relevant für Datenstruktur, z.b. Felder für Referenzen auf andere Objekte. Andere Felder nur relevant für Anwendung, nicht für Datenstruktur die Daten dieser Felder nennt man Satellitendaten. 4
Objekte, Referenzen, Zeiger Zugriff auf Objekte erfolgt in der Regel durch Referenzen oder Verweise auf Objekte wie in Java. In Sprachen wie C und C++ realisiert durch Zeiger, engl. Pointer. Zeiger/Pointer Notation aus Introduction to Algorithms. Verwenden Referenzen, Zeiger, Verweise synonym. Verweise zeigen oder verweisen oder referenzieren auf Objekte. 5
Operationen in dynamischen Mengen Insert(S,x): Füge Objekt x in Menge S ein. Search(S,k): Finde Objekt x mit Schlüssel k. Falls kein solches Objekt vorhanden Ausgabe NIL Delete(S,x): Entferne Objekt x aus Menge S. Minimum(S): Finde Objekt mit minimalem Schlüssel in S (es muss eine Ordnung auf Schlüsseln existieren). Maximum(S): Finde Objekt mit maximalem Schlüssel in S (es muss eine Ordnung auf Schlüsseln existieren). 6
Stacks (Stapel) und Queues (Schlangen) Definition 11.4: 1. Stacks (Stapel) sind eine Datenstruktur, die die dynamische Menge LIFO (last-in-first-out) implementiert. In LIFOs sollen beliebige Objekte eingefügt und das zuletzt eingefügte Objekt entfernt werden können. 2. Queues (Schlangen) sind eine Datenstruktur, die die dynamische Menge FIFO (first-in-first-out) implementiert. In FIFOs sollen beliebige Objekte eingefügt und das am längsten in der Menge befindliche Objekt entfernt werden können. 7
Stacks Einfügen eines Objekts wird bei Stacks Push genannt. Entfernen des zuletzt eingefügten Objekts wird Pop genannt. Zusätzliche Hilfsoperation ist Stack-Empty, die überprüft, ob ein Stack leer ist. Stack mit maximal n Objekten wird realisiert durch ein Array S[1 n] mit einem zusätzlichen Feld top[s], das den Index des zuletzt eingefügten Objekts speichert. 8
Stack - Beispiel Objekte sind hier natürliche Zahlen. Stack kann dann wie folgt aussehen: 15 6 2 9 top[ S] = 4 Achtung: Bei den folgenden Implementierungen findet keine Überprüfung auf Stack-Überlauf statt! 9
Stack - Operationen ( S) Stack - Empty 1. if top[ S] = 0 2. then return TRUE 3. else return FALSE Push 1. 2. ( S,x) top[ S] top[ S] S[ top[ S ] x + 1 Pop 1. if 2. 3. 4. ( S) Stack Empty then error "underflow" else top S return [ ] top[ S] 1 S[ top[ S] + 1] Satz 11.5: Mit Stacks können die Operationen einer LIFO in Zeit O(1) ausgeführt werden. 10
Illustration der Stackoperationen 15 6 2 9 top[ S] = 4 Nach Push(S,17), Push(S,3): 15 6 2 9 17 3 top[ S] = 6 Nach Pop(S): 15 6 2 9 17 3 top[ S] = 5 11
2. for i Beispiel: Korrekte Klammerung Korrekte - Klammerung 1. Stack - Create( S) 9. 10. if 11. then return true 12. else return false ( A) 1 to length( A) 2. do if A[ i] ist öffnende Klammer 3. then push( S, A[ i]) 4. if A[ i] ist schließende Klammer 5. then if Stack-Empty( S) 6. then return false 7. x pop(s) 8. if x ist nicht vom gleichen Klammertyp wie A[ i] then return false Stack-Empty(S) 12
Queues Einfügen eines Objekts wird Enqueue genannt. Entfernen des am längsten in der Queue befindlichen Objekts wird Dequeue genannt. Queue mit maximal n-1 Objekten wird realisiert durch ein Array Q[1 n] mit zusätzlichen Feldern head[q], tail[q]. head[q]: Position des am längsten in Queue befindlichen Objekts tail[q]: erste freie Position. Alle Indexberechnungen betrachten das Array als kreisförmig. Auf Position n folgt wieder Position 1. 13
Queue - Beispiele 15 6 9 8 4 head [ Q] = 7 tail[ Q] = 12 3 5 15 6 9 8 4 17 tail[ Q] = 3 head[ Q] = 7 14
Queue - Operationen Enqueue 1. Q tail 2. if 3. 4. ( Q,x) [ [ Q ] x tail[ Q] = length[ Q] then tail[ Q] 1 else tail[ Q] tail[ Q] + 1 Dequeue 1. x Q head 2. if head 3. then 4. else 5. return x ( Q) [ [ Q ] [ Q] = length[ Q] head[ Q] 1 head[ Q] head[ Q] + 1 Achtung: Auch hier finden keine Tests auf Über- oder Unterlauf der Queue statt! 15
Illustration Enqueue 15 6 9 8 4 head [ Q] = 7 tail[ Q] = 12 Nach Enqueue(Q,17), Enqueue(Q,3), Enqueue(Q,5): 3 5 15 6 9 8 4 17 tail[ Q] = 3 head[ Q] = 7 16
Illustration Dequeue 3 5 15 6 9 8 4 17 tail[ Q] = 3 head[ Q] = 7 Nach Dequeue: 3 5 15 6 9 8 4 17 tail[ Q] = 3 head[ Q] = 8 17
Laufzeit Queue-Operationen Satz 11.6: Mit Queues können die Operationen einer FIFO in Zeit O(1) ausgeführt werden. 18
Doppelt verkettete Listen (1) Verkettete Listen bestehen aus einer Menge linear angeordneter Objekte. Anordnung realisiert durch Verweise. Unterstützen alle dynamischen Mengen mit den Operationen Insert, Delete, Search, Search-Minimum, usw. Unterstützung ist nicht unbedingt effizient. Objekte in doppelt verketteter Liste L besitzen mindestens drei Felder: key, next, prev. Außerdem Felder für Satellitendaten möglich. Zugriff auf Liste L durch Verweis/Zeiger head[l]. 19
Doppelt verkettete Listen (2) head[l] verweist auf erstes Element der Liste L. x Objekt in Liste L: next[x] verweist auf nächstes Element in Liste, prev[l] verweist auf voriges Element in Liste. prev[x]=nil: x besitzt keinen Vorgänger. Dann ist x erstes Element der Liste und head[l] verweist auf x. next[x]=nil: x besitzt keinen Nachfolger. Dann ist x letztes Element der Liste. head[l]=nil: Liste L ist leer. 20
Varianten verketteter Listen Einfach verkettete Listen: Feld prev nicht vorhanden. Sortierte verkettete Liste: Schlüssel können sortiert werden. Reihenfolge in Liste entspricht sortierter Reihenfolge der Schlüssel. zykisch/kreisförmig verkettete Listen: next des letzten Objekts zeigt auf erstes Objekt. prev des ersten Objekts zeigt auf letztes Objekt. 21
Doppelt verkettete Listen - Beispiel Schlüssel head[l] / 9 16 4 1 / prev next zyklisch doppelt verkette Liste: head[l] 9 16 4 1 22
Einfügen in verkettete Liste List -Insert 1. next 2. if head 3. 4. head 5. prev ( L,x) [ x] head[ L] [ L] NIL then prev[ head[ L ] [ L] x [ x] NIL x Lemma 11.7: List-Insert erfordert Zeit Θ(1). 23
Löschen aus verketteter Liste List 1. if 2. 3. 4. if 5. - Delete prev then else next then ( L,x) [ x] NIL next[ prev[ x ] next[ x] head[ L] next[ x] [ x] NIL prev[ next[ x ] prev[ x] Lemma 11.8: List-Delete erfordert Zeit Θ(1) (falls Zugriff auf zu löschendes Objekt x durch Verweis möglich ist). 24
Illustration Operationen head [L] / 9 16 4 1 / Nach List-Insert(L,x), wobei key [x]=25: head [L] / 25 9 16 4 1 / Nach List-Delete(L,x), wobei key [x]=4: head [L] / 25 9 16 1 / 25
Durchsuchen einer verketteten Liste ( L,k ) [ L] List - Search 1. x head 2. while x NIL key 3. do x next 4. return x [ x] [ x] k Lemma 11.9: List-Search erfordert bei einer Liste mit n Elementen Zeit Θ(n). 26
Vereinfachung durch Sentinels (Wächter) Vermeidung von Abfragen head[l] NIL und x NIL durch Einfügen eines zusätzlichen Objekts nil[l]. nil[l] besitzt drei Felder key, prev, next, aber key[nil[l]]= NIL. next[nil[l]] verweist auf erstes (richtiges) Objekt in Liste. prev[nil[l]] verweist auf letztes Objekt in Liste. Ersetzten in Pseudocode NIL durch nil[l]. head[l] nicht mehr benötigt, ersetzt durch next[nil[l]]. Objekt zur Vereinfachung von Randbedingungen wird Sentinel oder Wächter genannt. 27
Zyklisch verkette Listen mit Sentinel nil [L] nil [L] / 9 16 4 nil [L] 1 / 25 9 16 4 nil [L] / 25 9 16 1 28
Durchsuchen verketteter Liste (mit Sentinel) List - Search 1. x next 2. while x 3. do 4. return x ( L,k ) [ nil[ L ] L] key[ x] x next[ x] k 29
Einfügen in verkettete Liste (mit Sentinel) List -Insert 1. next 2. prev 3. next 4. prev ( L,x) [ x] next[ nil[ L ] [ next[ nil[ L ] x [ nil[ L ] x [ x] nil[ L] 30
Löschen aus verketteter Liste (mit Sentinel) List - Delete 1. next 2. prev ( L,x) [ prev[ x ] next[ x] [ next[ x ] prev[ 31
Binäre Bäume Neben Felder für Schlüssel und Satellitendaten Felder p, left, right. x Knoten: p[x] Verweis auf Elternknoten, left[x] Verweis auf linkes Kind, right[x] Verweis auf rechtes Kind. Falls p[x]=nil, dann ist x Wurzel des Baums. left[x] / right[x]=nil: kein linkes/rechtes Kind. Zugriff auf Baum T durch Verweis root[t] auf Wurzelknoten. 32
Binäre Bäume - Illustration root[t] / / / / / / / / / / / / / 33
Suchen in binären Bäumen (rekursiv) Tree - 1. if x = nil 2. 3. if 4. 5. y 6. if Search Tree - y = nil ( x, k ) then return key[ x] = k then return nil x Search( left[ x], k) 7. then y Tree - Search( right[ x], k) 8. return y 34
Suchen in binären Bäumen (iterativ) Tree - Search - iterativ ( x, k ) 1. Stack - Create( S) 2. push( S,x) 3. while not Stack - Empty( S) 4. do y pop( S) 5. if y nil 6. then if key[ y] = k 7. then return y 8. push( S,right[ y]) 9. push( S,left[ y]) 10. return nil 35
Allgemeine Bäume Darstellung für binäre Bäume auch möglich für k-näre Bäume, k fest. Ersetze left[x]/right[x] durch child1[x],..,childk[x]. Bei Bäumen mit unbeschränktem Grad nicht möglich, oder ineffizient, da Speicher für viele Felder reserviert werden muss. Nutzen linkes-kind/rechtes-geschwister- Darstellung. Feld p für Eltern bleibt. Dann Feld left-child für linkes Kind und Feld right-sibling für rechtes Geschwister. 36
Allgemeine Bäume - Illustration root[t] / / / / / / / / / / 37
Prioritätswarteschlange (priority queue) Definition 11.10: Die dynamische Menge Prioritätswarteschlange (priority queue) unterstützt die Operationen Einfügen von Elementen: Insert(S,x) Bestimmen des Maximums: Maximum(S) Entfernen des Maximums: Extract-Max(S) Optional: Erhöhen des Wertes eines Schlüssels x auf k (k x): Increase-Key(S,x,k) 38
1. Heaps als Prioritätswarteschlangen Mit Max-Heaps können Prioritätswarteschlangen realisiert werden: Heap - Maximum return A[1] ( A) Heap - Extract 1. 2. max A[1] 3. heap-size[a] 4. MaxHeapify( A,1) 5. return A[1] max - Max ( A) A[heap-size[A]] heap-size[a] Lemma 11.11: Heap-Maximum erfordert Zeit Θ(1). Lemma 11.12: Heap-Extract-Max erfordert bei einem Heap mit n Elementen Zeit O(log n). 1 39
Heaps als Prioritätswarteschlangen (2) Heap -Increase - Key ( A, i, k) 1. A[ i] k 2. while i > 1und A[parent(i) ] 3. do A[ i] A[parent( i )] 4. i parent( i) < A[ i] Heap - Insert ( A, k ) 1. heap-size[ A] 2. A[ heap-size[ A]] heap-size[ A] + 1-3.Heap -Increase - Key( A, heap-size[ A], k) Lemma 11.13: Heap-Increase-Key und Heap-Insert erfordern bei einem Heap mit n Elementen Zeit O(log n). 40