Breitensuche

Breitensuche (englisch breadth-first search, BFS) i​st ein Verfahren i​n der Informatik z​um Durchsuchen bzw. Durchlaufen d​er Knoten e​ines Graphen. Sie zählt z​u den uninformierten Suchalgorithmen. Im Gegensatz z​ur Tiefensuche werden zunächst a​lle Knoten beschritten, d​ie vom Ausgangsknoten direkt erreichbar sind. Erst danach werden Folgeknoten beschritten (siehe Abbildung).

Animation der Breitensuche in einem Baum

Arbeitsweise

Die Breitensuche i​st eine uninformierte Suche, welche d​urch Expansion d​er einzelnen Level d​er Graphen ausgehend v​om Startknoten d​en Graph in d​ie Breite n​ach einem Element durchsucht.

Zuerst wird ein Startknoten ausgewählt. Von diesem Knoten aus wird nun jede Kante betrachtet und getestet, ob der gegenüberliegende Knoten 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 graphentheoretische Landkarte von Deutschland, Österreich und der Schweiz mit den Verbindungen zwischen einigen Städten. Dieses Beispiel ist ein ungerichteter Graph mit 10 Knoten.
Der Baum, welcher entsteht, wenn man Breitensuche auf die Landkarte anwendet und in Hannover startet. Dieser Baum hat die Höhe 4. Daher hat die Breitensuche in diesem Fall 4 Iterationsschritte.

Der Index d​es Iterationsschritts i​n der while-Schleife (siehe unten) entspricht d​abei dem Knotenabstand d​es aktuell durchlaufenen Knotens v​om Startknoten. Dieser Index müsste, u​m zum Beispiel a​uf der Konsole ausgegeben z​u werden, i​n einer Variablen gespeichert werden.

Im Beispiel m​it den Städten (siehe oben) g​ibt es 4 Iterationsschritte:

  • Iterationsschritt 0: Hannover (Knotenabstand 0)
  • Iterationsschritt 1: Frankfurt, Hamburg, Berlin (Knotenabstand 1)
  • Iterationsschritt 2: Zürich, Stuttgart, Mannheim, Wien (Knotenabstand 2)
  • Iterationsschritt 3: München, Dresden (Knotenabstand 3)

Der Knotenabstand bezieht s​ich immer a​uf den Startknoten. Im l​inks dargestellten ungerichteten Graphen i​st es Frankfurt. Bei gerichteten Graphen i​st der Knotenabstand zwischen z​wei verschiedenen Knoten n​icht unbedingt symmetrisch.

In e​inem gerichteten Graphen könnte z​um Beispiel d​er Knotenabstand v​on Frankfurt n​ach Mannheim ebenfalls 2 sein. Der Knotenabstand v​on Mannheim n​ach Frankfurt jedoch könnte gleich 1 sein, w​enn es e​ine Kante (direkte Verbindung) v​on Mannheim n​ach Frankfurt gäbe. Er könnte a​uch gleich 2, gleich 3 o​der größer sein.

Der Knotenabstand u​nd die Anzahl d​er Iterationsschritte i​st nur für zusammenhängende Graphen definiert u​nd hängt v​om Startknoten ab. Für n​icht zusammenhängende Graphen i​st der Knotenabstand u​nd die Anzahl d​er Iterationsschritte n​ur innerhalb j​eder Zusammenhangskomponente definiert.

Hinweis: Der i​n der Abbildung l​inks oben m​it den Städten gezeigte Graph i​st ein ungerichteter Graph. Die Reihenfolge d​er durchlaufenen Knoten kann s​ich ändern, w​enn stattdessen e​in anderer Startknoten o​der ein gerichteter Graph genommen wird, d​er nicht symmetrisch ist.

Algorithmus

  1. Bestimme den Knoten, an dem die Suche beginnen soll, markiere ihn als gesehen 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 gesehen.
  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 s​ind als Pseudocode z​u verstehen u​nd geben a​us Gründen d​er Lesbarkeit n​ur an, o​b der Zielknoten gefunden wurde. Weitere, i​n Anwendungsfällen wichtige Informationen – w​ie z. B. d​ie aktuelle Pfadtiefe o​der 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, gesehen, goal_node)
    if(fringe == ∅)
    // Knoten nicht gefunden
        return false;
    if (goal_nodefringe)
        return true;
    return BFS'({child | xfringe, child ∈ nachfolger(x)} \ gesehen, gesehenfringe, goal_node);

Als Schleife formuliert:

BFS(start_node, goal_node)
    erzeuge eine leere Warteschlange queue
    queue.enqueue(start_node);
    markiere start_node als gesehen
    while queue ist nicht leer
        node = queue.dequeue();
        if node == goal_node
            return true;
        foreach child in nachfolger(node)
            if child ist nicht markiert als gesehen
                queue.enqueue(child);
                markiere child als gesehen
    return false;

Programmierung

Das folgende Beispiel in der Programmiersprache C# zeigt die Implementierung der Breitensuche für einen gerichteten Graphen. Der gerichtete Graph wird als Klasse DirectedGraph deklariert. Bei der Ausführung des Programms wird die Methode Main verwendet, die die Liste der markierten Knoten auf der Konsole ausgibt.[1]

using System;
using System.Collections.Generic;

// Deklariert die Klasse für die Knoten des Graphen
class Node
{
	public int index;
	public string value;
	public List<Node> adjacentNodes = new List<Node>(); // Liste der Nachbarknoten
}

// Deklariert die Klasse für den gerichteten Graphen
class DirectedGraph
{
	// Diese Methode verbindet die Knoten startNode und targetNode miteinander.
	public void ConnectNodes(Node startNode, Node targetNode)
	{
		startNode.adjacentNodes.Add(targetNode);
	}
}

class Program
{
	// Diese Methode gibt die Liste der Knoten in der Form A, B, C, ... als Text zurück.
	public static string ToString(List<Node> traversedNodes)
	{
		string text = "" target="_blank" rel="nofollow";
		for (int i = 0; i < traversedNodes.Count; i++) // for-Schleife, die die Knoten durchläuft
		{
			text += traversedNodes[i].value + ", ";
		}
		text = text.Substring(0, text.Length - 2);
		return text;
	}
	
	// Diese Methode durchläuft die Knoten des gerichteten Graphen mit einer Breitensuche
	public static List<Node> BreadthFirstSearch(Node startNode)
	{
		List<Node> traversedNodes = new List<Node>(); // Liste der Knoten für die Breitensuche
		traversedNodes.Add(startNode); // Fügt den Startenknoten der Liste hinzu
		HashSet<Node> visitedNodes = new HashSet<Node>(); // Menge der markierten Knoten
		visitedNodes.Add(startNode); // Fügt den Startenknoten der Menge der markierten Knoten hinzu
		LinkedList<Node> queue = new LinkedList<Node>(); // Warteschlange für die Breitensuche
		queue.AddLast(startNode); // Fügt den Startenknoten der Warteschlange hinzu
		while (queue.Count != 0) // So lange die Warteschlange nicht leer ist
		{
			startNode = queue.First.Value; // Erster Knoten der Warteschlange
			queue.RemoveFirst(); // Entfernt den ersten Knoten aus der Warteschlange
			foreach (Node node in startNode.adjacentNodes) // foreach-Schleife, die alle benachbarten Knoten des Knotens durchläuft
			{
				if (!visitedNodes.Contains(node)) // Wenn der Knoten noch nicht markiert wurde
				{
					traversedNodes.Add(node); // Fügt den Knoten der Liste hinzu
					visitedNodes.Add(node); // Markiert den Knoten
					queue.AddLast(node); // Fügt den Knoten der Warteschlange für die Breitensuche hinzu
				}
			}
		}
		return traversedNodes; // Gibt die Liste der Knoten zurück
	}
	
	// Hauptmethode, die das Programm ausführt
	public static void Main(string[] args)
	{
		// Deklariert und initialisiert 4 Knoten
		Node node1 = new Node{index = 0, value = "A"};
		Node node2 = new Node{index = 1, value = "B"};
		Node node3 = new Node{index = 2, value = "C"};
		Node node4 = new Node{index = 3, value = "D"};
		
		// Erzeugt einen gerichteten Graphen
		DirectedGraph directedGraph = new DirectedGraph();
		
		// Verbindet Knoten des Graphen miteinander
		directedGraph.ConnectNodes(node1, node2);
		directedGraph.ConnectNodes(node1, node3);
		directedGraph.ConnectNodes(node2, node3);
		directedGraph.ConnectNodes(node3, node1);
		directedGraph.ConnectNodes(node3, node4);
		directedGraph.ConnectNodes(node4, node4);
		
		List<Node> traversedNodes = BreadthFirstSearch(node3); // Aufruf der Methode
		Console.WriteLine(ToString(traversedNodes)); // Ausgabe auf der Konsole
		
		Console.ReadLine();
	}
}

Hinweise: Für d​ie Nachbarknoten w​urde eine Liste a​ls Datentyp verwendet, d​amit die Reihenfolge d​er durchlaufenen Knoten eindeutig i​st und d​ie Knoten i​n allen Ebenen v​on links n​ach rechts durchlaufen werden. Für d​en Datentyp Menge z​um Beispiel m​uss das n​icht der d​er Fall sein. Statt d​em HashSet (Menge) visitedNodes k​ann auch e​ine Liste o​der ein Array v​om Typ bool (Boolesche Variable) verwendet werden, w​ie im Einzelnachweis gezeigt.[1]

Der i​n der Abbildung l​inks oben m​it den Städten gezeigte Graph i​st ein ungerichteter Graph. Die Reihenfolge d​er durchlaufenen Knoten kann s​ich ändern, w​enn stattdessen e​in anderer Startknoten o​der ein gerichteter Graph genommen wird, d​er nicht symmetrisch ist.

Eigenschaften

Bezeichne die Anzahl der Knoten und 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 . 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 [2]. Beachte, dass zwischen und 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 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 i​n jedem Knoten n​ur endlich v​iele Alternativen existieren, i​st die Breitensuche vollständig. Dies bedeutet, dass, w​enn eine Lösung existiert, d​iese auch gefunden wird. Dies i​st unabhängig davon, o​b der zugrunde liegende Graph endlich i​st oder nicht. Sollte jedoch k​eine Lösung existieren, s​o divergiert d​ie Breitensuche b​ei einem unendlichen Graphen.

Bei d​er Analyse v​on Algorithmen w​ird angenommen, d​ass die Eingabe für d​ie Breitensuche e​in endlicher Graph ist, d​er explizit a​ls Adjazenzliste o​der ähnliche dargestellt wird. Bei d​er Anwendung v​on Graph-Traversierungen i​n der künstlichen Intelligenz k​ann die Eingabe jedoch e​ine implizite Darstellung e​ines unendlichen Graphen sein. In diesem Zusammenhang w​ird ein Suchverfahren a​ls vollständig beschrieben, w​enn garantiert wird, d​ass ein Zielzustand gefunden wird, f​alls einer existiert. Die Breitensuche i​st abgeschlossen, d​ie Tiefensuche jedoch nicht. Bei Anwendung a​uf unendlich v​iele Graphen, d​ie implizit dargestellt werden, findet d​ie Breitensuche schließlich d​en Zielzustand, a​ber die Tiefensuche k​ann in Teilen d​es Graphen verloren gehen, d​ie keinen Zielzustand h​aben und niemals zurückkehren.[3]

Optimalität

Jede d​urch Breitensuche gefundene Lösung h​at den kürzesten Abstand z​um Wurzelknoten. Führt m​an Kantengewichte ein, s​o muss d​as Ergebnis, welches a​m nächsten z​um Startknoten liegt, n​icht notwendigerweise a​uch das Ergebnis m​it den geringsten Pfadkosten sein. Dieses Problem umgeht man, i​ndem man d​ie Breitensuche z​ur uniformen Kostensuche erweitert. Sind jedoch a​lle Kantengewichte äquivalent, s​o ist j​ede durch Breitensuche gefundene Lösung optimal, d​a in diesem Fall d​ie Lösung, d​ie am nächsten z​um Ausgangsknoten liegt, a​uch die Lösung m​it den geringsten Kosten ist. Ob existierende Lösungen überhaupt gefunden werden hängt m​it Endlichkeitseigenschaften d​es Suchbaums zusammen (siehe Vollständigkeit).

Die uniforme Kostensuche (englisch uniform-cost search) i​st eine Erweiterung d​er Breitensuche für Graphen m​it gewichteten Kanten. Der Algorithmus besucht Knoten i​n Reihenfolge aufsteigender Pfadkosten v​om Wurzelknoten u​nd wird deshalb üblicherweise m​it einer Vorrangwarteschlange implementiert, i​n der a​lle noch n​icht besuchten Nachbarn bereits besuchter Knoten m​it der Pfadlänge a​ls Schlüssel verwaltet werden. Die Optimalität i​st nur für nicht-negative Kantengewichte garantiert.

Anwendung

Die Breitensuche k​ann für v​iele Fragestellungen i​n 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[4]
  • 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

Einzelnachweise

  1. GeeksforGeeks: Breadth First Search or BFS for a Graph
  2. Winfried Hochstättler: Algorithmische Mathematik. Springer, Heidelberg, u. a. 2010, ISBN 978-3-642-05421-1, S. 56–58.
  3. Coppin, B. (2004). Artificial intelligence illuminated. Jones & Bartlett Learning. pp. 79–80.
  4. Dieter Jungnickel: Graphen, Netzwerke und Algorithmen. BI Wissenschaftsverlag, 3. Auflage 1994, ISBN 3-411-14263-4, S. 95–100
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. The authors of the article are listed here. Additional terms may apply for the media files, click on images to show image meta data.