Gliederung 5. Compiler 1. 2. 3. 4. Struktur eines Compilers Syntaxanalyse durch rekursiven Abstieg Ausnahmebehandlung Arrays und Strings 6. Sortieren und Suchen 1. Grundlegende Datenstrukturen 2. Bäume 3. Hash-Verfahren (Streuspeicherung) 7. Graphen 1. Darstellung und Topologisches Sortieren 2. Kürzeste Wege 3. Fluß- und Zuordnungsprobleme Graphen Ein (gerichteter) Graph ist ein Paar G = <V, E>, wobei gilt: • V ist eine endliche Menge von Knoten (engl. : Vertex) und • E ist eine zweistellige Relation auf V, d.h. E ⊆ V x V. Die Elemente von E werden Kanten (engl. : Edge) genannt. • Gilt G’ = <V’, E’> mit V’ ⊆ V und E’ ⊆ E, so heißt G’ Teilgraph von G. Graphen: Beispiele (1) V1 = Menge aller Flughäfen in Deutschland. G1 = { (x,y) ∈ V1 x V1 | Es gibt einen Direktflug zwischen x und y } (2) V2 = Bevölkerung von Erlangen G2 = { (x,y) ∈ V2 x V2 | x kennt y } (3) V3 = Bevölkerung von Erlangen G3 = { (x,y) ∈V3 x V3 | x ist verheiratet mit y }. G3 ist Teilgraph von G2 Bildliche Darstellung von Graphen Sei G= <V, E>: • Einen Knoten v ∈ V stellt man durch einen Punkt oder durch einen kleinen Kreis dar. • Eine Kante (x,y) ∈ E stellt man durch einen Pfeil vom Knoten x zum Knoten y dar. Beispiel: A D V = { A,B,C,D,E,F,G,H } F E = { (A,D), (D,A), (A,B), (B,C), (C,A), (B,E), (A,E), (F,G), (F,F)}. B H C G E Ungerichtete Graphen • Sei G = <V,E>. • Falls für jedes e ∈ E mit e = (v1,v2) gilt: e’ = (v2,v1) ∈ E, so heißt G ungerichteter Graph, ansonsten gerichteter Graph. • Die Relation E ist in diesem Fall symmetrisch. • Bei einem ungerichteten Graphen gehört zu jedem Pfeil von x nach y auch ein Pfeil von y nach x. • Daher lässt man Pfeile ganz weg und zeichnet nur ungerichtete Kanten. Ungerichtete Graphen: Beispiel • • V = einige Städte E = { (x,y) | Es gibt eine direkte Bahnverbindung zwischen x und y } 4 Kassel Marburg Köln 5 Bonn 0 7 Gießen 3 2 Fulda 1 Frankfurt 6 Mannheim 8 Würzburg Pfade, Zyklen und Gewichte • Eine Kante k = (x,y) heißt inzident zu x und y. • Ein Pfad (oder Weg) von x nach y ist eine Folge (x=a0 , a1, ... , ap=y) von Knoten mit (ai , ai+1 ) ∈ E. p wird die Länge des Weges von x nach y genannt. • In einem einfachen Pfad kommt jeder Knoten höchstens einmal vor. • Ein Pfad der Länge p > 1 von x nach x, in dem außer x kein Knoten mehr als einmal vorkommt, heißt Zyklus. • Ein gerichteter Graph <V, E> heißt zyklenfrei oder gerichteter azyklischer Graph (engl: directed acyclic graph, kurz: dag), wenn er keine Zyklen enthält. Beispiele (B, C, A, D, A) ist ein Pfad von B nach A. A D Er enthält einen Zyklus: (A, D, A). F (C, A, B, E) ist einfacher Pfad von C nach E. B H (F, F, F, G) ist ein Pfad. (A, B, C, A) und (A, D, A) und (F, F) sind die einzigen Zyklen. (A, B, E, A) ist kein Pfad und kein Zyklus. <{A,B,C,E}, {(A,B), (B,C), (B,E), (A,E)}> ist ein azyklischer Teilgraph von G. G C E Bewertete Graphen Ein Graph G = <V, E> kann zu einem bewerteten Graphen G = <V, E, gw(E)> erweitert werden, wenn man eine Gewichtsfunktion gw: E → int (oder gw: E → float/double) hinzunimmt, die jeder Kante e ∈ E ein (positives, ganzzahliges oder reelles) Gewicht gw(e) zuordnet. Für einen Weg w = (x=a0 , a1, ..., ap=y) heißt p-1 L(w) = Σ gw(ai, ai+1) i=0 die bewertete Länge von w. Im folgenden Beispiel gilt: L (Marburg, Gießen, Frankfurt, Mannheim) = 184 Bewertete Graphen : Beispiel 1 Köln 5 174 Marburg Bonn 224 106 3 Gießen 66 181 1 88 6 Mannheim 4 Kassel 96 7 30 34 0 104 2 Fulda 104 Frankfurt 136 93 8 Würzburg Bewertete Graphen: ein weiteres Beispiel San Rafael 1 15 Richmond 2 18 San Francisco 15 12 0 3 Oakland 20 15 20 Pacifica 14 5 Hayward San Mateo 15 Half Moon Bay Rund um die Bucht von San Francisco 25 13 20 4 14 18 6 10 10 10 12 20 15 Palo Alto 50 7 Fremont 15 9 8 San Jose Santa Clara 35 Scotts Valley Santa Cruz 70 60 Watsonville 11 Ungerichtete Graphen und Zusammenhang • Ungerichtete Graphen sind Spezialfälle von gerichteten Graphen. Zusätzlich soll für ungerichtete Graphen gelten: G heißt zusammenhängend, wenn es zwischen je zwei (verschiedenen) Knoten einen Weg gibt. Ist G nicht zusammenhängend, so zerfällt er in eine Vereinigung zusammenhängender Komponenten (auch Zusammenhangskomponenten genannt) . • Ein zusammenhängender zyklenfreier Graph ist ein Baum. F • Eine Gruppe paarweise nicht zusammenhängender Bäume heißt Wald. • Jeder zyklenfreie ungerichtete Graph ist also ein Wald. D A B H G C E Zusammenhangskomponenten: Zusammenhangskomponenten: Beispiel Beispiel Zusammenhang in gerichteten Graphen • Die Definitionen für Zusammenhang und Zusammenhangskomponenten lassen sich für gerichteten Graphen ausdehnen: Ein gerichteter Graph G heißt stark zusammenhängend, wenn es zwischen je zwei (verschiedenen) Knoten einen Weg gibt. Für einen beliebigen Graphen G kann man die Menge seiner starken Zusammenhangskomponenten betrachten. Zwei Knoten a und b liegen in der gleichen Komponente Z, wenn sowohl ein Weg von a nach b als auch einer von b nach a in Z existiert. • Ein gerichteter Graph G heißt schwach zusammenhängend, wenn der entsprechende ungerichtete Graph, der aus G durch Hinzunahme aller Rückwärtskanten entsteht, zusammenhängend ist. A D F B H G C E Starke StarkeZusammenhangskomponenten: Zusammenhangskomponenten:Beispiel Beispiel Aufspannender Baum • Ist G zusammenhängend und R ein zusammenhängender, zyklenfreier Teilgraph von G, der alle Knoten von G enthält, so heißt R ein (auf)spannender oder erzeugender Baum (engl.: spanning tree) von G. • Einfacher Algorithmus SpT zur Konstruktion des (auf)spannenden Baums für G: – Solange es einen Zyklus gibt, entferne eine Kante aus diesem Zyklus • Rekursiver Algorithmus SpT zur Konstruktion des (auf)spannenden Baums für G: – Markiere einen beliebigen Knoten v ∈ V – Wiederhole für alle von v ausgehenden Kanten e = (v,v') ∈ E: – Wenn v' unmarkiert, markiere v' und führe SpT (v') aus, sonst lösche e (und gehe zur nächsten Kante weiter). Erzeugender Baum : Beispiel Ein Graph G Ein erzeugender Baum von G Repräsentation von Graphen • Für die Repräsentation eines Graphen kommen in erster Linie zwei Datenstrukturen in Frage: – eine Boolesche Matrix (auch Adjazenzmatrix genannt): – eine Liste oder ein Array von Listen (für die Knoten des Graphen und deren jeweilige Verbindungen) . • Repräsentation durch eine Adjazenzmatrix: • Ein Graph G = ( V, E) ist i.W. durch die Angabe seiner Kanten E ⊆ V x V bestimmt. So wie Teilmengen von V durch Boolesche Arrays dargestellt werden können, kann man Teilmengen von V × V durch Boolesche Matrizen (sog. Adjazenzmatrizen) darstellen. – boolean [][] Graph; • Dies setzt natürlich voraus, daß wir mit V eine Aufzählung der Knoten des Graphen haben. Adjazenzmatrix: Beispiel String [] Knoten = {"A", "B", "C", "D", "E", "F", "G", "H"}; boolean [][] Kanten = { {false, true, false, true, true, false, false, false}, {false, false, true, false, true, false, false, false}, ... // usw. }; A D F B H C G E A B C D E F G H A x x x B x x C x D x E F x x G H ( True = x, False = " " ) Definition einer Graph-Klasse (1) class Graph{ String [] Knoten; int KnotenZahl; int[][] Kanten ; Graph(String [] Knotenliste, int [] [] Kantenliste){ Knoten = Knotenliste; Kanten = Kantenliste; KnotenZahl = knoten.length; } } Bewertete Adjazenzmatrizen (1) • Viele der klassischen Anwendungsbeispiele für Graphen kommen aus dem Bereich der Verkehrsnetze. • Wir wollen daher die folgenden Algorithmen anhand zweier umfangreichen Beispiele aus diesem Bereich erläutern. • Kanten sind mögliche direkte Verkehrsverbindungen (Straßen) zwischen Städten. • Bewertet werden sie mit einer Maßzahl, welche die Entfernung und/oder den durchschnittlichen Zeitaufwand für eine Fahrt zwischen den Städten reflektiert. • Dabei haben die Knoten jeweils einen Namen (den Städtenamen) und eine Nummer in der Aufzählung. • Daraus kann man dann eine Matrixdarstellung gewinnen. Bewertete Adjazenzmatrizen (2) • Bei dieser Matrix sind die Einträge keine Booleschen Werte mehr, sondern entsprechen den Bewertungen der Kanten. • Wenn der Wert nicht in der Matrix erscheint, bedeutet das, daß keine direkte Verbindung zwischen den entsprechenden Städten existiert. • In der Diagonalen steht die Bewertung der Verbindung jeder Stadt mit sich selbst. Diese wird hier stets mit 0 angenommen. • Wir haben damit das Prinzip der Adjazenzmatrix auf bewertete Graphen erweitert. Statt Statteines einesbooleschen booleschenWerts Wertsspeichert speichertman mandas das Gewicht Gewichtgw gw(u,v) (u,v)jeder jederKante Kantean ander derbetreffenden betreffenden Position PositionM[u,v] M[u,v]der derMatrix MatrixM. M. • Ist G ein ungerichteter Graph, so ist die Matrix symmetrisch - d.h. man kommt im Prinzip mit einer Dreiecksmatrix aus. Bewertete Adjazenzmatrix: Beispiel 1 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 BN F FD GI KS K MA MR WÜ 0 181 - - - 34 224 - - 181 0 104 66 - - 88 - 136 FD - 104 0 106 96 - - - 93 GI - 66 106 0 - 174 - 30 - KS - - 96 - 0 - - 104 - 34 - - 174 - 0 - - - 88 - - - - 0 - - BN F K MA 224 7 8 MR - - - 30 104 - - 0 - WÜ - 136 93 - - - - - 0 Bewertete Adjazenzmatrix: Beispiel 2 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 0 18 - 12 20 - - - - - - - - - 15 1 18 0 15 - - - - - - - - - - - - - 15 0 15 - - - - - - - - - - - 3 12 - 15 0 - 20 - - - - - - - - - 4 20 - - 0 20 18 - - - - - - 25 - - - - - - - 2 - 5 - - - 20 20 0 - 14 - 6 - - - - 18 - 0 15 - 10 - - - - - 7 - - - - - 14 15 0 20 - - - - - 8 - - - - - - - 20 0 15 - 60 - - - 9 - - - - - - 10 - 15 0 35 - - - 10 - - - - - - - - - 10 - - 11 - - - - - - - - 60 - 0 70 - - 12 - - - - - - - - - - 10 70 0 50 - 13 - - - - 25 - - - - - - - 50 0 15 15 - - - - - - - - - 14 - - - - 35 0 - - - 15 0 Adjazenzmatrix für Beispiel 1 • Bildung einer Instanz der Klasse Graph, um den Beispiel-1Graphen zu repräsentieren. Graph Bsp1 = new Graph( {"Bonn", "Frankfurt", "Fulda", "Gießen", "Kassel", "Köln", "Mannheim", "Marburg", "Würzburg" }, { }); { 0, 181, 0, 0, 0, 34, 224, 0, 0}, {181, 0, 104, 66, 0, 0, 88, 0, 136}, { 0, 104, 0, 106, 96, 0, 0, 0, 93}, { 0, 66, 106, 0, 0, 174, 0, 30, 0}, { 0, 0, 96, 0, 0, 0, 0, 104, 0}, { 34, 0, 0, 174, 0, 0, 0, 0, 0}, {224, 88, 0, 0, 0, 0, 0, 0, 0}, { 0, 0, 0, 30, 104, 0, 0, 0, 0}, { 0, 136, 93, 0, 0, 0, 0, 0, 0} Adjazenzmatrix für Beispiel 2 Graph Bsp2 = new Graph( { "San Francisco", "San Rafael", "Richmond", "Oakland", "San Mateo", "Hayward", "Palo Alto", "Fremont", "San Jose", "Santa Clara", "Scotts Valley", "Watsonville", "Santa Cruz", "Half Moon Bay", "Pacifica" }, { { 0, 18, 0, 12, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15}, {18, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, { 0, 15, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {12, 0, 15, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {20, 0, 0, 0, 0, 20, 18, 0, 0, 0, 0, 0, 0, 25, 0}, { 0, 0, 0, 20, 20, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 18, 0, 0, 15, 0, 10, 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0, 14, 15, 0, 20, 0, 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0, 0, 0, 20, 0, 15, 0, 60, 0, 0, 0}, { 0, 0, 0, 0, 0, 0, 10, 0, 15, 0, 35, 0, 0, 0, 0}, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 10, 0, 0}, { 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 70, 0, 0}, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 70, 0, 50, 0}, { 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 50, 0, 15}, {15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0} }); Knoten- und Kantenzugriffe • Der Zugriff auf einzelne Knoten und Kanten ist mit Hilfe einfacher Zugriffsfunktionen möglich: String gibKnoten (int k) { return Knoten[k];} int gibKantenW (int u, int v) { return Kanten[u][v];} Beispiel: Graph Bsp1 = new Graph(); Bsp1.gibKnoten (3); Bsp1.gibKantenW (3, 7); // Ergebnis: "Gießen" // Ergebnis: 30 Adjazenzlisten (1) A Eine alternative, weniger speicheraufwendige Methode, einen Graphen darzustellen, besteht darin, zu jedem Knoten eine Liste zu definieren, in der die unmittelbaren Nachbarn samt der Gewichte ihrer Verbindungen enthalten sind. Dabei wird jedem Knoten eine Adjazenzliste (= Liste seiner Nachbarknoten) zugeordnet. D F B H G C E A B C D B C A A D E E E F F G G H Adjazenzlisten (2) • Die Darstellung eines Graphen mit einer Adjazenzliste spart Speicherplatz, da die speicheraufwendige (und hochgradig redundante) Adjazenzmatrix vermieden wird. • Leider ist bei der Listendarstellung der Aufwand für einen direkten Zugriff auf den Wert einer Kante von x nach y hoch. • Dieser Zugriff erfolgt jedoch bei typischen Graphalgorithmen recht häufig. • Im Falle der Verwendung einer Adjazenzmatrix ist der Zugriff direkt möglich, im Falle der Listendarstellung führt der entsprechende Zugriff zu einer Suche in der Liste aller Nachbarn. Adjazenzlisten für Beispiel 1 0 Bonn 1 181 5 34 6 224 null 1 Frankfurt 0 181 2 104 3 66 6 88 8 136 null 2 Fulda 1 104 3 106 4 96 8 93 null 3 Gießen 1 66 2 106 5 174 7 30 null 4 Kassel 2 96 7 104 null 5 Köln 0 34 3 174 null 6 Mannheim 0 224 1 88 null 7 Marburg 3 30 4 104 null 8 Würzburg 1 136 2 93 null Adjazenzlisten für Beispiel 2 0 San Francisco 18 2 12 4 20 5 15 15 1 San Rafael 18 1 15 3 null 2 Richmond 15 2 15 4 null 3 Oakland 12 1 15 3 20 6 null 4 San Mateo 20 1 20 6 18 7 25 4 5 Hayward 20 4 20 5 14 8 null 6 Palo Alto 18 5 15 8 10 10 null 7 Fremont 14 6 15 7 20 9 null 8 San Jose 20 8 15 10 60 12 null 9 Santa Clara 10 7 15 9 35 11 null 10 Scotts Valley 35 10 10 13 null 11 Watsonville 60 9 70 13 null 12 Santa Cruz 10 10 70 12 50 14 null 13 Half Moon Bay 50 13 25 5 15 15 null 14 Pacifica 15 14 15 1 null null Adjazenzliste für Beispiel 2 (1) • Eine Klasse zur Repräsentation von Verbindungen class Verbindung{ int Laenge; int ziel; Verbindung next; Verbindung(int l, int w, Verbindung v){ Laenge = l; ziel = w; next = v; } } Adjazenzliste für Beispiel 2 (2) • Eine Klasse zur Repräsentation von Knoten class KnotenTyp{ String Name; Verbindung Nachbarn; KnotenTyp(String s, Verbindung v){ Name = s; Nachbarn = v; } } Definition einer Graph-Klasse (2) class Graph{ KnotenTyp [] Knoten; int KnotenZahl; Graph(Knotentyp [] KnotenTypListe){ Knoten = KnotenTypListe; KnotenZahl = knoten.length; } } Adjazenzliste für Beispiel 2 (3) • Realisierung des Graphen KnotenTyp [] Knoten = new KnotenTyp[15]; Knoten[ 0] = new KnotenTyp("San Francisco", new Verbindung(18, 1, new Verbindung(12, 3, new Verbindung(20, 4, new Verbindung(15, 14, null))))); Knoten[ 1] = new KnotenTyp("San Rafael", new Verbindung(18, 0, new Verbindung(15, 2, null))); Knoten[ 2] = new KnotenTyp("Richmond", new Verbindung(15, 1, new Verbindung(15, 3, null))); .... /// usw. usw. usw.... Graph myGraph= new Graph(Knoten); Knoten- und Kantenzugriffe • Der Zugriff auf einzelne Knoten ist immer noch einfach: String gibKnoten (int k) { return Knoten[k].Name[k]; } • Der Zugriff auf einzelne Kanten ist nur mit Hilfe einer aufwendigeren Zugriffsfunktionen möglich: int gibKantenW (int u, int v) { Verbindung vp = Knoten[u].Nachbarn; while (vp != null){ if (vp.ziel == v) return vp.Laenge; vp = vp.next; } return 0; } Implementierung durch Listen von Listen (1) • Eine weitere Möglichkeit zur Implementierung von Graphen besteht darin, auch die Folge der Knoten auf eine Liste abzubilden, d.h. der B D E A gesamte Graph wird durch eine Liste von Listen dargestellt. C E B A D F B G E A D A E H C C F G H F G Implementierung durch Listen von Listen (2) class Edge { int dest, cost; public Edge (int d, int c) { dest = d; cost = c; } } public class Graph { private Hashtable labels; private Vector nodes; public Graph () { labels = new Hashtable (); nodes = new Vector (); } Implementierung durch Listen von Listen (3) public void addNode (String label) { if (labels.contains (label)) throw new NodeAlreadyDefinedException (); nodes.addElement (new LinkedList ()); int idx = nodes.size () - 1; labels.put (label, new Integer (idx)); } public int getNodeID (String label) { Integer i = (Integer) labels.get (label); if (i == null) throw new NoSuchElementException (); return i.intValue (); } public void addEdge (String src, String dest, int cost) { LinkedList adjList = (LinkedList) nodes.elementAt (getNodeID (src)); adjList.add (new Edge (getNodeID (dest), cost)); } public Iterator getEdges (int node) { return ((LinkedList) nodes.elementAt (node)).iterator (); } Vergleich der Implementierungen • Alle hier betrachteten Möglichkeiten zur Implementierung von Graphen haben ihre spezifischen Vor- und Nachteile. • Seien n = Knotenzahl und m = Kantenzahl eines Graphen G. Vorteile Nachteile Adjazenzmatrix Berechnung der Inzidenz mit O(1) hoher Platzbedarf und teure Initialisierung: beide O(n2) Adjazenzliste Platzbedarf beträgt nur O(n+m) Effizienz der Kantensuche abhängig von Knotenordnung O(m) Liste von Listen Knoten lassen sich flexibel hinzufügen/ löschen Effizienz von Knoten- und Kantensuche abhängig von Listenposition O(m.n) Literatur • Die Vorlesungsfolien wurden aus der Vorlesung „Informatik II a, SS 2002“ von Prof. Sommer (Uni-Marburg) übernommen