Обхождане в ширина
В теорията на графите, обхождането в ширина е начин за търсене в граф, когато търсенето се ограничава до две основни операции:
- посещаване и достъпване на разклонение от графа;
- получаване на достъп до съседите на посещаваното в даден момент разклонение.
Обхождането в ширина започва от главното разклонение и достъпва всички съседни разклонения. За всяко едно от тези съседни разклонения, проверява техните съседни разклонения, които не са били посетени и така нататък.
Алгоритъм
[редактиране | редактиране на кода]Алгоритъмът използва структурата опашка, за да съхранява междинните резултати, като пресича графа по следния начин.
- Добавя (enqueue) към опашката главното разклонение
- Премахва (dequeue) от опашката разклонение и го претърсва, ако търсеният елемент е намерен в това разклонение търсенето се прекратява и се връща резултата.
- В противен случай добавя (enqueue) всички наследници (преки дъщерни разклонения), които все още не са открити.
- Ако опашката е празна и всяко разклонение от графа е било претърсено търсенето се прекратява и се връща, че търсеното не е било намерено.
- Ако опашката не е празна се повтаря стъпка 2.
Забележка: Използването на стек вместо опашка имплементира алгоритъмът – Обхождане в дълбочина.
Псевдокод
[редактиране | редактиране на кода]Вход: A граф G и главно разклонение v на G
1 начин на действие обхождане в ширина(G,v): 2 създаване на опашкаQ 3 добавяне на v в Q 4 отбелязваме v 5 докато Q не е празна: 6 t ← Q.dequeue() (премахване) 7 ако t това, което търсим: 8 връщаме t 9 за всички edges e in G.adjacentEdges(t) прави 10 u ← G.adjacentVertex(t,e) 11 ако u не е маркиран: 12 маркираме u 13 вмъкваме (enqueue) u в Q 14 връщаме none
Характеристики
[редактиране | редактиране на кода]Заемана памет
[редактиране | редактиране на кода]Когато броят на върховете на графа е известен предварително и вече добавените в опашката върхове се знаят, запазени в допълнителна структура, сложността може да се изрази с израза , където е множеството от върхове. Ако графът е представен като списък на съседство, той заема памет, докато представен като матрица на съседство заема памет.
Време на изпълнение
[редактиране | редактиране на кода]Времето на изпълнение може да се представи с израза , тъй като всеки връх и „път“ между два съседни върха ще бъде проверен.
Забележка: може да има стойности между и , в зависимост от това, колко сгъстен е дадения граф
(ако приемем, че графът е свързан).
Употреба
[редактиране | редактиране на кода]Обхождането в ширина може да бъде използван за решаването на много проблеми в теорията на графите, например:
- Намиране на всички разклонения в рамките на един свързан компонент
- Копиране на колекции, алгоритъм на Ченей
- Намиране на най-краткия път между две разклонение U и V (дължината на пътя е броя на крайните елементи)
- Тестване на граф за двучастичност (bipartiteness)
Намиране на свързани компоненти
[редактиране | редактиране на кода]Обхождането в ширина достига наборът от разклонения чрез свързаните компоненти съдържащи се в първото разклонение.
Тестване за двучастичност
[редактиране | редактиране на кода]Алгоритъмът за обхождане в ширина може да бъде използван за тестване на двучастичност, като търсенето започне от произволен връх и дадени редуващи се етикети за вече посетените върхове по време на търсенето. Етикет със стойност 0 получава началния връх, 1 получават всичките му съседи, 0 – съседите на съседите на началния връх и така нататък. Ако в даден момент се достигне до връх, който има съсед (посетен) със същия етикет като този връх, то графа не е двустранен. Ако търсенето приключи, без намиране на такъв връх, тогава графът е двустранен.
Реализация
[редактиране | редактиране на кода]Реализация на обхождане в ширина в Java
Class Node { // създаване на клас за разклоненията
Char data;
Public Node(char c) {
this.data=c;
}
public void bfs(){
Queue q=new LinkedList(); // създаване на опашка
q.add(this.rootNode); // добавяне не на главното разклонение към опашката
printNode(this.rootNode);
rootNode.visited=true; // отбелязване на разклонението като посетено
while(!q.isEmpty()){ // while цикъл докато опашката е пълна
Node n=(Node)q.remove(); // премахване на разклонението
Node child=null;
while((child=getUnvisitedChildNode(n))!=null){ // ако премахнатото разклонение има не посетени дъщерни разклонения
// ги отбелязваме като посетени и ги добавяме към опашката
child.visited=true;
printNode(child);
q.add(child);
}
}
//Clear visited property of nodes
clearNodes();
}
}
Реализация на обхождане в ширина в C#
using System;
using System.Collections.Generic;
/// <summary>
/// Имплементиране на разклонение
/// </summary>
/// <typeparam name="T">the type of the values in nodes</typeparam>
public class TreeNode<T>
{
// Съдържа стойността на разклонението
private T value;
// Показва дали разклонението има родител
private bool hasParent;
// Съдържа дъщерните разклонения
public List<TreeNode<T>> Children;
/// <summary>
/// Конструкторът на класът TreeNode
/// </summary>
/// <param name="value">стойността на разклонението</param>
public TreeNode(T value)
{
if (value == null)
{
throw new ArgumentNullException(
"Cannot insert null value!");
}
this.value = value;
this.children = new List<TreeNode<T>>();
}
/// <summary>
///Стойността на разклонението
/// </summary>
public T Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public bool IsVisited {get;set;}
}
...
public static int BFS(TreeNode start, TreeNode end) // създаваме метод, който приема като параметри стартовото и крайното разклонение
{
Queue<TreeNode> queue = new Queue<TreeNode>(); // създаваме си опашка от тип разклонение
queue.Enqueue(start); // добавяме към опашката първото разклонение
while (queue.Count != 0) // пускаме цикъл докато опашката е пълна
{
TreeNode CurrentNode = queue.Dequeue(); // изваждаме от опашката дадено разклонение
CurrentNode.IsVisited = true; // отбелязваме го като посетено
foreach (Node Child in CurrentNode.Children.Keys) // претърсваме всички дъщерни разклонения на избраното
{
if (Child.IsVisited == false) // проверяваме дали е посетено
{
Child.IsVisited = true; // ако не е, го отбелязваме като посетено
if (Child == end) return 1; // ако няма повече разклонения връщаме 1
}
queue.Enqueue(Child); // ако е посетено го добавяме към опашката
}
}
return 0; // ако търсенето не даде резултат връщаме 0
}
Източници
[редактиране | редактиране на кода]Тази страница частично или изцяло представлява превод на страницата Breadth-first search в Уикипедия на английски. Оригиналният текст, както и този превод, са защитени от Лиценза „Криейтив Комънс – Признание – Споделяне на споделеното“, а за съдържание, създадено преди юни 2009 година – от Лиценза за свободна документация на ГНУ. Прегледайте историята на редакциите на оригиналната страница, както и на преводната страница, за да видите списъка на съавторите.
ВАЖНО: Този шаблон се отнася единствено до авторските права върху съдържанието на статията. Добавянето му не отменя изискването да се посочват конкретни източници на твърденията, които да бъдат благонадеждни. |