„Breitensuche" – Versionsunterschied

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
Versionsgeschichte interaktiv durchsuchen
[gesichtete Version] [gesichtete Version]
← Zum vorherigen Versionsunterschied Zum nächsten Versionsunterschied →
Inhalt gelöscht Inhalt hinzugefügt
Zeile 25: Zeile 25:


[[Rekursion|Rekursiv]] formuliert:
[[Rekursion|Rekursiv]] formuliert:
BFS(''start_node'', ''goal_node'')(削除) { (削除ここまで)
BFS(''start_node'', ''goal_node'')
'''return''' BFS'({''start_node''}, ∅, ''goal_node'');
(追記) (追記ここまで) '''return''' BFS'({''start_node''}, ∅, ''goal_node'');
(削除) } (削除ここまで)
BFS'(''fringe'', ''visited'', ''goal_node'')(削除) { (削除ここまで)
BFS'(''fringe'', ''visited'', ''goal_node'')
'''if'''(''fringe'' == ∅)(削除) { (削除ここまで)
(追記) (追記ここまで) '''if'''(''fringe'' == ∅)
// Knoten nicht gefunden
// Knoten nicht gefunden
'''return''' false;
(追記) (追記ここまで) '''return''' false;
(追記) (追記ここまで) '''if''' (''goal_node'' ∈ ''fringe'')
}
(追記) (追記ここまで) '''return''' true;
'''if''' (''goal_node'' ∈ ''fringe'')(削除) { (削除ここまで)
(追記) (追記ここまで) '''return''' BFS'({''child'' | ''x'' ∈ ''fringe'', ''child'' ∈ expand(''x'')} \ ''visited'', ''visited'' ∪ ''fringe'', ''goal_node'');
'''return''' true;
}
'''return''' BFS'({''child'' | ''x'' ∈ ''fringe'', ''child'' ∈ expand(''x'')} \ ''visited'', ''visited'' ∪ ''fringe'', ''goal_node'');
}


Als [[Schleife (Programmierung)|Schleife]] formuliert:
Als [[Schleife (Programmierung)|Schleife]] formuliert:
BFS(''start_node'', ''goal_node'')(削除) { (削除ここまで)
BFS(''start_node'', ''goal_node'')
erzeuge eine leere Warteschlange ''queue''
(追記) (追記ここまで) erzeuge eine leere Warteschlange ''queue''
'''for'''(all nodes i) ''visited''[''i''] = false; // anfangs sind keine Knoten besucht
(追記) (追記ここまで) '''for'''(all nodes i)
(追記) (追記ここまで) ''visited''[''i''] = false;(追記) (追記ここまで) // anfangs sind keine Knoten besucht
''queue''.enqueue(''start_node''); // mit Start-Knoten beginnen
(追記) (追記ここまで) ''queue''.enqueue(''start_node'');(追記) (追記ここまで) // mit Start-Knoten beginnen
''visited''[''start_node''] = true;
(追記) (追記ここまで) ''visited''[''start_node''] = true;
'''while'''(! ''queue''.empty() ) (削除) { (削除ここまで) // solange queue nicht leer ist
(追記) (追記ここまで) '''while'''(! ''queue''.empty() ) (追記) (追記ここまで) // solange queue nicht leer ist
''node'' = ''queue''.dequeue(); // erstes Element von der queue nehmen
(追記) (追記ここまで) ''node'' = ''queue''.dequeue(); // erstes Element von der queue nehmen
'''if'''(''node'' == ''goal_node'')(削除) { (削除ここまで)
(追記) (追記ここまで) '''if'''(''node'' == ''goal_node'')
'''return''' true;(削除) (削除ここまで) // testen, ob Ziel-Knoten gefunden
(追記) (追記ここまで) '''return''' true; // testen, ob Ziel-Knoten gefunden
'''foreach'''(''child'' '''in''' expand(''node'')) // alle Nachfolge-Knoten, ...
}
'''(削除) foreach (削除ここまで)'''(''child'' (削除) '''in''' (削除ここまで) (削除) expand(''node'') (削除ここまで)) (削除) { (削除ここまで) // (削除) alle (削除ここまで) (削除) Nachfolge-Knoten, (削除ここまで) ...
(追記) (追記ここまで) '''(追記) if (追記ここまで)'''((追記) visited[ (追記ここまで)''child''(追記) ] (追記ここまで) (追記) == (追記ここまで) (追記) false (追記ここまで)) // (追記) ... die noch nicht besucht (追記ここまで) (追記) wurden (追記ここまで) ...
''(削除) 'if' (削除ここまで)''((削除) visited[ (削除ここまで)''child''(削除) ] == false (削除ここまで)) (削除) { (削除ここまで) // ... (削除) die (削除ここまで) (削除) noch (削除ここまで) (削除) nicht besucht wurden ... (削除ここまで)
(追記) (追記ここまで) ''(追記) queue (追記ここまで)''(追記) .enqueue (追記ここまで)(''child'')(追記) ; (追記ここまで) // ... (追記) zur (追記ここまで) (追記) queue (追記ここまで) (追記) hinzufügen... (追記ここまで)
''(削除) queue (削除ここまで)''(削除) .enqueue( (削除ここまで)''child''(削除) ); (削除ここまで) // ... (削除) zur (削除ここまで) (削除) queue (削除ここまで) (削除) hinzufügen... (削除ここまで)
(追記) (追記ここまで) ''(追記) visited (追記ここまで)''(追記) [ (追記ここまで)''child''(追記) ] (追記ここまで) (追記) = (追記ここまで) (追記) true; (追記ここまで) // ... (追記) und als bereits (追記ここまで) (追記) gesehen (追記ここまで) (追記) markieren (追記ここまで)
''(削除) visited (削除ここまで)''(削除) [ (削除ここまで)''(削除) child''] (削除ここまで) (削除) = true (削除ここまで); // (削除) ... und (削除ここまで) (削除) als (削除ここまで) (削除) bereits (削除ここまで) (削除) gesehen (削除ここまで) (削除) markieren (削除ここまで)
'''(追記) return (追記ここまで)''' (追記) false (追記ここまで);(追記) (追記ここまで) // (追記) Knoten (追記ここまで) (追記) kann (追記ここまで) (追記) nicht (追記ここまで) (追記) erreicht (追記ここまで) (追記) werden (追記ここまで)
}
}
}
'''return''' false; // Knoten kann nicht erreicht werden
}


== Eigenschaften ==
== Eigenschaften ==

Version vom 27. September 2020, 13:31 Uhr

Breitensuche

Breitensuche (englisch breadth-first search, BFS) ist ein Verfahren in der Informatik zum Durchsuchen bzw. Durchlaufen der Knoten eines Graphen. Sie zählt zu den uninformierten Suchalgorithmen. Im Gegensatz zur Tiefensuche werden zunächst alle Knoten beschritten, die vom Ausgangsknoten direkt erreichbar sind. Erst danach werden Folgeknoten beschritten (siehe Abbildung).

Arbeitsweise

Die Breitensuche ist eine uninformierte Suche, welche durch Expansion der einzelnen Level der Graphen ausgehend vom Startknoten den Graph in die Breite nach einem Element durchsucht.

Zuerst wird ein Startknoten u {\displaystyle u} {\displaystyle u} ausgewählt. Von diesem Knoten aus wird nun jede Kante ( u , v ) {\displaystyle (u,v)} {\displaystyle (u,v)} betrachtet und getestet, ob der gegenüberliegende Knoten v {\displaystyle v} {\displaystyle v} schon entdeckt wurde bzw. das gesuchte Element ist. Ist dies noch nicht der Fall, so wird der entsprechende Knoten in einer Warteschlange gespeichert und im nächsten Schritt bearbeitet. Hierbei ist zu beachten, dass Breitensuche immer zuerst alle Knoten der gleichen Ebene bearbeitet, und nicht wie die Tiefensuche einem Pfad in die Tiefe folgt. Nachdem alle Kanten des Ausgangsknotens betrachtet wurden, wird der erste Knoten der Warteschlange entnommen und das Verfahren wiederholt.

Eine Landkarte von Deutschland mit den Verbindungen zwischen einigen Städten.
Der Baum, welcher entsteht, wenn man BFS auf die Landkarte anwendet und in Frankfurt startet.
Weiteres Beispiel für Breitensuche

Algorithmus

  1. Bestimme den Knoten, an dem die Suche beginnen soll, markiere ihn als besucht und speichere ihn in einer Warteschlange ab.
  2. Entnimm einen Knoten vom Beginn der Warteschlange.
    • Falls das gesuchte Element gefunden wurde, brich die Suche ab und liefere „gefunden" zurück.
    • Anderenfalls hänge alle bisher unmarkierten Nachfolger dieses Knotens ans Ende der Warteschlange an und markiere sie als besucht.
  3. Wenn die Warteschlange leer ist, dann wurde jeder Knoten bereits untersucht. Beende die Suche und liefere „nicht gefunden" zurück.
  4. Wiederhole Schritt 2.

Nachstehend formulierte Algorithmen sind als Pseudocode zu verstehen und geben aus Gründen der Lesbarkeit nur an, ob der Zielknoten gefunden wurde. Weitere, in Anwendungsfällen wichtige Informationen – wie z. B. die aktuelle Pfadtiefe oder der bisherige Suchweg – könnten zusätzlich eingefügt werden.

Rekursiv formuliert:

BFS(start_node, goal_node)
 return BFS'({start_node}, ∅, goal_node);
BFS'(fringe, visited, goal_node)
 if(fringe == ∅)
 // Knoten nicht gefunden
 return false;
 if (goal_nodefringe)
 return true;
 return BFS'({child | xfringe, child ∈ expand(x)} \ visited, visitedfringe, goal_node);

Als Schleife formuliert:

BFS(start_node, goal_node)
 erzeuge eine leere Warteschlange queue
 for(all nodes i)
 visited[i] = false; // anfangs sind keine Knoten besucht
 queue.enqueue(start_node); // mit Start-Knoten beginnen
 visited[start_node] = true;
 while(! queue.empty() ) // solange queue nicht leer ist
 node = queue.dequeue(); // erstes Element von der queue nehmen
 if(node == goal_node)
 return true; // testen, ob Ziel-Knoten gefunden
 foreach(child in expand(node)) // alle Nachfolge-Knoten, ...
 if(visited[child] == false) // ... die noch nicht besucht wurden ...
 queue.enqueue(child); // ... zur queue hinzufügen...
 visited[child] = true; // ... und als bereits gesehen markieren
 return false; // Knoten kann nicht erreicht werden

Eigenschaften

Bezeichne | V | {\displaystyle |V|} {\displaystyle |V|} die Anzahl der Knoten und | E | {\displaystyle |E|} {\displaystyle |E|} die Anzahl der Kanten im Graphen. Speicherplatzverbrauch und Laufzeit des Algorithmus sind in Landau-Notation angegeben.

Speicherplatzverbrauch

Da alle bisher entdeckten Knoten gespeichert werden, beträgt der Speicherplatzverbrauch von Breitensuche O ( | V | ) {\displaystyle {\mathcal {O}}(|V|)} {\displaystyle {\mathcal {O}}(|V|)}. Die Breitensuche ist für Verfahren, bei denen die Knoten erst während der Breitensuche generiert werden (z. B. das Branch-&-Bound-Verfahren), aufgrund des großen Platzverbrauches meist ungeeignet. Ein der Breitensuche ähnliches Verfahren, das jedoch meist mit deutlich weniger Speicher auskommt, ist die iterative Tiefensuche.

Laufzeit

Da im ungünstigsten Fall alle möglichen Pfade zu allen möglichen Knoten betrachtet werden müssen, beträgt die Laufzeit der Breitensuche O ( | V | + | E | ) {\displaystyle {\mathcal {O}}(|V|+|E|)} {\displaystyle {\mathcal {O}}(|V|+|E|)}[1] . Beachte, dass O ( | E | ) {\displaystyle {\mathcal {O}}(|E|)} {\displaystyle {\mathcal {O}}(|E|)} zwischen O ( 1 ) {\displaystyle {\mathcal {O}}(1)} {\displaystyle {\mathcal {O}}(1)} und O ( | V | 2 ) {\displaystyle {\mathcal {O}}(|V|^{2})} {\displaystyle {\mathcal {O}}(|V|^{2})} variieren kann, abhängig davon, wie dünn der Graph ist.

Wenn die Anzahl der Knoten im Graphen im Voraus bekannt ist und zusätzliche Datenstrukturen verwendet werden, um zu bestimmen, welche Knoten bereits zur Warteschlange hinzugefügt wurden, kann die Platzkomplexität als O ( | V | ) {\displaystyle {\mathcal {O}}(|V|)} {\displaystyle {\mathcal {O}}(|V|)} ausgedrückt werden. Dies gilt zusätzlich zu dem für das Graphen selbst erforderlichen Speicherplatz, der abhängig von der von einer Implementierung des Algorithmus verwendeten Graphendarstellung variieren kann.

Vollständigkeit

Wenn in jedem Knoten nur endlich viele Alternativen existieren, ist die Breitensuche vollständig. Dies bedeutet, dass, wenn eine Lösung existiert, diese auch gefunden wird. Dies ist unabhängig davon, ob der zugrunde liegende Graph endlich ist oder nicht. Sollte jedoch keine Lösung existieren, so divergiert die Breitensuche bei einem unendlichen Graphen.

Bei der Analyse von Algorithmen wird angenommen, dass die Eingabe für die Breitensuche ein endlicher Graph ist, der explizit als Adjazenzliste oder ähnliche dargestellt wird. Bei der Anwendung von Graph-Traversierungen in der künstlichen Intelligenz kann die Eingabe jedoch eine implizite Darstellung eines unendlichen Graphen sein. In diesem Zusammenhang wird eine Suchverfahren als vollständig beschrieben, wenn garantiert wird, dass ein Zielzustand gefunden wird, falls einer existiert. Die Breitensuche ist abgeschlossen, die Tiefensuche jedoch nicht. Bei Anwendung auf unendlich viele Graphen, die implizit dargestellt werden, findet die Breitensuche schließlich den Zielzustand, aber die Tiefensuche kann in Teilen des Graphen verloren gehen, die keinen Zielzustand haben und niemals zurückkehren.[2]

Optimalität

Jede durch Breitensuche gefundene Lösung hat den kürzesten Abstand zum Wurzelknoten. Führt man Kantengewichte ein, so muss das Ergebnis, welches am nächsten zum Startknoten liegt, nicht notwendigerweise auch das Ergebnis mit den geringsten Pfadkosten sein. Dieses Problem umgeht man, indem man die Breitensuche zur uniformen Kostensuche erweitert. Sind jedoch alle Kantengewichte äquivalent, so ist jede durch Breitensuche gefundene Lösung optimal, da in diesem Fall die Lösung, die am nächsten zum Ausgangsknoten liegt, auch die Lösung mit den geringsten Kosten ist. Ob existierende Lösungen überhaupt gefunden werden hängt mit Endlichkeitseigenschaften des Suchbaums zusammen (siehe Vollständigkeit).

Die uniforme Kostensuche (englisch uniform-cost search) ist eine Erweiterung der Breitensuche für Graphen mit gewichteten Kanten. Der Algorithmus besucht Knoten in Reihenfolge aufsteigender Pfadkosten vom Wurzelknoten und wird deshalb üblicherweise mit einer Vorrangwarteschlange implementiert, in der alle noch nicht besuchten Nachbarn bereits besuchter Knoten mit der Pfadlänge als Schlüssel verwaltet werden. Die Optimalität ist nur für nicht-negative Kantengewichte garantiert.

Anwendung

Die Breitensuche kann für viele Fragestellungen in der Graphentheorie verwendet werden. Einige sind:

  • Finde alle Knoten innerhalb einer Zusammenhangskomponente
  • Prüfe, ob der gegebene Graph paar ist und finde ggf. eine zulässige 2-Färbung seiner Knoten[3]
  • Finde zwischen zwei Knoten u und w einen kürzesten Pfad (ungewichtete Kanten)
  • Kürzeste-Kreise-Problem

Siehe auch

Literatur

  • Thomas H. Cormen, Charles Leiserson, Ronald L. Rivest, Clifford Stein: Introduction to Algorithms. MIT Press, 2nd edition 2001, ISBN 0-262-53196-8
  • Sven Oliver Krumke, Hartmut Noltemeier: Graphentheoretische Konzepte und Algorithmen. 3. Auflage. Springer Vieweg, 2012, ISBN 978-3-8348-1849-2
Commons: Breitensuche  – Sammlung von Bildern, Videos und Audiodateien
Wikibooks: Breitensuche  – Implementierungen in der Algorithmensammlung

Dieser Artikel ist als Audiodatei verfügbar:


Mehr Informationen zur gesprochenen Wikipedia

Einzelnachweise

  1. Winfried Hochstättler: Algorithmische Mathematik. Springer, Heidelberg, u. a. 2010, ISBN 978-3-642-05421-1, S. 56–58. 
  2. Coppin, B. (2004). Artificial intelligence illuminated. Jones & Bartlett Learning. pp. 79–80.
  3. Dieter Jungnickel: Graphen, Netzwerke und Algorithmen. BI Wissenschaftsverlag, 3. Auflage 1994, ISBN 3-411-14263-4, S. 95–100
Abgerufen von „https://de.wikipedia.org/w/index.php?title=Breitensuche&oldid=204028814"