Algorithmik II Aufgabe 20 8. Übung Graphen Gegeben sei der folgende Graph: 3 A A A 4 A @ A @A @ AU R @ -2 - 5 1 6 a) Geben Sie an, wie der Graph durch eine Adjazenzmatrix und durch Adjazenzlisten gespeichert wird. Lösungsvorschlag: Ein gerichteter Graph G = (V, E) ist gegeben durch zwei Mengen V und E mit E ⊆ V × V . (a) V : Knotenmenge, n =| V | (b) E: Kantenmenge, e =| E | • Adjazenzmatrix: Ein Graph G wird in einer n × n–Matrix gespeichert, wobei ein Matrixelement ai,j (1 ≤ i, j ≤ n) eine 1 enthält, falls G eine Kante vom Knoten i zum Knoten j enthält, ansonsten ist das Matrixelement mit einer 0 besetzt. 1 0 0 0 0 0 0 1 2 3 4 5 6 2 1 0 0 0 0 0 3 0 1 0 0 0 0 4 0 1 0 0 0 0 5 0 1 1 1 0 1 6 0 0 0 0 0 0 • Adjazenzlisten: Jedem Knoten i des Graphen G wird eine lineare Liste zugeordnet. Diese Liste enthält für jeden Knoten j, der mit i durch eine Kante direkt verbunden ist (d. h. (i, j) ∈ E), einen Eintrag. 1 ? 2 2 ? 4 3 ? 5 4 ? 5 5 6 ? 5 ? 3 ? 5 16. Juni - 20. Juni 2003 -1- Sommersemester 2003 Algorithmik II 8. Übung b) Bestimmen Sie für den Graphen eine topologische Sortierung. Lösungsvorschlag: (a) Topologisches Sortieren: Erster Entwurf Eine topologische Sortierung ordnet jedem Knoten des Graphen eine natürliche Zahl zwischen 1 und n zu, so daß für jede Kante der Quellknoten eine kleinere Zahl als der Zielknoten hat. Der Algorithmus ordnet zunächst jedem Knoten die minimal mögliche Ordnungszahl“ ” (1) zu. Daran anschließend werden in einer Schleife über alle Kanten die Ordnungs” zahlen“ erhöht, solange noch Unstimmigkeiten“ bestehen, d. h. solange es eine Kante ” gibt, deren Zielknoten eine kleinere Zahl als der Quellknoten v hat. 1 Initialisierung 1 v=1 v=2 v=3 v=4 v=5 v=6 v=1 .. . 2 1 2 3 1 4 1 5 1 3 3 3 4 6 1 v=6 Der zweite Schleifendurchlauf über alle Kanten führt zu keiner weiteren Änderung. (b) Topologisches Sortieren: Schnelleres Verfahren Mit indegree(v) (Eingangsgrad(v)) bezeichnet man die Anzahl der Kanten, die den Knoten v als Zielknoten haben: indegree(v) =| {v 0 | (v 0 , v) ∈ E} | Der Algorithmus betrachtet zunächst einen Knoten v mit Eingangsgrad 0 und ordnet diesem die Ordnungszahl“ 1 zu. Daran anschließend wird der Knoten v entfernt“ und ” ” der Algorithmus arbeitet nun mit einen um einen Knoten verkleinerten Graphen und der Ordnungszahl“ 2 weiter, usw. Das Entfernen“ eines Knotens wird dadurch realisiert, ” ” daß die Eingangsgrade seiner direkten Nachbarn um den Wert 1 verringert werden. Um einen Knoten mit Eingangsgrad 0 schnell zu finden, wird eine zusätzliche Menge zeroin eingeführt, die all die Knoten mit aktuellem Eingangsgrad 0 enthält. Der Algorithmus vergibt jede Zahl zwischen 1 und n genau einmal. Dies hat zur Folge, daß jeder Knoten eine andere Ordnungszahl“ hat. Wird eine Ordnungszahl“ vergeben, die größer als die ” ” Knotenanzahl n ist, so ist eine topologische Sortierung des Graphen unmöglich. • Initialisierung: v 1 2 3 4 – indegree(v) 0 1 1 1 – zeroin = {1, 6}, i = 0 • Schleifendurchlauf mit v = 1: 16. Juni - 20. Juni 2003 5 6 4 0 -2- Sommersemester 2003 Algorithmik II • • • • • 8. Übung – zeroin = {6}, i = 1, ord(1) = 1 – (1, 2) ∈ E =⇒ indegree(2) = 0, zeroin ={2, 6} Schleifendurchlauf mit v = 2: – zeroin = {6}, i = 2, ord(2) = 2 – (2, 3) ∈ E =⇒ indegree(3) = 0, zeroin ={3, 6} – (2, 4) ∈ E =⇒ indegree(4) = 0, zeroin ={4, 3, 6} – (2, 5) ∈ E =⇒ indegree(5) = 3 Schleifendurchlauf mit v = 4: – zeroin = {3, 6}, i = 3, ord(4) = 3 – (4, 5) ∈ E =⇒ indegree(5) = 2 Schleifendurchlauf mit v = 3: – zeroin = {6}, i = 4, ord(3) = 4 – (3, 5) ∈ E =⇒ indegree(5) = 1 Schleifendurchlauf mit v = 6: – zeroin = ∅, i = 5, ord(6) = 5 – (6, 5) ∈ E =⇒ indegree(5) = 0, zeroin ={5} Schleifendurchlauf mit v = 5: – zeroin = ∅, i = 6, ord(5) = 6 c) Bestimmen Sie mit Hilfe des Algorithmus zum systematischen Durchlaufen eines Graphen alle Knoten, die von Knoten 1 aus erreichbar sind. Als Auswahlstrategie soll LIFO verwendet werden. Lösungsvorschlag: Der Algorithmus dient dazu, die Menge S aller von einem Knoten aus erreichbaren Knoten zu bestimmen. Hierbei soll keine Kante mehrfach durchlaufen werden. Um noch nicht besuchte Kanten effizient bestimmen zu können, wird neben der Menge S eine weitere Menge R eingeführt. R enthält all diejenigen Knoten aus S, von denen noch nicht besuchte Kanten ausgehen. R bezeichnet man auch als den Rand von S. Für jeden Knoten v aus R gibt es eine Liste p[v], die die noch nicht besuchten Kanten enthält, die von v ausgehen. Ist der Graph mit Hilfe von Adjazenzlisten gespeichert, so kann p[v] einfach mit adj liste[v] initialisiert werden. Wird eine Kante bearbeitet, deren Zielknoten noch nicht in S ist, so wird dieser Knoten sowohl in S als auch in R eingetragen. LIFO–Auswahlstrategie bedeutet, daß als nächstes immer eine Kante des Knotens betrachtet wird, der zuletzt in die Menge R eingetragen wurde. Die Kanten eines Knotens v werden in der Reihenfolge bearbeitet, die durch p[v] gegeben ist. • Initialisierung: – S = {1}, R = {1} – p[1] = adj liste[1] • Schleifendurchlauf für v = 1: – (1, 2) ist die nächste Kante von p[1] – setze p[1] ein Element weiter, d. h. auf den Abschlußzeiger von adj liste[1] – S = {2, 1}, R = {2, 1}, p[2] = adj liste[2] 16. Juni - 20. Juni 2003 -3- Sommersemester 2003 Algorithmik II 8. Übung • Schleifendurchlauf für v = 2: – (2, 3) ist die nächste Kante von p[2] – setze p[2] ein Element weiter, d. h. auf den Knoten 3 – S = {3, 2, 1}, R = {3, 2, 1}, p[3] = adj liste[3] • Schleifendurchlauf für v = 3: – (3, 5) ist die nächste Kante von p[3] – setze p[3] ein Element weiter, d. h. auf den Abschlußzeiger von adj liste[3] – S = {5, 3, 2, 1}, R = {5, 3, 2, 1}, p[5] = adj liste[5] • Schleifendurchlauf für v = 5: – p[5] ist undefiniert =⇒ R = {3, 2, 1} • Schleifendurchlauf für v = 3: – p[3] ist undefiniert =⇒ R = {2, 1} • Schleifendurchlauf für v = 2: – (2, 4) ist die nächste Kante von p[2] – setze p[2] ein Element weiter, d. h. auf den Knoten 4 – S = {4, 5, 3, 2, 1}, R = {4, 2, 1}, p[4] = adj liste[4] • Schleifendurchlauf für v = 4: – (4, 5) ist die nächste Kante von p[5] – setze p[4] ein Element weiter, d. h. auf den Abschlußzeiger von adj liste[4] – R und S ändern sich nicht, da der Knoten 5 bereits in S vorhanden ist • Schleifendurchlauf für v = 4: – p[4] ist undefiniert =⇒ R = {2, 1} • Schleifendurchlauf für v = 2: – (2, 5) ist die nächste Kante von p[2] – setze p[2] ein Element weiter, d. h. auf den Abschlußzeiger von adj liste[2] – R und S ändern sich nicht, da der Knoten 5 bereits in S vorhanden ist • Schleifendurchlauf für v = 2: – p[2] ist undefiniert =⇒ R = {1} • Schleifendurchlauf für v = 1: – p[1] ist undefiniert =⇒ R = ∅ Vom Knoten 1 sind somit die Knoten 4, 5, 3, 2 und 1 erreichbar. 3 1 16. Juni - 20. Juni 2003 - A A A 4 A A A AU 5 2 -4- Sommersemester 2003 Algorithmik II 8. Übung Rechnerübung 15 Graphen a) Schreiben Sie eine Klasse AdjList, die es Ihnen ermöglicht, einen gerichteten Graphen in einer Adjazenzliste zu speichern sowie eine Klasse AdjMatrix, mit der ein gerichteter Graph durch eine Adjazenzmatrix gespeichert werden kann. Dem Kontruktor beider Klassen soll jeweils die Anzahl der Knoten des Graphen als Parameter übergeben werden. Die Speicherung der von den einzelnen Knoten ausgehenden Kanten soll in der Klasse AdjList mit Hilfe einer verketteten Liste realisiert werden. Weiterhin sollen beide Graphen eine Methode insertEdge(int from, int to) zur Verfügung stellen, mit der Sie eine Kante vom Knoten from zum Knoten to einfügen können. Dabei soll die Benennung der Knoten bei 1 beginnen. Implementieren Sie auch die Methode toString() für beide Klassen. Lösungsvorschlag: class AdjListElement { private int nodeNumber; private AdjListElement next; public AdjListElement() { next = null; nodeNumber = -1; } public AdjListElement(int node) { next = null; nodeNumber = node; } public int getNodeNumber() { return nodeNumber; } public void setNodeNumber(int number) { nodeNumber = number; } public AdjListElement getNext() { return next; } public void setNext(AdjListElement next) { this.next = next; } } public class AdjList { public AdjListElement nodes[]; public AdjList(int numNodes) { nodes = new AdjListElement[numNodes]; } public String toString() { String s = ""; for(int n=0; n < nodes.length; n++) { s += (n + 1) + ": "; 16. Juni - 20. Juni 2003 -5- Sommersemester 2003 Algorithmik II 8. Übung if(nodes[n] == null) { s += "*\n"; continue; } AdjListElement currNode = nodes[n]; do { s += currNode.getNodeNumber() + " - "; currNode = currNode.getNext(); } while(currNode != null); s += "*\n"; } return s; } // neue Kante einfuegen public void insertEdge(int from, int to) { if(from < 1 || from > nodes.length || to < 1 || to > nodes.length) { System.out.println("Angegebener Knoten existiert nicht!"); return; } AdjListElement currNode = nodes[from-1]; // erste Kante if(currNode == null) { nodes[from-1] = new AdjListElement(to); return; } // neue Kante am Ende anfuegen while(currNode.getNext() != null) { currNode = currNode.getNext(); } currNode.setNext(new AdjListElement(to)); } } class AdjMatrix { public boolean matrix[][]; final static boolean EDGE = true; final static boolean NO_EDGE = false; 16. Juni - 20. Juni 2003 -6- Sommersemester 2003 Algorithmik II 8. Übung AdjMatrix(int dimension) { matrix = new boolean[dimension][dimension]; } public String toString() { String s = ""; for(int n=0; n < matrix.length; n++) { s += (n + 1) + ": "; for(int edgeTo=0; edgeTo < matrix.length; edgeTo++) { if(matrix[n][edgeTo] == EDGE) { s += (edgeTo + 1) + " - "; } } s += "*\n"; } return s; } // neue Kante einfuegen public void insertEdge(int from, int to) { if(from < 1 || from > matrix.length || to < 1 || to > matrix.length) { System.out.println("Angegebener Knoten existiert nicht!"); return; } matrix[from -1][to -1] = EDGE; } } b) * Schreiben Sie für die Klasse AdjMatrix eine Methode AdjMatrixToAdjList(), die ein der Matrizendarstellung entsprechendes AdjList-Objekt zurückliefert sowie für die Klasse AdjList eine Methode AdjListToAdjMatrix(). Lösungsvorschlag: public AdjMatrixToAdjList() { AdjList graph = new AdjList(matrix.length); for(int source = 0; source < matrix.length; source++) { for(int goal = 0; goal < matrix.length; goal++) { if(matrix[source][goal] == EDGE) { graph.insert(source + 1, goal + 1); } } } 16. Juni - 20. Juni 2003 -7- Sommersemester 2003 Algorithmik II 8. Übung return graph; } public AdjListToAdjMatrix() { AdjMatrix graph = new AdjMatrix(nodes.length); for(int n = 0; n < nodes.length; n++) { AdjListElement current = nodes[n]; while(current != null) { graph.insert(n + 1, current.getNodeNumber()); current = current.getNext(); } } return graph; } c) Schreiben Sie für die Klasse AdjList eine Methode topologicalSort(), die die topologische Sortierung des Graphen unter Verwendung des in der Vorlesung vorgestellten verbesserten Algorithmus zum topologischen Sortieren ausgibt. Weiterhin soll die Methode ausgeben, ob der Graph zyklenfrei ist. Testen Sie Ihre Implementierung anhand einiger Beispiele aus der Vorlesung. Lösungsvorschlag: public class AdjList { ... public void topologicalSort() { int i, indegree[], ord[]; ArrayList zeroin = new ArrayList(); indegree = new int[nodes.length]; ord = new int [nodes.length]; for(i=0; i < nodes.length; i++) indegree[i] = 0; // Zunaechst den indegree jedes Knotens ermitteln for(i=0; i < nodes.length; i++) { AdjListElement currNode = nodes[i]; while(currNode != null) { indegree[currNode.getNodeNumber() -1]++; currNode = currNode.getNext(); } } // jetzt die Menge zeroin ermitteln for(i=0; i < nodes.length; i++) { 16. Juni - 20. Juni 2003 -8- Sommersemester 2003 Algorithmik II 8. Übung if(indegree[i] == 0) zeroin.add(new Integer(i)); } i=0; // Jetzt ueber alle Elemente von zeroin gehen while(!zeroin.isEmpty()) { // Waehle das erste Element v aus zeroin Integer currZeroinNode = (Integer) zeroin.get(0); int zeroinNode = currZeroinNode.intValue(); // nimm v aus zeroin heraus zeroin.remove(currZeroinNode); ord[zeroinNode] = ++i; AdjListElement currNode = nodes[zeroinNode]; while(currNode != null) { indegree[currNode.getNodeNumber() -1]--; if(indegree[currNode.getNodeNumber() -1] == 0) zeroin.add(new Integer(currNode.getNodeNumber() -1)); currNode = currNode.getNext(); } } if(i >= nodes.length) System.out.println("Der Graph ist zyklenfrei!"); else { System.out.println("Der Graph ist nicht zyklenfrei!"); return; } // noch sortieren int sortedList[] = new int[nodes.length]; for(i=0; i < nodes.length; i++) { // Bestimmen des Knotens mit dem naechstgroessten ord-Wert for(int n=0; n < nodes.length; n++) if(ord[n] == i+1) { sortedList[i] = n; break; } } // Ausgabe: 16. Juni - 20. Juni 2003 -9- Sommersemester 2003 Algorithmik II 8. Übung System.out.println("Topologische Sortierung:"); for(int n=0; n < nodes.length; n++) { System.out.println(sortedList[n] +1); } } } 16. Juni - 20. Juni 2003 - 10 - Sommersemester 2003