Breitensuche Warteschlangen und die Grundidee der Breitensuche Im Folgenden wollen wir Graphen systematisch von einem Startknoten aus durchmustern, das heißt, alle Knoten sollen besucht und alle Kanten berührt werden, wobei nach dem Startknoten s erst alle Nachbarn von s, dann alle Nachbarn der Nachbarn usw. besucht werden sollen. Dazu sei G = (V, E) ein ungerichteter Graph gegeben durch seine Adjazenzlistendarstellung und s ∈ V ein fest gewählter Startknoten. Wir werden den Knoten ‘Farben’ geben welche deren aktuellen Zustand symbolisieren: • weiß: Knoten wurde noch nicht gesehen; zu Beginn sind alle Knoten weiß • grau: Knoten wurde schon gesehen, wir müssen aber noch überprüfen, ob er noch weiße Nachbarn hat • schwarz: Knoten ist erledigt, Knoten selbst und alle seine Nachbarn wurden gesehen. Die noch zu untersuchenden grauen Knoten werden in einer Warteschlange Q verwaltet. Als Datenstruktur ist eine Warteschlange dadurch charakterisiert, dass Objekte in einer linearen Ordnung gehalten werden, neue Objekte kann man nur am Ende einfügen und zugreifen bzw. entfernen kann man nur den Kopf der Schlange, also das Objekt, was am längsten in der Schlange war. Eine solche Struktur realisiert das sogenannte FIFO-Prinzip : First–In–First– Out. Ohne die Details der Implementierung einer Warteschlange zu spezifizieren, werden wir ihre Funktionalität im Pseudocode verwenden, insbesondere die Funktion enqueue v in Q zum Einfügen des Knoten v in die Warteschlange Q und die Funktion dequeue Q um den vordersten Knoten aus der Warteschlange zu entfernen. Ein Knoten, dessen Farbe von weiß nach grau wechselt, wird ans Ende der Schlange eingefügt. Die Schlange wird vom Kopf her abgearbeitet. Ist die Schlange leer, sind alle von s erreichbaren Knoten erledigt. Mit BFS kann der Abstand d[u] = dG (s, u) eines erreichbaren Knotens u von s berechnet werden (Beweis später). Intuitiv sollte dies aber klar sein, denn der Algorithmus besucht zunächst alle Knoten im Abstand i von s, danach erst alle im Abstand i + 1. Gleichzeitig wird ein Baum von kürzesten Wegen von s zu allen erreichbaren Knoten aufgebaut. Dieser ist dadurch beschrieben, dass man für jeden Knoten u einen Zeiger π[u] auf den Vorgängerknoten auf einem kürzesten Weg von s nach u aufrechterhält. Ist dieser noch nicht bekannt oder existiert gar nicht, so ist der Zeiger auf N IL gesetzt. Pseudocode und ein Beispiel Breitensuche BFS(G,s): for all u ∈ V(G) \{ s} Farbe[u] = weiss d[u] = ∞ π[u] = NIL 1 Farbe[s] = grau d[s] = 0 π[s] = NIL enqueue s in Q while Q not empty u = dequeue Q for all v ∈ Adj[u] if Farbe[v] = weiss then Farbe[v] = grau d[v] = d[u]+1 π[v] = u enqueue v in Q Farbe[u] = schwarz Beispiel: Im Beispiel werden die Adjazenzlisten als lexikographisch sortiert angenommen. s a b c 8 8 8 0 Zustand nach Initialisierung: Nur der Startknoten s ist grau (dargestellt als gepunkteter Kreis) 8 e 8 8 d f Inhalt der Warteschlange: a b c 8 1 j 8 0 i 8 h 8 s 8 8 g Q=[s] Zustand nach Entfernung von s aus der Warteschlange Q f schwarze Knoten und Baumkanten 8 e 8 d 1 sind durch dicke Linien dargestellt Inhalt der Warteschlange: a 0 1 d 1 e 3 g 2 3 h i 8 h 8 s 8 8 g b c 2 3 Endzustand wenn Q leer ist: BFS−Baum dick gezeichnet f 3 i Q=[a,d] j 4 4 j 2 Die Laufzeit des BFS–Algorithmus ist offensichtlich O(|V | + |E|). Man beachte, dass der konstruierte Baum kürzester Wege von der Reihenfolge der Knoten in den Adjazenzlisten abhängt, der Abstand der Knoten selbst natürlich nicht. Genauer, verschiedene Adjazenzlistendarstellungen ein und desselben Graphen können nichtisomorphe Breitensuchbäume produzieren. Ist der Graph G ungerichtet und zusammenhängend, so liefert BFS einen aufspannenden Baum. Hat G mehrere Zusammenhangskomponenten, so kann man leicht einen aufspannenden Wald erzeugen. Man startet die BFS–Suche erneut bei einem beliebigen noch weißen Knoten. Was kann man damit noch anfangen? Auch der Test, ob ein ungerichteter Graph bipartit (also 2-färbbar) ist, wird jetzt sehr einfach. Wir konstruieren einen aufspannenden Wald, färben dessen Knoten mit zwei Farben und testen abschließend, ob die Graphkanten, die nicht im Wald sind, korrekt 2–gefärbt sind. Breitensuche und der Abstand vom Startknoten Wir wollen jetzt zeigen, dass man mit BFS korrekt die graphentheoretischen Abstände in G vom Startknoten s berechnet, also für alle Knoten v gilt: d[v] = dG (s, v). Beweis der BFS–Korrektheit: Wir führen einen Induktionsbeweis über den Zeitpunkt der Aufnahme des Knoten v in die Warteschlange Q. Um alle Voraussetzungen zur Ableitung der Induktionsbehauptung zur Verfügung zu haben, wird die zu beweisende Aussage wie folgt erweitert: 1. d[v] ≥ dG (s, v) 2. Ist w das (aktuell) erste und v das letzte Element in Q, dann gilt d[v] − d[w] ≤ 1 3. Wird v nach x in Q aufgenommen, dann gilt d[x] ≤ d[v], d.h. die Folge der d[.]-Werte in Q ist schwach monoton wachsend. 4. Ist dG (s, x) < dG (s, v), dann wird x vor v in Q aufgenommen. 5. d[v] = dG (s, v) Induktionsanfang: s wird als erster Knoten in Q aufgenommen, laut Initialisierung mit d[s] = 0 und somit sind alle Bedingungen erfüllt. Danach wird s als Kopfelement aus Q entfernt und alle Nachbarn v von s mit d[v] = 1 in Q eingefügt. Auch dabei sind alle Bedingungen erfüllt. Induktionvoraussetzung: Die Bedingungen 1 bis 5 gelten für alle Knoten v 0 , die vor v in Q eingefügt wurden. Induktionsschritt: Wir betrachten den Zeitpunkt der Aufnahme von v in die Warteschlange. Sei u der Knoten, der zur Aufnahme von v in Q führt (d.h. u ist der zuletzt entfernte Kopfknoten und v ein weißer Nachbar von u). (1) Offensichtlich muss dG (s, u) + 1 ≥ dG (s, v) sein. Nach Induktionsvoraussetzung ist d[u] = dG (s, u) und folglich d[v] = d[u] + 1 ≥ dG (s, v). (2) Ist w der aktuelle Kopfknoten in Q, dann ist nach Induktionsvoraussetzung (Bedingung 3) d[u] ≤ d[w] und folglich d[v] − d[w] = d[u] + 1 − d[w] ≤ 1. 3 (3) Da sowohl vor der Entfernung von u aus Q als auch vor der Aufnahme von v in Q die Voraussetzungen 2 und 3 erfüllt waren, gilt wegen d[v] = d[u] + 1 Aussage 3 auch nach Aufnahme von v. (4) Sei x ein Knoten mit dG (s, x) < dG (s, v). Nehmen wir an, dass x nicht vor v in der Warteschlange war. Offensichtlich muss x 6= s sein. Sei y der Vorgänger von x auf einem kürzesten Weg von s nach x. Dann ist dG (s, y) = dG (s, x) − 1 < dG (s, v) − 1 ≤ dG (s, u). Nach Induktionsvoraussetzung (bezüglich u) muss dann y vor u in Q gewesen sein. Spätestens bei der Entfernung von y aus Q wären alle (noch) weißen Nachbarn von y in die Warteschlange gelangt, also x vor v - Widerspruch. (5) Sei z der Vorgänger von v auf einem tatsächlich kürzestem Weg von s nach v. Fall 1: z wurde vor u in Q aufgenommen. Dann wäre v (spätestens) schon beim Entfernen von z aus Q in die Warteschlange gelangt - Widerspruch! Fall 2: z = u oder z wurde nach u in Q aufgenommen. Dann ist nach Induktionsvoraussetzung dG (s, u) = d[u] ≤ d[z] = dG (s, z) und folglich d[v] = d[u] + 1 ≤ dG (z) + 1 ≤ dG (s, v). Zusammen mit der Bedingung 1 ergibt sich die Gleichheit. Bemerkung: Der eigentliche, tiefere Grund, weshalb dieser Beweis funktioniert, ist die banale Feststellung, dass ein kürzester Weg (hier von s zu v) als Teilwege wieder kürzeste Wege enthält (hier von s nach z). Dies werden wir bei anderen Kürzeste–Wege–Problemen wiederfinden. 4