Fachhochschule Regensburg Aufgabenblatt 8: Topologisches Sortieren Algorithmen und Datenstrukturen Name: ________________________ Aufgabensteller: Prof. Sauer Vorname: _____________________ Januar 2002 Topologisches Sortieren: Sortieren bedeutet Herstellung einer totalen (vollständigen) Ordnung. Es gibt auch Prozesse zur Herstellung von teilweisen Ordnungen, d.h.: Es gibt eine Ordnung für einige Paare dieser Elemente, aber nicht für alle. 1. Aufgabe a) Gegeben ist die binäre Relation R = {(1,2),(1,3),(2,4),(2,5),(2,6),(3,5),(3,7),(5,7),(6,7)}. Gib an, wie sich diese binäre Relation durch einen gerichteten Graphen (anschaulich) darstellen lässt. Die Relation beschreibt über die paarweisen Angaben Knotenidentifikationen. Ein Paar besteht aus einem Startknoten und ein Zielknoten, im Graphen wird dies durch einen Pfeil vom Startknoten zum Zielknoten beschrieben. Pfeile im Graphen bestimmen die Ordnungsrelation (topologische Sortierung). Die dadurch bestimmte Ordnung kann in eine lineare Ordnung eingebettet werden. b) Zeige diese lineare Ordnung (topologische Sortierung) durch Anordnung der Knoten in einer Reihe, so dass die Pfeile nach rechts zeigen. d) Wie kann man die unter b) angegebene Folge einfach auf der Konsole (über ein Rechenprogramm) darstellen? 1 Für azyklische Graphen kann man die Knoten-Identifikationen als Folge beschreiben. Bezogen auf eine Kante (Pfeil) mit Anfangsknoten-Identifikation I und Endknoten-Identifikation J, erscheint die Knoten-Identifikation I vor Knotenidentifikation J. 2. Aufgabe Gegeben ist der folgende azyklische, gerichtete Graph 2 1 4 3 Kann es zu diesem Graphen mehrere topologische Folgen geben? Ja Wenn es mehrere topologische Folgen gibt, dann gib mindestens zwei dieser topologischen Folgen an. 3. Aufgabe Gegeben ist der folgende azyklische, gerichtete Graph 1 3 2 4 5 6 7 a) Zeige, wie sich dieser Graph durch eine Adjazensliste beschreiben lässt. 2 b) Für das topologische Sortieren ist die Aufnahme eines Zählers in der Knotenbeschreibung zweckmäßig. Der Zähler soll festhalten, wie viele unmittelbare Vorgänger der Knoten hat. Hat ein Knoten keine Vorgänger, dann wird der Zähler auf 0 gesetzt. Trage in die Darstellung des Graphen zu den Knotennummern den Zähler für den unmittelbaren Vorgänger des betreffenden Knoten ein. c) Es ist zweckmäßig diesen Zähler in die Beschreibung der Knoten in einem Java-Programm, das die topologische Folge der Knoten in einem Graphen ermittelt, aufzunehmen. Gib eine Beschreibung einer Klasse Vertex an, die in Anlehnung an die Klasse Vertex im Skriptum1 die Knoten eines azyklischen, gewichteten Graphen beschreibt, der den Zähler der unmittelbaren Vorgängerknoten vorsieht. class Vertex { ______________________________________________________________________ ______________________________________________________________________ ______________________________________________________________________ ______________________________________________________________________ ______________________________________________________________________ ______________________________________________________________________ } d) Für den Aufbau des Graphen soll die Klasse Graph aus dem Skriptum herangezogen werden. Ändere diese Vorlage so ab, daß der Graph über eine Adjazensliste eindeutig beschrieben wird und je Knoten die Anzahl der Vorgängerknoten festgelegt wird. Hat ein Knoten keine Vorgänger, dann wird dieser Zähler auf 0 gesetzt. Gib die Veränderungen der Methode addEdge() bzw. getVertex() an, die den Aufbau des Graphen, wie es soeben beschrieben wurde, erreichen public void addEdge(String sourceName,String destName) { ____________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ } 1 Vgl. pr22850 bzw. Skriptum, Kapitel 2 3 // Falls vertexName nicht da ist, fuege den Knoten // mit diesem Namen in die vertexMap. // In jedem Fall: Rueckgabe des Knoten. private Vertex getVertex(String vertexName) { _________________________________________________________________ _________________________________________________________________ _________________________________________________________________ _________________________________________________________________ _________________________________________________________________ _________________________________________________________________ _________________________________________________________________ } e) Den Kern der Klasse Graph bildet die Methode topsort(). Diese Methode ist durch die folgende Pseudocode-Darstellung gegeben: void topsort() { Queue q int zaehler = 0; Vertex v, w; Q = new Queue(); for each vertex v if (v.indegree2 == 0) q = new Queue(); while (!q.isEmpty()) { v = q.dequeue(); zaehler++; for each w adjacent to v if (--w.indegree == 0) q.enqueue(w); } if (zaehler != anzahlKnoten) System.out.println(“Fehler: Zyklus gefunden”); } Zur Bestimmung der gewünschten topologischen Folge wird mit den Knotenpunktnummern begonnen, deren Zähler den Wert 0 enthalten. Sie verfügen über keinen Vorgänger und erscheinen in der topologischen Folge an erster Stelle. Überführe die Pseudocode-Darstellung der Methode topsort() in Java-Quellcode und baue diese Methode in die Klasse Graph ein. public void topsort() { _______________________________________________________________ _______________________________________________________________ 2 indegree ist der Zähler für die jeweilige Anzahl von Vorgängerknoten 4 _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ _______________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ 5 __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________ } f) Schreibtischtest. Die folgende Tabelle soll die Veränderung des Zählers für unmittelbare Vorgänger zeigen und über die Knotenidentifikationen das Ein- bzw. Ausgliedern aus der Schlange (Queue) q. Gib mit den Daten des in Aufgabe 3 vorgebenen Graphen die Tabelle an! Vertex 1 2 3 4 5 6 7 Enqueue Dequeue 1 2 3 4 5 6 7 g) Übersetze und teste das Programm Lösungen 1. Aufgabe a) Gegeben ist die binäre Relation R = {(1,2),(1,3),(2,4),(2,5),(2,6),(3,5),(3,7),(5,7),(6,7)}. Gib an, wie sich diese binäre Relation durch einen gerichteten Graphen (anschaulich) darstellen lässt. 1 2 3 5 4 6 7 Die Relation beschreibt über die paarweisen Angaben Knotenidentifikationen. Ein Paar besteht aus einem Startknoten und ein Zielknoten, im Graphen wird dies durch einen Pfeil vom Startknoten zum Zielknoten beschrieben. 6 Pfeile im Graphen bestimmen die Ordnungsrelation (topologische Sortierung). Die dadurch bestimmte Ordnung kann in eine lineare Ordnung eingebettet werden. b) Zeige diese lineare Ordnung (topologische Sortierung) durch Anordnung der Knoten in einer Reihe, so dass die Pfeile nach rechts zeigen. 1 2 4 6 3 5 7 d) Wie kann man die unter b) angegebene Folge einfach auf der Konsole (über ein Rechenprogramm) darstellen? 1 2 4 5 6 3 5 7 Für azyklische Graphen kann man die Knoten-Identifikationen als Folge beschreiben. Bezogen auf eine Kante (Pfeil) mit Anfangsknoten-Identifikation I und Endknoten-Identifikation J, erscheint die Knoten-Identifikation I vor Knotenidentifikation J. 2. Aufgabe Gegeben ist der folgende azyklische, gerichtete Graph 7 2 1 4 3 Kann es zu diesem Graphen mehrere topologische Folgen geben? Ja Wenn es mehrere topologische Folgen gibt, dann gib mindestens zwei dieser topologischen Folgen an. 1 2 1 3 3 4 2 4 3. Aufgabe Gegeben ist der folgende azyklische, gerichtete Graph 0 1 1 2 2 3 3 1 4 5 3 2 6 7 a) Zeige, wie sich dieser Graph durch eine Adjazensliste beschreiben lässt. 1 2 3 2 4 5 3 6 4 6 7 5 4 7 4 3 6 8 b) Für das topologische Sortieren ist die Aufnahme eines Zählers in der Knotenbeschreibung zweckmäßig. Der Zähler soll festhalten, wie viele unmittelbare Vorgänger der Knoten hat. Hat ein Knoten keine Vorgänger, dann wird der Zähler auf 0 gesetzt. Trage in die Darstellung des Graphen zu den Knotennummern den Zähler für den unmittelbaren Vorgänger des betreffenden Knoten ein. c) Es ist zweckmäßig diesen Zähler in die Beschreibung der Knoten in einem Java-Programm, das die topologische Folge der Knoten in einem Graphen ermittelt, aufzunehmen. Gib eine Beschreibung einer Klasse Vertex an, die in Anlehnung an die Klasse Vertex im Skriptum3 die Knoten eines azyklischen, gewichteten Graphen beschreibt, der den Zähler der unmittelbaren Vorgängerknoten vorsieht. class Vertex { String name; LinkedList adj; int // Name des Knoten // Benachbarte Knoten indegree = 0; // Ingrad des Knoten // Konstruktor public Vertex( String nm ) { name = nm; adj = new LinkedList(); } } d) Für den Aufbau des Graphen soll die Klasse Graph aus dem Skriptum herangezogen werden. Ändere diese Vorlage so ab, daß der Graph über eine Adjazensliste eindeutig beschrieben wird und je Knoten die Anzahl der Vorgängerknoten festgelegt wird. Hat ein Knoten keine Vorgänger, dann wird dieser Zähler auf 0 gesetzt. Gib die Veränderungen der Methode addEdge() bzw. getVertex() an, die den Aufbau des Graphen, wie es soeben beschrieben wurde, erreichen public void addEdge(String sourceName,String destName) { Vertex v = getVertex(sourceName); Vertex w = getVertex(destName); w.indegree++; v.adj.add(w); } // Falls vertexName nicht da ist, fuege den Knoten // mit diesem Namen in die vertexMap. // In jedem Fall: Rueckgabe des Knoten. private Vertex getVertex(String vertexName) { Vertex v = (Vertex) vertexMap.get(vertexName); 3 Vgl. pr22850 bzw. Skriptum, Kapitel 2 9 if( v == null ) { v = new Vertex(vertexName); vertexMap.put(vertexName, v); } return v; } e) Den Kern der Klasse Graph bildet die Methode topsort(). Diese Methode ist durch die folgende Pseudocode-Darstellung gegeben: void topsort() { Queue q; int zaehler = 0; Vertex v, w; Q = new Queue(); for each vertex v if (v.indegree4 == 0) q = new Queue(); while (!q.isEmpty()) { v = q.dequeue(); zaehler++; for each w adjacent to v if (--w.indegree == 0) q.enqueue(w); } if (zaehler != anzahlKnoten) System.out.println(“Fehler: Zyklus gefunden”); } Zur Bestimmung der gewünschten topologischen Folge wird mit den Knotenpunktnummern begonnen, deren Zähler den Wert 0 enthalten. Sie verfügen über keinen Vorgänger und erscheinen in der topologischen Folge an erster Stelle. Überführe die Pseudocode-Darstellung der Methode topsort() in Java-Quellcode und baue diese Methode in die Klasse Graph ein. public void topsort() { Vertex v = null, w; int zaehler = 0; // Schlange fuer breadth search first LinkedList q = new LinkedList( ); for (Iterator itr = vertexMap.values().iterator(); itr.hasNext();) { v = ((Vertex) itr.next()); 4 indegree ist der Zähler für die jeweilige Anzahl von Vorgängerknoten 10 if (v.indegree == 0) { // enqueue: Einreihen in die Schlange q.addLast(v); // System.out.println(v.name); } } if (v == null) { System.out.println("Fehler: Kein vorgaengerloser Knoten"); System.exit(0); } while( !q.isEmpty( ) ) { // dequeue: Entnehmen aus der Schlange v = (Vertex) q.removeFirst( ); zaehler++; System.out.print(v.name + " "); for( Iterator itr = v.adj.iterator( ); itr.hasNext( ); ) { w = (Vertex) itr.next( ); if(--w.indegree == 0) { q.addLast( w ); } } } // System.out.println(vertexMap.size()); // System.out.println(zaehler); if (zaehler != vertexMap.size()) { System.out.println("Fehler: Zyklus gefunden"); System.exit(0); } 11 } f) Schreibtischtest. Die folgende Tabelle soll die Veränderung des Zählers für unmittelbare Vorgänger zeigen und über die Knotenidentifikationen das Ein- bzw. Ausgliedern aus der Schlange (Queue) q. Gib mit den Daten des in Aufgabe 3 vorgebenen Graphen die Tabelle an! Vertex 1 2 3 4 5 6 7 Enqueue Dequeue 1 0 1 2 3 1 3 2 1 1 2 0 0 1 2 1 3 2 2 2 3 0 0 1 1 0 3 2 5 5 4 0 0 1 0 0 3 1 4 4 5 0 0 0 0 0 2 0 3,7 3 6 0 0 0 0 0 1 0 7 7 0 0 0 0 0 0 0 6 6 g) Übersetze und teste das Programm import java.util.*; import java.io.*; class Vertex { String name; // Name des Knoten LinkedList adj; // Benachbarte Knoten int indegree = 0; // Ingrad des Knoten // int dist; // Kosten // Vertex path; // Vorheriger Knoten auf dem kuerzesten Pfad // Konstruktor public Vertex( String nm ) { name = nm; adj = new LinkedList(); } } public class Graph { // Abbildung der Knoten private HashMap vertexMap = new HashMap(); // Methode Hinzufuegen Kante public void addEdge(String sourceName,String destName) { Vertex v = getVertex(sourceName); Vertex w = getVertex(destName); w.indegree++; v.adj.add(w); } // Falls vertexName nicht da ist, fuege den Knoten // mit diesem Namen in die vertexMap. // In jedem Fall: Rueckgabe des Knoten. private Vertex getVertex(String vertexName) { Vertex v = (Vertex) vertexMap.get(vertexName); if( v == null ) { v = new Vertex(vertexName); vertexMap.put(vertexName, v); } return v; } /* * Herstellung der Ordnung von Knoten in einem * gerichteten, azyklischen Graphen 12 */ public void topsort() { Vertex v = null, w; int zaehler = 0; // Schlange fuer breadth search first LinkedList q = new LinkedList( ); for (Iterator itr = vertexMap.values().iterator(); itr.hasNext();) { v = ((Vertex) itr.next()); if (v.indegree == 0) { // enqueue: Einreihen in die Schlange q.addLast(v); // System.out.println(v.name); } } if (v == null) { System.out.println("Fehler: Kein vorgaengerloser Knoten"); System.exit(0); } while( !q.isEmpty( ) ) { // dequeue: Entnehmen aus der Schlange v = (Vertex) q.removeFirst( ); zaehler++; System.out.print(v.name + " "); for( Iterator itr = v.adj.iterator( ); itr.hasNext( ); ) { w = (Vertex) itr.next( ); if(--w.indegree == 0) { q.addLast( w ); } } } // System.out.println(vertexMap.size()); // System.out.println(zaehler); if (zaehler != vertexMap.size()) { System.out.println("Fehler: Zyklus gefunden"); System.exit(0); } } /* * Eine main()-Routine, die * 1. Eine Datei liest, die Kanten enthaelt * (Der Dateiname wird als Parameter ueber die * Kommandozeile eingegeben); * 2. den Graphen aufbaut; * 3. wiederholt 2 Knoten anfordert und * den Algorithmus zur Berechnung des topologischen Sort * in Gang setzt. * Die Datei besteht aus Zeilen mit dem Format * Quelle (source) Ziel (destination). */ public static void main(String [] args) { Graph g = new Graph( ); try { FileReader din = new FileReader(args[0]); BufferedReader graphFile = new BufferedReader(din); // Lies die Kanten und fuege ein String zeile; while( ( zeile = graphFile.readLine() ) != null ) 13 { StringTokenizer st = new StringTokenizer(zeile); try { if( st.countTokens( ) != 2 ) throw new Exception( ); String source = st.nextToken( ); String dest = st.nextToken( ); g.addEdge(source, dest); } catch( Exception e ) { System.err.println( e + " " + zeile ); } } } catch( Exception e ) { System.err.println( e ); } System.out.println( "File read" ); // System.out.print(g.vertexMap); // System.out.println(g.vertexMap.size()); g.topsort(); System.out.println(); } } 14