Wolfgang Hönig / Andreas Ecke WS 09/10 1 topologisches Sortieren 1.1 Überblick 1. Solange noch Knoten vorhanden: a) Suche Knoten v, zu dem keine Kante führt (Falls nicht vorhanden ⇒ keine topologische Sortierung möglich) b) v an Ausgabeliste anhängen c) v und alle Kanten welche von v ausgehen löschen 1.2 Grundidee Häufig können Daten nicht eindimensional in Listen dargestellt werden, sondern es sind kompliziertere Datenstrukturen nötig. Wenn auch Bäume nicht ausreichen, bietet sich eine Darstellung als Graph an. Diese bestehen aus Knoten (mit einer Beschriftung) und Kanten, welche je zwei Knoten (gegebenenfalls mit Richtung) verbinden können. Bäume (und damit auch Listen) sind daher offenbar Spezialfälle von Graphen. Offenbar sind auch für diese komplexeren Strukturen Sortier- und Suchalgorithmen notwendig. Im Folgenden wird daher die topologische Sortierung sowie die Tiefen- und Breitensuche erläutert. Ein gerichteter Graph ist sehr gut geeignet, um Abhängigkeiten darzustellen. Beispielsweise benötigt man zum Bestehen der Logikklausur Wissen über Aussagen- und Prädikatenlogik. Die Prädikatenlogik setzt die Aussagenlogik voraus. Damit man weiß, worauf man sich einlässt, sollte man Lord of Logic sehen und zur Vorbereitung auf die Prädikatenlogik bietet sich hoelli.avi an. Insgesamt lässt sich dies als Graph darstellen: Aussagenlogik Prädikatenlogik Klausur Lord of Logic hoelli.avi Die Frage der topologischen Sortierung ist nun: In welcher Reihenfolge sollte man lernen, um die Klausur zu bestehen? (Zum Beispiel könnte man zuerst Lord of Logic schauen, dann die Aussagenlogik lernen, danach hoelli.avi anschauen, die Prädikatenlogik lernen und schließlich zur Klausur gehen.) 1.3 Erklärung am Beispiel Folgender gerichteter Graph soll topologisch sortiert werden: 5 2 6 4 1 3 Ausgabeliste: Zuerst sucht man einen Knoten in dem Graphen, zu dem keine Kante führt. In diesem Fall gehen von Knoten 6 und 1 je zwei Kanten aus, aber es führt keine Kante zu den beiden Knoten. Wir können also entweder den Knoten 6 oder 1 wählen. Im folgenden Bild 1 Wolfgang Hönig / Andreas Ecke WS 09/10 wurde Knoten 6 gewählt und deshalb an die Ausgabeliste drangehängt. Da dieser Knoten nun komplett abgearbeitet wurde, kann er ohne weiteres inklusive der zugehörigen Kanten gelöscht werden: 5 2 4 1 3 Ausgabeliste: 6 Jetzt wird wieder ein Knoten gesucht, zu dem keine Kanten führen (2 oder 1 erfüllen die Bedingung). Nach dem Anhängen an die Liste und löschen von Knoten 2 ergibt sich: 5 4 1 3 Ausgabeliste: 6, 2 Diesmal fällt die Auswahl auf Knoten 3: 5 4 1 Ausgabeliste: 6, 2, 3 Jetzt bleibt nur noch Knoten 1 übrig, welcher die Eigenschaft erfüllt: 5 4 Ausgabeliste: 6, 2, 3, 1 Nun ist nur noch Knoten 4 auswählbar: 5 Ausgabeliste: 6, 2, 3, 1, 4 Es ergibt sich schließlich: 6,2,3,1,4,5. Aus dem Text ist ersichtlich, dass die Wahl des Knotens v teilweise beliebig ist (Am Anfang zum Beispiel 1 oder 6). Deshalb gibt es auch mehrere korrekte Lösungen: 6,2,3,1,4,5 oder 6,1,2,3,4,5 oder 6,2,1,3,4,5 oder 1,6,2,3,4,5. 1.3.1 Eigenschaften Komplexität: O(n + m) mit n . . . Anzahl Knoten, m . . . Anzahl Kanten 2 2 Tiefensuche (DFS) 2.1 Aufgabentyp Der gerichtete Graph G = (V, E) sei durch folgende Darstellung gegeben: [...] Wenden Sie auf den Graphen G den DFS-Algorithmus mit dem Startknoten [...] an, und bestimmen Sie auf diese Weise einen depth first forest. Geben Sie mindestens drei unterschiedliche Lösungen an. Zwischenschritte zu den Lösungen brauchen Sie nicht anzugeben. 2.2 Überblick 1. Startknoten v übernehmen, falls noch nicht besucht 2. für jeden nicht besuchten Nachfolger u in v a) Kante von v nach u b) Tiefensuche(u) (rekursiv!) 3. Falls Tiefensuche komplett beendet (inklusive rekursiver Abbau): Tiefensuche(u) für einen noch nicht besuchten Knoten u 2.3 Grundidee Große Graphen sind meist nicht in ihrem gesamten Ausmaß bekannt (d.h. die komplette Menge der Knoten und Kanten ist nicht bekannt), sondern es existiert ein bekannter Knoten. Jeder Knoten wiederum kennt seine Nachfolger (also die Kanten). Zum Beispiel kann das Internet als Graph aufgeschrieben werden, wobei nur ein Startknoten bekannt ist (der eigene Computer) sowie einige Kanten (Nachbarcomputer). Soll nun ein bestimmter Knoten gesucht werden, muss natürlich verhindert werden, dass ein Knoten mehrmals besucht wird. Ansonsten könnte die Suchanfrage durch Zyklen in dem Graphen ewig andauern. Ziel einer Suche auf Graphen ist also primär doppeltes Besuchen von Knoten zu vermeiden. Letztendlich gibt es zwei verschiedene Strategien: Tiefen- und Breitensuche. Bei der Tiefensuche wird ein Nachfolgerknoten bevorzugt behandelt, während bei der Breitensuche (nahezu) alle Nachfolger gleichmäßig abgearbeitet werden. Die eigentliche Suche rückt bei uns etwas in den Hintergrund - es wird jeweils nur der Suchbaum (bzw. Suchwald) betrachtet. Das ist die Datenstruktur, welche im worst-case (Element nicht gefunden) entstehen würde. Um eine vollständige Suche herzustellen ist nur bei jedem neuen Element ein jeweiliger Vergleich nötig. Prüfungsrelevant ist jedoch nur der Wald, welcher bei der Tiefensuche auf gerichteten Graphen entsteht, beziehungsweise der Baum, welcher bei der Breitensuche auf ungerichteten Graphen entsteht. 2.4 Erklärung am Beispiel Folgender gerichteter Graph sei gegeben: 3 Wolfgang Hönig / Andreas Ecke 6 5 3 1 WS 09/10 4 7 2 Als erstes kann ein Startknoten ausgesucht werden (in der Klausur ist dieser meist vorgegeben). Hier soll Knoten 1 gewählt werden. Dieser hat die Nachfolgerknoten 2, 4 und 6. Alle drei Nachfolger wurden noch nicht besucht. Also wählen wir einen Nachfolger - z.B. die 6 - aus, ziehen eine Kante von 1 nach 6, rufen Tiefensuche(6) auf und erhalten: 1 6 5 6 3 1 4 7 2 Die 6 hat keinen Nachfolger, sodass kein weiterer rekursiver Aufruf erfolgen kann. Also betrachten wir wieder eine Ebene obendrüber den Knoten 1. Dieser hat jetzt noch zwei nicht besuchte Nachfolger: 2 und 4. Wir wählen wiederum einen Nachfolger aus (hier die 2), ziehen eine Kante von 1 nach 2 und rufen Tiefensuche(2) auf. Damit ergibt sich: 1 6 5 6 3 1 4 2 7 2 Der Knoten 2 hat nur einen noch nicht besuchten Nachfolger: die 7. Damit erhalten wir: 1 6 5 6 3 1 4 2 7 7 2 Die 7 hat wiederum nur einen Nachfolger (die 6). Allerdings wurde diese schon besucht, sodass kein rekursiver Aufruf erfolgen kann. Wir betrachten also wieder eine Ebene niedriger: den Knoten 2. Auch dieser Knoten hat keine noch nicht besuchten Nachfolger, so dass wiederm eine Ebene tiefer betrachtet wird: die 1. Hier ist noch ein nicht besuchter Knoten übrig, nämlich die 4, sodass sich ergibt: 1 6 5 6 3 1 4 2 4 7 7 2 Die 4 hat keine noch nicht besuchten Nachfolger. Also betrachten wir wieder die nächsthöhere Ebene. Auch die 1 hat keine noch nicht besuchten Nachfolger mehr. Also muss die Tiefensuche noch einmal für einen anderen Startknoten aufgerufen werden. In diesem Beispiel bietet sich die 5 an: 1 6 6 3 1 4 5 5 2 4 7 2 7 4 Wolfgang Hönig / Andreas Ecke WS 09/10 Der Knoten 5 hat nur einen noch nicht besuchten Nachfolger: die 3: 1 6 6 3 1 5 5 2 4 3 7 4 7 2 Die 3 hat keine nicht besuchten Knoten mehr, ebenso wenig wie die Ebene obendrüber (Knoten 5). Also wurden alle Knoten mit der Tiefensuche erschlossen und der Algorithmus ist fertig. In der Klausur ist Baum und kein gerichteter Graph gefordert. Damit ergibt sich als korrekte Schreibweise: 5 1 3 6 2 4 7 Auch hier sind wieder sehr viele verschiedene Lösungen möglich, je nachdem wie die Knotenreihenfolge gewählt wird. Bei Startknoten 1 ist zum Beispiel auch folgende Lösungen 5 3 5 korrekt: 1 oder 1 3 6 4 6 4 2 2 7 7 2.4.1 Eigenschaften Komplexität: O(n + m) mit n . . . Anzahl Knoten, m . . . Anzahl Kanten 5 3 Breitensuche (BFS) 3.1 Aufgabentyp Der ungerichtete Graph G = (V, E) sei durch folgende Darstellung gegeben: [...] Wenden Sie auf den Graphen G den BFS-Algorithmus mit dem Startknoten [...] an, und bestimmen Sie auf diese Weise einen breadth first tree. Geben Sie mindestens drei unterschiedliche Lösungen an. Zwischenschritte zu den Lösungen brauchen Sie nicht anzugeben. 3.2 Überblick 1. Startknoten an Warteschlänge hängen 2. Solange Warteschlage nicht leer: Element v aus der Warteschlage entnehmen und für jeden nicht besuchten Nachfolger u von v: • Kante von v nach u • u an Warteschlange hängen 3.3 Grundidee siehe Tiefensuche. 3.4 Erklärung am Beispiel Folgender ungerichteter Graph sei gegeben: 6 5 3 1 4 7 2 Warteschlange: 1 Als erstes kann ein Startknoten ausgesucht werden (in der Klausur ist dieser meist vorgegeben). Hier soll Knoten 1 gewählt werden. Also wird die 1 an unsere Warteschlange W = 1 gehängt. Jetzt wird das erste Element aus der Warteschlange entnommen, in diesem Fall also die 1. Knoten 1 hat die Nachfolgerknoten 6, 3, 4 und 2. Alle vier Nachfolger wurden noch nicht besucht. Also zeichnen wir eine Kante von 1 nach 2, 1 nach 3, 1 nach 4 und 1 nach 6. Und hängen diese Nachfolgerknoten an die Warteschlange: 1 6 5 3 1 4 7 6 3 4 2 2 Warteschlange: 6,3,4,2 Jetzt wird wieder das erste Element - diesmal die 6 - aus der Schlange entnommen. Es gibt nur einen noch nicht besuchten Nachfolgerknoten: Knoten 7. Also zeichnen wir eine Kante von 6 nach 7 und fügen die 7 der Warteschlange hinzu: 6 Wolfgang Hönig / Andreas Ecke 1 6 5 3 1 WS 09/10 7 4 6 3 4 2 2 7 Warteschlange: 3,4,2,7 Das nächste Element, welches entnommen wird ist die 3. Der einzige Nachfolgerknoten ist die 5, welche an die Warteschlange gehängt wird. Zustätzlich entsteht die neue Kante von 3 nach 5: 1 6 5 3 1 7 4 6 3 7 5 4 2 2 Warteschlange: 4,2,7,5 Nun müssen der Reihe nach noch die 4,2,7 und 5 abgearbeitet werden. Da aber keiner dieser Knoten noch nicht besuchte Nachfolger hat, ist die Breitensuche beendet. In der Klausur ist ein Baum und kein Graph gefordert. Damit ergibt sich als korrekte Schreibweise: 1 6 3 7 5 4 2 Auch hier sind wieder sehr viele verschiedene Lösungen möglich, je nachdem wie die Knotenreihenfolge gewählt wird. Bei Startknoten 1 ist zum Beispiel auch folgende Lösung korrekt: 1 2 7 6 3 4 5 Mit Startknoten 5 ergibt sich: 5 3 6 1 oder 5 7 4 2 7 6 3 2 1 4 3.4.1 Eigenschaften Komplexität: O(n + m) mit n . . . Anzahl Knoten, m . . . Anzahl Kanten 7