Datenstrukturen Sommersemester 2010 Isolde Adler Herzlich willkommen! Wiederholung Kapitel 3: Elementare Datenstrukturen Ankündigung Vortrag von Prof. Douglas Rogers: Zero-one evaluations for the classic non-associative bracketing problem • Fragestellungen aus der Kombinatorik • u.A. die berühmten Catalan-Zahlen • historische Zusammenhänge • Wann? Kommenden Dienstag, 11.5.2010, 10:00 c.t. hier im Magnus-Hörsaal Sie sind herzlich eingeladen! Isolde Adler Datenstrukturen 2010 2/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Zusätzliche Übungsgruppe Wir konnten noch eine zusätzliche Übungsgruppe einrichten! • Mittwochs, 8-10 Uhr. Nächster Termin: 12.5.2010 • in Raum NM 125 • Leitung: Herr Maziar Behdju • Sie können gerne noch zu Herrn Behdju wechseln! Isolde Adler Datenstrukturen 2010 3/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Anwesenheit in den Übungen • Die Teilnahme an den Übungen ist freiwillig, aber . . . nehmen Sie unbedingt an den Übungen teil!! Es sind insgesamt nur 7 Termine! • Bearbeiten Sie unbedingt die Aufgabenblätter!! • Erfahrungsgemäß entscheidet das in fast allen Fällen über das Bestehen der Klausur • Wenn Sie sich entscheiden würden, an einem kostenpflichtigen Intensivkurs teilzunehmen, würden Sie auch versuchen, jeden Termin wahrzunehmen • 10% der Punkte aus den Übungen können bei regelmäßiger und aktiver Teilnehme an den Übungen mit in die Klausur genommen werden Isolde Adler Datenstrukturen 2010 4/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Zusammenfassung der letzten Vorlesungsstunde, 1 Analyse von C++ Programmen • Zuweisungen: Eine Zuweisung zu einer einfachen“ Variablen ist ” einfach zu zählen, eine Zuweisung zu einer Array-Variablen ist mit der Länge des Arrays zu gewichten. • Auswahl-Anweisungen: Häufig genügt: Bedingung + Gesamtaufwand für den längsten der alternativen Anweisungsblöcke. • Schleifen: Häufig genügt: Maximale Anzahl der auszuführenden Anweisungen innerhalb einer Schleife × Anzahl der Schleifendurchläufe • Beispiel: Matrizenmultiplikation A · B = C for (i=0; i < n; i++) for (j=0; j < n; j++) { C[i][j] = 0; for (k=0; k < n ; k++) C[i][j] += A[i][k]*B[k][j]; } Isolde Adler Datenstrukturen 2010 5/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Zusammenfassung der letzten Vorlesungsstunde, 2 Datenstrukturen: • Einfach verkettete Listen, Anwendungsbeispiel: Addition dünnbesetzter Matrizen • Stacks: last in first out (LIFO) • Queues: first in first out (FIFO) • Deques: kombinieren Stacks und Queues • Bäume als Datenstrukturen Isolde Adler Datenstrukturen 2010 6/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Ausblick: • verschiedene Baum-Implementierungen • Suche in Bäumen, Preorder, Postorder, Inorder • Graphen: Grundbegriffe und topologisches Sortieren Isolde Adler Datenstrukturen 2010 7/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Operationen auf Bäumen (als Datenstrukturen) (1) Wurzel: Bestimme die Wurzel von T . (2) Vater(v ): Bestimme den Vater des Knoten v in T . Wenn v = r , dann ist der Null-Zeiger auszugeben. (3) Kinder(v ): Bestimme die Kinder von v . Wenn v ein Blatt ist, dann ist der Null-Zeiger als Antwort zu geben. (4) Für binäre geordnete Bäume: (4a) LKind(v ): Bestimme das linke Kind von v . (4b) RKind(v ): Bestimme das rechte Kind von v . (4c) Sollte das entsprechende Kind nicht existieren, ist der Null-Zeiger als Antwort zu geben. (5) Tiefe(v ): Bestimme die Tiefe von v . (6) Höhe(v ): Bestimme die Höhe von v . (7) Baum(v , T1 , . . . , Tm ): Erzeuge einen geordneten Baum mit Wurzel v und Teilbäumen T1 , . . . , Tm . (8) Suche(x): Bestimme alle Knoten mit Wert x. Isolde Adler Datenstrukturen 2010 8/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Implementierung mittels Vater-Array Annahme: Jeder Knoten besitzt eine Zahl aus {1, . . . , n} als Namen und zu jedem i ∈ {1, . . . , n} gibt es genau einen Knoten mit Namen i. • Ein Integer-Array Parent speichert für jeden Knoten v den Namen des Vaters von v : Parent[v ] = Name des Vaters von v . Wenn v = r , dann setze Parent[v ] = r . • Das Positive: + schnelle Vater-Bestimmung (Zeit = O(1)) + und schnelle Bestimmung der Tiefe von v (Zeit = O(Tiefe(v ))). + Minimaler Speicherplatzverbrauch: Bäume mit n Knoten benötigen Speicherplatz n. • Das Negative: für die Bestimmung der Kinder muss der gesamte Baum durchsucht werden (Zeit = O(Anzahl Knoten).) Isolde Adler Datenstrukturen 2010 9/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Die Adjazenzlisten-Implementierung Für Bäume mit n Knoten benutze ein Kopf-Array“ Head mit den Zellen ” 1, . . . , n: Head[i] ist ein Zeiger auf die Liste der Kinder von i. 1 2 3 4 5 6 7 8 9 10 Isolde Adler - 10 -1 -2 -4 -7 -9 - Datenstrukturen 2010 -3 - -6 -8 - - 10/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Stärken und Schwächen + Die Kinderbestimmung gelingt schnellstmöglich, in Zeit proportional zur Anzahl der Kinder. + Höhe (v , T ) wird auch angemessen unterstützt mit der Laufzeit O(Anzahl der Knoten im Teilbaum mit Wurzel v ). Warum? - Für die Bestimmung des Vaters muss möglicherweise der gesamte Baum durchsucht werden! Auch die Bestimmung der Tiefe ist schwierig, da der Vater nicht bekannt ist. - Speicherplatzverbrauch: Für Bäume mit n Knoten benötigen wir • 2n − 1 Zeiger: einen Zeiger für jede der n Zellen von Head und einen Zeiger für jede der n − 1 Kanten • und 2n − 1 Zellen: n Zellen für das Array Head und n − 1 Zellen für die n − 1 Kanten. Zu großer Speicherplatz. Aber die Adjazenzlisten-Darstellung kann auch für Graphen benutzt werden. Isolde Adler Datenstrukturen 2010 11/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Die Binärbaum-Implementierung Ein Knoten wird durch die Struktur typedef struct Knoten { int wert; Knoten *links, *rechts; }; dargestellt. • Wenn der Zeiger z auf die Struktur des Knoten v zeigt, • dann ist z → wert der Wert von v und • z → links (bzw. z → rechts) zeigt auf die Struktur des linken (bzw. rechten) Kindes von v . • Der Zeiger wurzel zeige auf die Struktur der Wurzel des Baums. • Im Vergleich zur Adjazenzlisten-Darstellung: • Ähnliches Laufzeitverhalten bei den Operationen • aber bessere Speichereffizienz: 2n Zeiger (zwei Zeiger pro Knoten) und n Zellen (eine Zelle pro Knoten). Isolde Adler Datenstrukturen 2010 12/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Die Kind-Geschwister-Implementierung Ein Knoten wird durch die Struktur typedef struct Knoten { int wert; Knoten *LKind, *RGeschwister; }; dargestellt. • Wenn der Zeiger z auf die Struktur des Knoten v zeigt, • dann ist z -> wert der Wert von v und • z -> LKind (bzw. z -> RGeschwister) zeigt auf die Struktur des linken Kindes, bzw. des rechten Geschwisterknoten von v . • Der Zeiger wurzel zeige wieder auf die Struktur der Wurzel des Baums. • Im Vergleich zur Binärbaum-Darstellung: • Ähnliches Laufzeitverhalten und ähnliche Speichereffizienz, • aber die Darstellung ist für alle Bäume und nicht nur Binärbäume anwendbar! Isolde Adler Datenstrukturen 2010 13/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Suche in Bäumen: Postorder, Preorder und Inorder Sei T ein geordneter Baum mit Wurzel r und Teilbäumen T1 , . . . , Tm . • Postorder: Durchlaufe rekursiv die Teilbäume T1 , . . . , Tm nacheinander. Danach wird die Wurzel r besucht. • Preorder: besuche zuerst r und danach durchlaufe rekursiv die Teilbäume T1 , . . . , Tm . • Inorder: Durchlaufe zuerst T1 rekursiv, sodann die Wurzel r und letztlich die Teilbäume T2 , . . . , Tm rekursiv. void preorder (Knoten *p){ Knoten *q; if (p != null) { cout << p->wert; for (q=p->LKind; q!=null; q = q->RGeschwister) preorder(q); }} Isolde Adler Datenstrukturen 2010 14/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Bestimmung der Tiefe und Höhe von Knoten Die Struktur eines Knoten besteht aus den Feldern tiefe, hoehe, LKind und RGeschwister. • void tiefe (Knoten *p, int t) { Knoten *q; p->tiefe = t; t = t+1; for(q = p->LKind; q!=null; q = q->RGeschwister) tiefe(q,t); } • Aufruf mit if (wurzel != null) tiefe(wurzel,0); • int hoehe (Knoten *p){ Knoten *q; int h=-1; for(q = p->LKind; q!=null; q = q->RGeschwister) h = max(h,hoehe(q)); h=h+1; p->hoehe = h; return h; } • Aufruf mit if (p != null) hoehe(p); Isolde Adler Datenstrukturen 2010 15/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Welcher Knoten wird direkt nach v besucht? Postorder: • das linkeste Blatt im rechten Nachbarbaum. • Wenn v keinen rechten Geschwisterknoten besitzt, dann wird der Vater von v als nächster besucht. Preorder: • das linkeste Kind von v . • Wenn v ein Blatt ist, dann das erste nicht-besuchte Kind des tiefsten, nicht vollständig durchsuchten Vorfahren von v Inorder:. . . ? Wenn wir zusätzliche Zeiger gemäß einer dieser Reihenfolgen einsetzen, dann können wir Rekursion vermeiden: besuche die Knoten gemäß der neuen Zeiger (zusätzlicher Verwaltungsaufwand). Die Datenstrukturen heißen dann postorder-, preorder-, inorder-gefädelte Bäume Isolde Adler Datenstrukturen 2010 16/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Eine nicht-rekursive Preorder-Implementierung Der Teilbaum mit Wurzel v ist in Preorder-Reihenfolge zu durchlaufen. (1) Wir fügen einen Zeiger auf die Struktur von v in einen anfänglich leeren Stack ein. (2) Solange der Stack nicht leer ist, wiederhole: (a) Entferne das erste Stack-Element w mit Hilfe der Pop-Operation. Besuche w . (b) Füge die Kinder von w in umgekehrter Reihenfolge in den Stack ein. /∗ Durch die Umkehrung der Reihenfolge werden die Bäume später in ihrer natürlichen Reihenfolge abgearbeitet. ∗ / Die Laufzeit ist linear in der Knotenzahl n. • Jeder Knoten wird genau einmal in den Stack eingefügt. • Insgesamt werden also höchstens O(n) Stackoperationen durchgeführt. Stackoperationen dominieren aber die Laufzeit. Isolde Adler Datenstrukturen 2010 17/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Laufzeit Für einen Baum mit n Knoten, der in Kind-Geschwister-Darstellung, Adjazenzisten- oder Binärbaum-Darstellung vorliegt, können die Reihenfolgen Postorder, Präoerder bzw. Inorder in Zeit O(n) berechnet werden. Isolde Adler Datenstrukturen 2010 18/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen (a) Ein ungerichteter Graph G = (V , E ) besteht aus einer endlichen Menge und einer Teilmenge V von Knoten (engl.: vertices) E ⊆ {u, v } u, v ∈ V , u 6= v von Kanten (engl.: edges). • Die Endpunkte u, v einer ungerichteten Kante {u, v } sind gleichberechtigt. • u und v heißen dann Nachbarn. Wir sagen auch: u und v sind adjazent. (b) Für die Kantenmenge E eines gerichteten Graphen G = (V , E ) gilt E ⊆ {(u, v ) | u, v ∈ V , u 6= v }. • Der Knoten u ist Anfangspunkt und der Knoten v Endpunkt der Kante (u, v ). • v heißt auch ein direkter Nachfolger von u und u ein direkter Vorgänger von v . Isolde Adler Datenstrukturen 2010 19/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Warum Graphen? Graphen modellieren • das World Wide Web: Die Knoten entsprechen Webseiten, die (gerichteten) Kanten entsprechen Hyperlinks. • Rechnernetzwerke: Die Knoten entsprechen Rechnern, die (gerichteten und/oder ungerichteten) Kanten entsprechen Direktverbindungen zwischen Rechnern. • Das Schienennetz der Deutschen Bahn: Die Knoten entsprechen Bahnhöfen, die (ungerichteten) Kanten entsprechen Direktverbindungen zwischen Bahnhöfen. Bei der Erstellung von Reiseplänen müssen kürzeste (gewichtete) Wege zwischen einem Start- und einem Zielbahnhof bestimmt werden. • Schaltungen: die Knoten entsprechen Gattern, die (gerichteten) Kanten entsprechen Leiterbahnen zwischen Gattern. Isolde Adler Datenstrukturen 2010 20/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Das Königsberger Brückenproblem Gibt es einen Rundweg durch Königsberg, der alle Brücken über den Pregel genau einmal überquert? 1 BBBBBBB BBBBBBB BBBBBBB 2 BBBBBBB BBBBBBB BBBBBBB 3 4 Der zugehörige ungerichtete Graph (mit Mehrfachkanten!): # 1j ! 2j " 4j Isolde Adler Datenstrukturen 2010 3j ! 21/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Euler-Kreise Ein Euler-Kreis beginnt in einem Knoten v , durchläuft alle Kanten genau einmal und kehrt dann zu v zurück. Das Königsberger Brückenproblem besitzt 1f 2f 4f keine Lösung, denn der Graph 3f hat keinen Euler-Kreis: Ansonsten hätte jeder Knoten eine gerade Anzahl von Nachbarn! Und der folgende Graph? h 0 @ @ Rh 1h 2 PP 1 6PP q ? 3h 4h I @ @ 5h Isolde Adler Datenstrukturen 2010 22/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Wichtige Begriffe Sei G = (V , E ) ein gerichteter oder ungerichteter Graph. • Eine Folge (v0 , v1 , ..., vm ) heißt ein Weg in G , falls für jedes i (0 ≤ i < m) gilt • (vi , vi+1 ) ∈ E (für gerichtete Graphen) bzw. • {vi , vi+1 } ∈ E (für ungerichtete Graphen). Die Weglänge ist m, die Anzahl der Kanten. Ein Weg heißt einfach, wenn kein Knoten zweimal auftritt. • Ein Weg heißt ein Kreis, wenn v0 = vm und (v0 , ...vm−1 ) ein einfacher Weg ist. G heißt azyklisch, wenn G keine Kreise hat. • Ein ungerichteter Graph heißt zusammenhängend, wenn je zwei Knoten durch einen Weg miteinander verbunden sind. Isolde Adler Datenstrukturen 2010 23/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Topologisches Sortieren n Aufgaben a0 , . . . , an−1 sind auszuführen. Allerdings gibt es eine Menge P von p Prioritäten zwischen den einzelnen Aufgaben. Die Priorität (i, j) impliziert, dass Aufgabe ai vor Aufgabe aj ausgeführt werden muss. Das Ziel ist die Erstellung einer Reihenfolge, in der alle Aufgaben ausgeführt werden können, bzw. festzustellen, dass eine solche Reihenfolge nicht existiert. • Eine graphentheoretische Formulierung: • Wähle V = {0, . . . , n − 1} als Knotenmenge. Wir verabreden, dass Knoten i der Aufgabe ai entspricht. • Wir setzen genau dann eine Kante von i nach j ein, wenn (i, j) eine Priorität ist. • Wie ist das Ziel zu formulieren? Bestimme eine Reihenfolge v1 , . . . , vi , . . . , vn der Knoten, so dass es keine Kante (vi , vj ) mit j < i gibt. Isolde Adler Datenstrukturen 2010 24/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Die Idee Eine Aufgabe aj kann als erste Aufgabe ausgeführt werden, wenn es keine Priorität der Form (i, j) in P gibt. • Ein Knoten v von G heißt eine Quelle, wenn Eingangsgrad(v ) = 0 ist, wenn v also kein Endpunkt einer Kante ist. • Also bestimme eine Quelle v , führe v aus und entferne v . • Wiederhole dieses Verfahren, solange G noch Knoten besitzt: bestimme eine Quelle v , führe v aus und entferne v . Welche Datenstrukturen sollten wir verwenden? Isolde Adler Datenstrukturen 2010 25/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Ein erster Versuch Wir verketten alle p Kanten in einer Liste Priorität“ und benutzen ein ” Integer-Array Reihenfolge“ sowie zwei boolesche Arrays Erster“ und ” ” Fertig“ mit jeweils n Zellen. ” Zaehler = 0. Für alle i setze Fertig[i] = falsch. Wiederhole n Mal: (0) Durchlaufe das Array Erster. Setze Erster[i] = wahr genau dann, wenn Fertig[i] = falsch. (1) Durchlaufe die Liste Priorität. Wenn Kante (i, j) angetroffen wird, setze Erster[j] = falsch. (2) Bestimme das kleinste j mit Erster[j] = wahr. Danach setze (a) Fertig[j] = wahr, (b) Reihenfolge[Zaehler++] = j (Aufgabe j wird ausgeführt) (c) und durchlaufe die Prioritätsliste: entferne jede Kante (j, k), da aj eine Ausführung von Aufgabe ak nicht mehr behindert. Isolde Adler Datenstrukturen 2010 26/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Eine Laufzeitanalyse • Was ist besonders teuer? • In jeder Iteration muss die Liste Priorität vollständig durchlaufen werden: Zeit = O(p). • Weiterhin muss das Array Erster jeweils initialisiert werden: Zeit = O(n). • Die Laufzeit pro Iteration ist dann durch O(p + n) beschränkt. Die Gesamtlaufzeit ist O(n · (p + n)), da wir n Iterationen haben. • Was können wir verbessern? • Wir müssen nur die in j beginnenden Kanten entfernen. • Warum kompliziert nach der ersten ausführbaren Aufgabe suchen? Eine vor aj nicht in Betracht kommende Aufgabe ak wird nur interessant, wenn (j, k) eine Priorität ist. Isolde Adler Datenstrukturen 2010 27/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Der zweite Versuch Stelle die Prioritäten durch eine Adjazenzliste mit dem Kopf-Array Priorität dar. Benutze ein Array Eingangsgrad mit Eingangsgrad[v ] = k, falls v Endpunkt von k Kanten ist. (1) Initialisiere die Adjazenzliste Priorität durch Einlesen aller Prioritäten. (Zeit = O(n + p)). (2) Initialisiere das Array Eingangsgrad. (Zeit = O(n + p)). (3) Alle Knoten v mit Eingangsgrad[v ] = 0 werden in eine Schlange gestopft. (Zeit = O(n)). (4) Setze Zähler = 0; Wiederhole solange, bis Schlange leer ist: (a) Entferne einen Knoten i aus Schlange. (b) Setze Reihenfolge [Zähler++] = i. (c) Durchlaufe die Liste Priorität[i] und reduziere den Eingangsgrad für jeden Nachfolger j von i um 1. Wenn jetzt Eingangsgrad[j] = 0, dann stopfe j in Schlange: Aufgabe aj ist jetzt ausführbar. Isolde Adler Datenstrukturen 2010 28/30 Graphen Wiederholung Kapitel 3: Elementare Datenstrukturen Graphen Die Analyse • Die Initialisierungen laufen in O(n + p) Schritten ab. • Ein Knoten wird nur einmal in die Schlange eingefügt. Also beschäftigen sich höchstens O(n) Schritte mit der Schlange. • Eine Kante (i, k) wird, mit Ausnahme der Initialisierungen, nur dann inspiziert, wenn i aus der Schlange entfernt wird. • Jede Kante wird nur einmal angefasst“ ” • und höchstens O(p) Schritte behandeln Kanten. Das Problem des topologischen Sortierens wird für einen Graphen mit n Knoten und p Kanten in Zeit O(n + p) gelöst. Schneller geht’s nimmer. Isolde Adler Datenstrukturen 2010 29/30 Wiederholung Kapitel 3: Elementare Datenstrukturen Graph-Implementierungen • Welche Datenstruktur sollten wir für die Darstellung eines Graphen G wählen? • Welche Operationen sollen schnell ausführbar sein? • Ist e eine Kante von G ? Die Adjazenzmatrix wird sich als eine gute Wahl herausstellen. • Bestimme die Nachbarn, bzw. Vorgänger und Nachfolger eines Knoten. Die Adjazenzlistendarstellung ist unschlagbar. Besonders die Nachbar- und Nachfolgerbestimmung ist wichtig, um Wege zu durchlaufen. Isolde Adler Datenstrukturen 2010 30/30 Graphen