Technische Universität München Technische Universität München Kapitel 7 Fortgeschrittene Datenstrukturen 7.1 Baumstrukturen • Bäume sind eine zentrale Datenstruktur: Codebäume, Suchbäume, Syntaxbäume, Entscheidungsbäume, Dateibäume ... • Ein Baum ist eine verallgemeinerte Listenstruktur. • Bäume bestehen aus Knoten unterschiedlichen Typs; die Verbindung zwischen zwei Knoten wird als Kante bezeichnet. • Ein Element (Knoten) hat nicht nur einen Nachfolger (wie im Fall der Liste), sondern eine begrenzte Anzahl von Nachfolgern (Söhne). • In der Informatik: idR Bäume mit oben liegender Wurzel Motivation: • Lineare Liste: – Suchen eines Elements ist schnell O(log n) – Einfügen eines Elements ist langsam O(n) • Verkettete Liste (nicht sortiert): – Suchen eines Elements ist langsam O(n) – Einfügen eines Elements ist schnell O(1) Ziel: Effiziente Datenstrukturen zur Speichern von Elementen – Schnelles Suchen von Elementen: O(log n) – Schnelles Einfügen von Elementen: O(log n) 1 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 2 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München • „Oberster“ Knoten ist die Wurzel des Baums (kein Vorgänger oder Vater) • Eine Folge p0...pk von Knoten, für die gilt: pi+1 ist Sohn von pi, 0 ≤ i < k, heißt Pfad mit der Länge k Beispiel: Bäume zur Speicherung von Datenbank-Einträgen Im Folgenden präzise Definitionen AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 3 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 4 Technische Universität München Technische Universität München Definition • Ein Graph ist ein Tupel G = (E, V) mit – V: Menge von Knoten (Vertices) – E: Menge von Kanten (Edges) (v, w), mit v, w ∈ V • Ein Graph G = (E, V) heißt gerichtet, wenn seine Kanten (v, w) gerichtete Kanten sind. • Für (v, w) ∈ E heißt v Vorgänger von w, und w Nachfolger. • Wenn (v, w) ∈ E eine gerichtete Kante ist, dann ist w adjazent zu v. Rekursive Definition eines Baumes Gegeben sei eine endliche Menge V von n ≥ 0 Knoten. • Ein speziell ausgezeichneter Knoten ist die Wurzel. • Die übrige Knoten werden in m ≥ 0 disjunkte Mengen V1, V2, ..., Vm aufgeteilt. • Jedes Vk definiert ein Menge von Knoten eines Baums Tk = (Ek, Vk) • T1, T2, ..., Tm werden als die Unterbäume der Wurzel bezeichnet. Definition • Ein Baum ist ein zusammenhängender, ungerichteter Graph, der keinen Kreis/Zyklus enthält. 5 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Wald • Ein Wald ist eine Menge von n ≥ 0 disjunkten Bäumen. • Entfernen der Wurzel eines Baums erzeugt einen Wald. T1 T2 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Definition • Die Anzahl der Unterbäume eines Knoten v definiert den Grad deg(v) des Knoten v. • Ein Endknoten oder ein Blatt v ist ein Knoten ohne Nachfahren. • Ein Knoten, der kein Endknoten ist , hat den Grad deg(v) > 0. • Die Tiefe d(v) des Knoten v ist die Länge des Pfades von der Wurzel zum Knoten v. • Die Knoten gleicher Tiefen bilden ein Niveau. • Die Höhe h(v) des Knoten v ist die Länge des längsten Pfades von v zu einem Blatt. • Die Höhe des Baumes T ist die Höhe der Wurzel h(T) = h(Root). Wurzel A Unterbaum 6 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Tm 7 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 8 Technische Universität München Technische Universität München Beispiel 1 Baum mit 7 Knoten • A ist die Wurzel und hat Unterbäume { B } und { C,D,E,F,G } • Der Baum { C,D,E,F,G } hat den Knoten C als seine Wurzel. • C hat die Unterbäume { D },{ E },{ F,G } • C hat den Grad 3, deg(C) = 3 • Die Höhe von C, h(C) = 2 Beispiel 2 A A A B • { B }, { D }, { E } und { G } sind Blätter • F ist der einzige Knoten mit Grad 1 • Der Baum hat die Höhe 3 • Knoten D hat Tiefe 2 C D D E F F B B C C D D E E G G G 9 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 10 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Definition Gegeben sei ein Baum (E, V). Für jeden Knoten v ∈ V sei eine Ordnung ≤ auf den Nachfolgerknoten von v definiert. Dann heißt der Baum geordneter Baum. Beispiele für Binärbäume 1 2 Definition Ein Binärbaum ist ein geordneter Baum und für alle v ∈ V gilt, deg(v) ≤ 2. D.h. kein Knoten hat mehr als 2 Nachfolgerknoten. Beim Binärbaum spricht man vom linken Sohn oder rechten Sohn. AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert F FF 3 6 4 7 5 11 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 8 9 10 12 Technische Universität München Technische Universität München Definition Gegeben sei ein Binärbaum (E, V). Der Baum heißt vollständig wenn jedes Niveau die maximale Anzahl an Knoten enthält. D.h. Jeder Knoten hat einen linken und einen rechten Sohn, oder er ist ein Endknoten Sei T = (E, V) ein vollständiger Binärbaum der Höhe k. Satz: T besitzt 2k+1 – 1 Knoten und 2k Blätter. Beweis Gegeben sei ein vollständiger, binärer Baum der Höhe k. Ein solcher Baum hat: • 2k+1 – 1 Knoten • 2k Blätter 13 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 14 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München 7.2 Repräsentation von Bäumen: Darstellung eines Binärbaums mit einfach verketteten Listen: • Attribut ‚Schlüssel‘: bezeichnet den Wert des Elements Links Rechts • Pro Knoten: Verweise auf linkes u. rechtes Kind Beispiel: Vollständiger Binärbaum Gegeben sei der unten abgebildete vollständige Baum mit der Höhe k = 3. Der Baum besitzt: 15 • 15 Knoten • 8 Blätter 13 1 14 2 9 1 10 2 3 11 4 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 5 12 6 7 3 8 6 4 7 5 15 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 8 9 10 1 2 6 2 3 4 3 0 0 4 0 5 5 0 0 6 7 8 7 0 0 8 9 10 9 0 0 10 0 0 16 Technische Universität München Graphische Repräsentation wurzel 2 3 Zeiger auf die Wurzel des Baums Schlüssel Links 1 Technische Universität München Rechts 6 4 7 5 8 9 10 17 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Darstellung eines Baums mit doppelt verketteten Listen: • Ein Knoten wird als Element einer Liste dargestellt • Jedes Element wird durch Attribute beschrieben: • Das Attribut ‚Schlüssel‘ beschreibt den Wert des Knoten x, schlüssel[x], (z.B., Zahl, Name, Adresse, ...) • Das Attribut wurzel[T] zeigt auf die Wurzel des Baumes • Darstellung eines Binärbaums • Das Attribut ‚p‘ ist ein Verweis auf den Vaterknoten (Parent) im Baum, falls gilt: p[x] = NIL, dann ist x die Wurzel des Baumes • Das Attribut ‚links‘ verweist auf das linke Kind des Knoten x, links[x] • Das Attribut ‚rechts‘ verweist auf das rechte Kind, rechts[x] 18 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Beispiel: Repräsentation in C typedef struct bnode { int key; struct bnode* left; struct bnode* right; } bin_node; if (root==NULL) return ; inorder(root->left); printf("%d ",root->key); inorder(root->right); }//end inorder void insert(bin_node **root, int val){ bin_node *newnode; newnode=(bin_node*)malloc(sizeof(bin_node)); newnode->right=NULL; newnode->left=NULL; if ((*root)==NULL) { *root=newnode; (*root)->key=val; return; } if (val<(*root)->key) insert((&root)->left,val); else insert((&root)->right,val); }//end Vielfältige Varianten sind möglich: Beispielsweise die Verwendung zusätzlicher Zeiger, um auf den Bruder-Knoten (rechts oder links) zu verweisen. AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert void inorder(bin_node *root) { 19 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 20 Technische Universität München Technische Universität München 7.3 Traversieren von Bäumen Gegeben sei ein Baum (E,V). Eine wichtige Operation auf Baumstrukturen ist das Traversieren des Baumes, d.h. das systematische Durchlaufen der Knoten des Baumes. • Es gibt verschiedene Traversierungsalgorithmen, um systematisch alle Knoten eines Baumes zu besuchen. • Ein vollständiger Durchlauf erzeugt eine lineare Anordnung der Knoten. Traversieren in In-Order Reihenfolge • Gegeben sei ein Baum mit der Menge V und der Wurzel R • die Nachfahren seien geordnet v1, v2, ..., vk, k ≥ 0 5 In-Order Reihenfolge: 3 • Durchlaufe den linken Unterbaum. • Besuche Wurzel R. 1 4 6 • Durchlaufe den rechten Unterbaum. 2 Wir unterscheiden 3 Formen des Traversierens: • Pre-Order Reihenfolge (Hauptreihenfolge, prefix-Form) • Post-Order Reihenfolge (Nebenreihenfolge, postfix-Form) • In-Order Reihenfolge 22 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Algorithmus für In-Order – Reihenfolge Annahme: • wurzel[T] ist Zeiger auf den Wurzelknoten des binären Baums, • A ist eine Kellerdatenstruktur, mit push und pop-Operationen Schritt 1 P = wurzel[T] Schritt 2 IF P = NIL, then goto Schritt 4. Schritt 3 push(schlüssel[P]) P = links[P] goto Schritt 2. Schritt 4. if A = leer, then Halt else schlüssel[P] = pop Schritt 5. Print schlüssel[P] P = rechts[P] goto Schritt 2. AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 5 3 1 8 4 2 7 Eigenschaften • alle Knoten im linken Unterbaum mit der Wurzel R haben eine Nummer die kleiner als R ist, • alle Knoten im rechten Teilbaum haben eine Nummer > R. 21 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 8 6 7 Beispiel Gegeben sei der nebenstehende Baum • Traversieren der Knoten des Baums in In-Order Reihenfolge. • Der Durchlauf erfolgt in der B Reihenfolge: CDBEAGHF C D 23 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert A F E G H 24 Technische Universität München Technische Universität München Traversieren in Pre-Order Reihenfolge Beispiel • Gegeben sei ein Baum mit der Menge E und der Wurzel R • die Nachfahren seien geordnet v1, v2, ..., vk , k ≥ 0 1 Pre-Order Reihenfolge: • Besuche Wurzel R. • Durchlaufe den linken Unterbaum. 3 • Durchlaufe den rechten Unterbaum. 2 6 5 7 4 Eigenschaften • Alle Knoten in einem Unterbaum mit der Wurzel R haben eine Nummer, die nicht kleiner als die Nummer der Wurzel R ist. Gegeben sei der nebenstehende Baum • Traversieren der Knoten des Baums in Pre-Order Reihenfolge. • Der Durchlauf erfolgt in der B Reihenfolge: ABCDEFGH C G H 26 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Traversieren in Post-Order Reihenfolge Beispiel • Gegeben sei ein Baum mit der Menge E und der Wurzel R • die Nachfahren seien geordnet v1, v2, ..., vk , k ≥ 0 Gegeben sei der nebenstehende Baum • Traversieren der Knoten des Baums in Post-Order Reihenfolge. B • Der Durchlauf erfolgt in der Reihenfolge: DCEBHGFA C 8 Post-Order Reihenfolge: • Durchlaufe den linken Unterbaum. • Durchlaufe den rechten Unterbaum. 4 • Besuche Wurzel R. 7 3 6 D AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert E D 25 1 F 8 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 2 A A F E G H 5 27 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 28 Technische Universität München Technische Universität München 7.4 Datenstruktur: Hashtabelle Ziel: • Effiziente Datenstruktur für dynamische Mengen mit einfachen Operationen: Insert, Search, Delete Beispiel: Traversieren Algebraischer Ausdrücke Pre-Order: * + a / b c – d * e f In-Order: (a + b / c) * (d – e * f) Post-Order: a b c / + d e f * - * * Motivation: + a – / b d c * e f 29 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 30 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Definition: Eine Hash-Tabelle H ist eine Datenstruktur zur Organisation von Daten. Die Hashtabelle definiert eine Berechnungsvorschrift, wir nennen das eine Hashfunktion h. Die Hashfunktion h ordnet jedem Datum der Tabelle eindeutig eine Speicheradresse zu. Als Basis dafür dient eine weitere Hilfsfunktion, die jedem Datum einen Suchschlüssel-Wert k aus einem definierten Bereich, dem Universum U an Schlüsselwerten, zuordnet. Hashfunktion h: U A, wobei A ein Adressraum ist. Beispiel: • H ist eine verallgemeinerte Feld-Struktur: z.B. Google-Search • Suchen in Hashtabelle in Worst Case wie bei verketteten Listen auch O(n), aber kann auf O(1) reduziert werden! AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 31 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 32 Technische Universität München Technische Universität München 33 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert • ein Platz k in H verweist auf das Element mit dem Schlüssel k in U • Hashfunktion: h: U → {0, … , m – 1} bildet jeden Schlüssel eindeutig auf eine Indexposition in der Tabelle H ab. h(k) wird als Hashwert des Schlüssels k bezeichnet. • Eine solche Hashtabelle ermöglicht den direkten Zugriff. Beispiel: Schlüssel Element H 7.4.1 Direkte Adressierung • Szenario mit relativ kleiner Menge von Schlüsseln U, mit U= {0, … , m – 1}, jeder Schlüssel k ∈ U identifiziert ein Element. • Gegeben sei eine Hashtabelle H[0, … , m – 1] mit m Plätzen. • Jeder Platz (auch Index genannt) in der Tabelle H entspricht eindeutig einem Schlüssel k in der Menge U. Beispiel: 34 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Repräsentation in C Operationen auf Hashtabellen mit direkter Adressierung • Aufgrund der Einfachheit der Datenstruktur ist die Implementierung der Operationen Insert, Search, Delete sehr einfach: Direct-Address-Search(H, k) return H[k] typedef struct _node{ char *name; char *desc; struct _node *next; }node; Beispiel #define HASHSIZE 101 static node* hashtab[HASHSIZE]; Direct-Address-Insert(H, x) H[schlüssel(x)] = x void inithashtab(){ int i; for(i=0;i<HASHSIZE;i++) hashtab[i]=NULL; } Direct-Address-Delete(H, x) H[schlüssel(x)] = NULL unsigned int hash(char *s){ unsigned int h=0; for(;*s;s++) h=*s+h*31; return h%HASHSIZE; } node* lookup(char *n){ unsigned int hi=hash(n); node* np=hashtab[hi]; for(;np!=NULL;np=np->next){ if(!strcmp(np->name,n)) return np; } return NULL; } Nachteil direkter Adressierung? AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 35 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 36 Technische Universität München Technische Universität München • Definition einer Hashfunktion h: U → {0, … , m – 1} • Problem: Es kann zu Kollisionen kommen, d.h. • es kann ki, kj ∈ U geben, für die gilt: ki ≠ kj, aber h(ki) = h(kj) Beispiel: Erkenntnis: • Die Anzahl der Schlüssel aus U, die tatsächlich benötigt und gespeichert werden muss, kann sehr klein sein. Konsequenz: In Hashtabelle mit direkter Adressierung würde sehr viel Speicherplatz verschwendet werden. Lösung: Hashing mit Verkettung H 7.4.2 Hashing mit Verkettung Idee: • Die Menge U der Schlüssel sei groß: |U| = n • Die Hashtabelle H sei wesentlich kleiner: H[0, …, m – 1], wobei gilt: m << n (<< bedeutet „wesentlich kleiner als“) 37 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Notwendig: Techniken zum Umgang mit Kollisionen • Ansätze: • Kollisions-Vermeidung • Kollisions-Erkennung und -Auflösung • Vollständige Kollisionsvermeidung ist nicht möglich! Denn: Hashfunktionen sind idR nicht injektiv. • Aber: • man kann Hashfunktionen so gestalten, dass sie einen Hashwert pseudo-zufällig, aber deterministisch, berechnen. • d.h. eine Kollision kann vorkommen, aber das gezielte Herbeiführen einer Kollision ist sehr aufwändig und schwierig • wofür nützlich? AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 38 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 39 Kollisionsauflösung durch Verkettung Idee: • alle Elemente, die mit der Hashfunktion h auf den gleichen Tabellenplatz abgebildet werden, werden in einer Liste verkettet. • Tabellenplatz j enthält einen Zeiger auf den Kopf der Liste von Elementen k, mit h(k) = j H Beispiel: AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 40 Technische Universität München Technische Universität München Operationen auf der Hashtabelle: Chained-Hash-Insert(H,x) füge x an den Kopf der Liste H[h(schlüssel(x))] ein Fazit: • Einfügen: Laufzeit im Worst-Case nur O(1). • Suchen: Worst-Case ist die Laufzeit O(n), • n ist die Listenlänge. • Eine gute Verteilung der Hashwerte auf die T-Plätze ist wichtig! • Entfernen eines Elementes x bei doppelter Verkettung: O(1). Chained-Hash-Search(H,k) suche in der Liste H[h(k)] nach einem Element mit Schlüssel k Frage: was ist ein Beispiel für eine ‚gute‘ Hashfunktion? • Gute Verteilung der Suchschlüssel k auf den Indexbereich (die Plätze) der Hashtabelle H. • Ohne dass man Annahmen über eine zugrundeliegende Wahrscheinlichkeitsverteilung machen muss. Chained-Hash-delete(H,x) entferne x aus der Liste H[h(schlüssel(x))] 41 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 42 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert Technische Universität München Technische Universität München Lösung: • heuristische Ansätze oder • Divisions- und Multiplikationsmethode im Folgenden nur Divisionsmethode als Beispiel Was ist bei Wahl von m zu beachten? • Achtung: wenn m =2p ist, für eine natürliche Zahl p, dann gilt: • h(k) sind die p-niederwertigen Bits der Binärdarstellung von k. • Vorteil: sehr einfache ‚Berechnung‘ von h(k) Das wird in der Praxis z.B. beim sehr schnellen Suchen in schnellen Zwischenspeichern, bei Direct Mapped Cache verwendet • Nachteil: Sehr hohe Kollisions-Wahrscheinlichkeit, da der Funktionswert h nicht von allen Bits von k abhängt. Divisionsmethode • Hashfunktion h ist definiert durch: h(k) = k mod m, wobei m die Größe der Hashtabelle H ist. Beispiel: • Guter Kandidat für Wert von m: • gute Kandidaten sind Primzahlen p, die nicht sehr nahe bei einer 2-er Potenz liegen. AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 43 AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 44 Technische Universität München Technische Universität München Beispiel: • Rahmenbedingungen • Hashtabelle H soll n = 2000 Zeichenketten speichern • pro Zeichen 8 Bit zur Repräsentation (z.B. ASCII Code) • Kollisionen sollen mit Verkettung aufgelöst werden. • Forderung: bei erfolgloser Suche sollen im Mittel nur drei Elemente überprüft werden müssen. • Wahl der Hashtabellengröße: • Tabellengröße m sei gewählt mit: m= 701 • 701 ist Primzahl und der Wert ist nahe dem Wert 2000/3. • 701 liegt nicht in der Nähe einer 2-er Potenz. • Hashfunktion: h(k) = k mod 701, wobei k eine ganze Zahl ist. AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 45 Fazit: Hashing • Verwaltung von Daten so, dass ein Kompromiss aus Speicher- und Zeit-Komplexität erreicht wird. • Wenn U groß ist: Platz sparen durch Anlegen einer Hashtabelle, die wesentlich kleiner als U ist; • dadurch können aber Kollisionen auftreten • Verkettung in (kleinen) Listen zur Auflösung von Kollisionen • Durch Wahl einer guten Hashfunktion: • Kollisionswahrscheinlichkeit reduzieren, z.B. k mod p • Operationen auf Hashtabellen: • Schnelle Einfüge-, Entferne-Operationen: O(1) • Mit guter Hashfunktion auch Suche effizient: O(1) AuD, Kapitel 7 Fortgeschrittene Datenstrukturen, WS10/11, C. Eckert 46