# 3. Graphstrukturen (LK) Als Spezialfall eines Graphen betrachten wir hier den ungerichteten, gewichteten Graphen. D.h. eine Kantenverbindung zwischen zwei Knoten wird grundsätzlich in beide Richtungen interpretiert und jeder Kante wird ein Gewicht (z.B. die Weglänge oder die benötigte Zeit) zugeordnet. Durch die Gesamtheit aller Kanten ergibt sich der Graph. ## Eine Datenstruktur für Graphen Um Graphen zu modellieren, stehen die drei Klassen *Vertex*, *Edge* und *Graph* zur Verfügung. ### Einen Graphen aufbauen Der folgende Quelltext baut mithilfe der drei Klassen einen einfachen Beispielgraphen auf: ![](img/graph.png) Im Quelltext ist deutlich zu sehen, dass die Knoten und Kanten des Graphen getrennt erzeugt und dem Graphen zugewiesen werden. ```java Graph g = new Graph(); Vertex v1 = new Vertex("1"); Vertex v2 = new Vertex("2"); Vertex v3 = new Vertex("3"); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); Edge e1 = new Edge(v1,v2,5); Edge e2 = new Edge(v1,v3,7); Edge e3 = new Edge(v2,v3,13); g.addEdge(e1); g.addEdge(e2); g.addEdge(e3); ``` ### Beispiel: Nächster Nachbar Als exemplarische Anwendung der drei Klassen wird hier in einem gegebenem Graphen für einen gegebenen Knoten der nächstgelegene Nachbar bestimmt, d.h. die ID desjenigen Knoten, der mit dem gegebenen Knoten direkt verbunden ist und dessen zugehörige Kante das geringste Gewicht aufweist. Dazu wird mithilfe der Methode *getNeighbours()* zunächst eine Liste aller Nachbarknoten erfragt. Diese Liste wird nun durchlaufen. Dabei wird jeweils überprüft, ob das bisherige minimale Kantengewicht von der aktuellen Kante (vom Ausgangsknoten zum Nchbarknoten) unterboten werden kann. ```java public String findeNaechstenNachbarn(String ID) { Vertex startnode = g.getVertex(ID); if (startnode==null) { return "Knoten unbekannt"; } else { List l = g.getNeighbours(startnode); l.toFirst(); String next_neighbour = "kein Nachbar"; double min = 10000.0; while (l.hasAccess()) { Vertex node = l.getContent(); Edge e = g.getEdge(startnode,node); double distance = e.getWeight(); if (distance s = new Stack(); s.push(startnode); while (!s.isEmpty()) { Vertex aktuell = (Vertex) s.top(); if (!aktuell.isMarked()) { // VERARBEITEN System.out.println(aktuell.getID()); } aktuell.setMark(true); List l = g.getNeighbours(aktuell); l.toFirst(); boolean gefunden = false; while (l.hasAccess()) { // nicht-markieter Knoten vorhanden? Vertex nachbar = l.getContent(); if (!nachbar.isMarked()) { s.push(nachbar); gefunden = true; break; } else { l.next(); } } if (!gefunden) { s.pop(); } } } ``` ### Breitensuche Im Unterschied zur Tiefensuche wird bei der Breitensuche eine Schlange als Hilfsdatenstruktur eingesetzt. Da für jeden aktuellen alle noch nicht besuchten Nachbarn an die Schlange angehängt werden, ergibt sich eine "ebenenweise" Abarbeitung des Graphen. ```java private Queue nichtDoppeltEinfuegen(Queue q, Vertex node) { Queue qneu = new Queue(); boolean insert = true; while (!q.isEmpty()) { Vertex n = q.front(); q.dequeue(); if (n.getID().equals(node.getID())) { insert = false; } qneu.enqueue(n); } if (insert) { qneu.enqueue(node); } return qneu; } ``` Die Hilfsmethode, die das nicht-doppelte Einfügen in die Schlange realisiert, sei hier der Vollständigkeit halber mit angegeben: ```java private Queue nichtDoppeltEinfuegen(Queue q, Vertex node) { Queue qneu = new Queue(); boolean insert = true; while (!q.isEmpty()) { Vertex n = q.front(); q.dequeue(); if (n.getID().equals(node.getID())) { insert = false; } qneu.enqueue(n); } if (insert) { qneu.enqueue(node); } return qneu; } ``` ## Backtracking Der folgende Backtracking-Algorithmus bestimmt alle Wege, die von einem gegebenen Startknoten zu einem gegebenen Zielknoten führen. Es ist leicht einzusehen, dass dieser Algorithmus exponentielle Laufzeit haben muss. Die Startmethode trifft alle Vorbereitungen, um das Backtracking in Gang zu setzen. ```java public void sucheWeg(Graph g, String vonID, String nachID) { g.setAllVertexMarks(false); Vertex vonKnoten = g.getVertex(vonID); Vertex nachKnoten = g.getVertex(nachID); List knotenliste = new List(); vonKnoten.setMark(true); // Markiere den Startknoten knotenliste.append(vonKnoten); backtrack(g, vonKnoten, nachKnoten, knotenliste); // Starte die Rekursion! } ``` Die zentrale Idee des Algorithmus besteht darin, dass ausgehend von einem bislang ermittelten Pfad mit seinem bisherigen Endknoten ein Weg beginnend mit dem bisherigen Endknoten und endend mit dem gegebenen Endknoten gefunden werden soll. Der rekursive Algoritthmus sorgt dafür, dass für jedes Zwischenergebnis (also jeden Zwischenpfad) alle weiteren möglichen Pfade gesucht werden. Dabei werden nur neue (Nachbar-)Knoten berücksichtigt, die noch nicht besucht worden sind. Auf das eigentliche Backtracking (Speichere die Länge des bislang kürzesten gefundenen Weges und verwerfe Alternativwege, deren Länge bereits größer ist.) wird hier zur Vereinfachung verzichtet. ```java private void backtrack(Graph g, Vertex vonKnoten, Vertex nachKnoten, List weg) { if (vonKnoten == nachKnoten) { // Ziel schon erreicht? String hilf = druckeWegAus(g, weg); System.out.println(hilf); } else { List nachbarKnoten = g.getNeighbours(vonKnoten); nachbarKnoten.toFirst(); while (nachbarKnoten.hasAccess()) { // Bearbeite alle Nachbarknoten Vertex knoten = nachbarKnoten.getContent(); if (!knoten.isMarked()) { knoten.setMark(true); weg.append(knoten); backtrack(g, knoten, nachKnoten, weg); // Suche ueber diesen Nachbarn weiter nach dem Ziel knoten.setMark(false); weg.toLast(); weg.remove(); } nachbarKnoten.next(); } } } ``` Zum Ausdrucken eines vollständig gefundenen Pfades vom Start- zum Endknoten wird eine Hilfsmethode verwendet, die hier der Vollständigkeit halber mit angegeben ist. ```java private String druckeWegAus(Graph g, List wegliste) { // Bestimme zunaechst die Weglaenge double wegLaenge = 0; wegliste.toFirst(); Vertex wegKnoten1 = wegliste.getContent(); wegliste.next(); while (wegliste.hasAccess()) { Vertex wegKnoten2 = wegliste.getContent(); Edge e = g.getEdge(wegKnoten1, wegKnoten2); double distanz = e.getWeight(); wegLaenge = wegLaenge + distanz; wegKnoten1 = wegKnoten2; wegliste.next(); } // Baue Zeichenkette zusammen wegliste.toFirst(); String s = wegLaenge + ": "; while (wegliste.hasAccess()) { Vertex wegKnoten = wegliste.getContent(); s = s + wegKnoten.getID()+" "; wegliste.next(); } return s+"\n"; } ```