Что такое bfs
Перейти к содержимому

Что такое bfs

  • автор:

Обход в ширину

Обход в ширину (Поиск в ширину, англ. BFS, Breadth-first search) — один из простейших алгоритмов обхода графа, являющийся основой для многих важных алгоритмов для работы с графами.

Описание алгоритма

Алгоритм BFS
посещенные вершины

Пусть задан невзвешенный ориентированный граф [math] G = (V, E) [/math] , в котором выделена исходная вершина [math]s[/math] . Требуется найти длину кратчайшего пути (если таковой имеется) от одной заданной вершины до другой. Частным случаем указанного графа является невзвешенный неориентированный граф, т.е. граф, в котором для каждого ребра найдется обратное, соединяющее те же вершины в другом направлении.

Для алгоритма нам потребуются очередь и множество посещенных вершин [math] was [/math] , которые изначально содержат одну вершину [math] s [/math] . На каждом шагу алгоритм берет из начала очереди вершину [math] v [/math] и добавляет все непосещенные смежные с [math] v [/math] вершины в [math] was [/math] и в конец очереди. Если очередь пуста, то алгоритм завершает работу.

Анализ времени работы

Оценим время работы для входного графа [math]G = (V, E)[/math] , где множество ребер [math] E [/math] представлено списком смежности. В очередь добавляются только непосещенные вершины, поэтому каждая вершина посещается не более одного раза. Операции внесения в очередь и удаления из нее требуют [math] O(1) [/math] времени, так что общее время работы с очередью составляет [math] O(|V|) [/math] операций. Для каждой вершины [math] v [/math] рассматривается не более [math] \mathrm(v) [/math] ребер, инцидентных ей. Так как [math] \sum\limits_ \mathrm(v) = 2|E| [/math] , то время, используемое на работу с ребрами, составляет [math] O(|E|) [/math] . Поэтому общее время работы алгоритма поиска в ширину — [math] O(|V| + |E|) [/math] .

Корректность

В очереди поиска в ширину расстояние вершин до [math]s[/math] монотонно неубывает.

Докажем это утверждение индукцией по числу выполненных алгоритмом шагов.

Введем дополнительный инвариант: у любых двух вершин из очереди, расстояние до [math] s [/math] отличается не более чем на [math] 1 [/math] .

База: изначально очередь содержит только одну вершину [math] s [/math] .

Переход: пусть после [math] i-й [/math] итерации в очереди [math] a + 1 [/math] вершин с расстоянием [math] x [/math] и [math] b [/math] вершин с расстоянием [math] x + 1 [/math] .

Рассмотрим [math] i-ю [/math] итерацию. Из очереди достаем вершину [math] v [/math] , с расстоянием [math] x [/math] . Пусть у [math]v[/math] есть [math]r [/math] непосещенных смежных вершин. Тогда, после их добавления, в очереди находится [math] a [/math] вершин с расстоянием [math] x [/math] и, после них, [math] b + r [/math] вершин с расстоянием [math] x + 1 [/math] .

Алгоритм поиска в ширину в невзвешенном графе находит длины кратчайших путей до всех достижимых вершин.

Допустим, что это не так. Выберем из вершин, для которых кратчайшие пути от [math] s [/math] найдены некорректно, ту, настоящее расстояние до которой минимально. Пусть это вершина [math] u [/math] , и она имеет своим предком в дереве обхода в ширину [math] v [/math] , а предок в кратчайшем пути до [math] u [/math] — вершина [math] w [/math] .

Так как [math] w [/math] — предок [math] u [/math] в кратчайшем пути, то [math] \rho(s, u) = \rho(s, w) + 1 \gt \rho(s, w) [/math] , и расстояние до [math] w [/math] найдено верно, [math] \rho(s, w) = d[w] [/math] . Значит, [math] \rho(s, u) = d[w] + 1 [/math] .

Так как [math] v [/math] — предок [math] u [/math] в дереве обхода в ширину, то [math] d[u] = d[v] + 1 [/math] .

Дерево обхода в ширину

Поиск в ширину также может построить дерево поиска в ширину. Изначально оно состоит из одного корня [math] s [/math] . Когда мы добавляем непосещенную вершину в очередь, то добавляем ее и ребро, по которому мы до нее дошли, в дерево. Поскольку каждая вершина может быть посещена не более одного раза, она имеет не более одного родителя. После окончания работы алгоритма для каждой достижимой из [math] s [/math] вершины [math] t [/math] путь в дереве поиска в ширину соответствует кратчайшему пути от [math] s [/math] до [math] t [/math] в [math] G [/math] .

Реализация

Предложенная ниже функция возвращает кратчайшее расстояние между двумя вершинами.

  • [math] \mathtt [/math] — исходная вершина
  • [math] \mathtt [/math] — конечная вершина
  • [math] \mathtt [/math] — граф, состоящий из списка вершин [math] \mathtt [/math] и списка смежности [math] \mathtt [/math] . Вершины нумеруются целыми числами.
  • [math] \mathtt [/math] — очередь.
  • В поле [math] \mathtt [/math] хранится расстояние от [math] \mathtt [/math] до [math] \mathtt [/math] .
int BFS(G: (V, E), source: int, destination: int): d = int[|V|] fill(d, [math] \infty [/math]) d[source] = 0 Q = [math] \varnothing [/math] Q.push(source) while Q [math] \ne \varnothing [/math] u = Q.pop() for v: (u, v) in E if d[v] == [math] \infty [/math] d[v] = d[u] + 1 Q.push(v) return d[destination]

Если требуется найти расстояние лишь между двумя вершинами, из функции можно выйти, как только будет установлено значение [math] \mathtt [/math] . Еще одна оптимизация может быть проведена при помощи метода meet-in-the-middle.

Вариации алгоритма

0-1 BFS

Пусть в графе разрешены ребра веса [math] 0 [/math] и [math] 1 [/math] , необходимо найти кратчайший путь между двумя вершинами. Для решения данной задачи модифицируем приведенный выше алгоритм следующим образом:

Вместо очереди будем использовать дек (или можно даже steque). Если рассматриваемое ее ребро имеет вес [math] 0 [/math] , то будем добавлять вершину в начало, а иначе в конец. После этого добавления, дополнительный введенный инвариант в доказательстве расположения элементов в деке в порядке неубывания продолжает выполняться, поэтому порядок в деке сохраняется. И, соответственно, релаксируем расстояние до всех смежных вершин и, при успешной релаксации, добавляем их в дек.

Таким образом, в начале дека всегда будет вершина, расстояние до которой меньше либо равно расстоянию до остальных вершин дека, и инвариант расположения элементов в деке в порядке неубывания сохраняется. Значит, алгоритм корректен на том же основании, что и обычный BFS. Очевидно, что каждая вершина войдет в дек не более двух раз, значит, асимптотика у данного алгоритма та же, что и у обычного BFS.

1-k BFS

Пусть в графе разрешены ребра целочисленного веса из отрезка [math]1 \ldots k[/math] , необходимо найти кратчайший путь между двумя вершинами. Представим ребро [math]uv[/math] веса [math]m[/math] как последовательность ребер [math]uu_1u_2 \ldots u_v[/math] (где [math]u_1 \ldots u_[/math] — новые вершины). Применим данную операцию ко всем ребрам графа [math] G [/math] . Получим граф, состоящий (в худшем случае) из [math]k|E|[/math] ребер и [math]|V| + (k — 1)|E|[/math] вершин. Для нахождения кратчайшего пути следует запустить BFS на новом графе. Данный алгоритм будет иметь асимптотику [math] O(|V| + k|E|) [/math] .

См. также

  • Обход в глубину, цвета вершин
  • Алгоритм Дейкстры
  • Теория графов

Источники информации

  • Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4
  • MAXimal :: algo :: Поиск в ширину
  • Wikipedia — Breadth-first search
  • Wikipedia — Поиск в ширину
  • Визуализатор алгоритма
  • Алгоритмы и структуры данных
  • Кратчайшие пути в графах

Поиск в ширину (Breadth first search, BFS)

Обход означает посещение всех узлов графа. «Обход в ширину» или «Поиск в ширину» (Breadth first traversal or Breadth first Search) — это рекурсивный алгоритм поиска всех вершин графа или древовидной структуры данных. В этой статье вы познакомитесь с примерами алгоритма BFS, псевдокода BFS и кодом алгоритма «поиска в ширину» с реализацией в программах на C ++, C, Java и Python.

Алгоритм BFS

  1. Посещенные.
  2. Не посещенные.

Цель алгоритма — пометить каждую вершину, как посещенную, избегая циклов.

Алгоритм работает следующим образом:

  1. Начните с размещения любой вершины графа в конце очереди.
  2. Возьмите передний элемент очереди и добавьте его в список посещенных.
  3. Создайте список смежных узлов этой вершины. Добавьте те, которых нет в списке посещенных, в конец очереди.
  4. Продолжайте повторять шаги 2 и 3, пока очередь не опустеет.

Граф может иметь две разные несвязанные части, поэтому, чтобы убедиться, что мы покрываем каждую вершину, мы также можем запустить алгоритм BFS на каждом узле.

Пример BFS

Давайте посмотрим, как алгоритм «поиска в ширину» работает на примере. Мы используем неориентированный граф с 5 вершинами.

Мы начнем с вершины 0, алгоритм BFS начинается с помещения его в список посещенных и размещения всех смежных вершин в стеке.

Затем мы посещаем элемент в начале очереди, то есть 1, и переходим к соседним узлам. Так как 0 уже был посещен, мы посещаем 2.

У вершины 2 есть соседняя не посещенная вершина 4, поэтому мы добавляем ее в конец очереди и посещаем 3, которая находится в начале очереди.

В очереди остается только 4, поскольку единственный соседний узел с 3, то есть 0, уже посещен. Мы посещаем вершину 4.

Поскольку очередь пуста, мы завершили обход в ширину графика.

BFS псевдокод

create a queue Q mark v as visited and put v into Q while Q is non-empty remove the head u of Q mark and enqueue all (unvisited) neighbours of u

Код BFS

Код для алгоритма «поиска в ширину» с примером показан ниже. Код был упрощен, поэтому мы можем сосредоточиться на алгоритме, а не на других деталях.

BFS в C

#include #include #define SIZE 40 struct queue < int items[SIZE]; int front; int rear; >; struct queue* createQueue(); void enqueue(struct queue* q, int); int dequeue(struct queue* q); void display(struct queue* q); int isEmpty(struct queue* q); void printQueue(struct queue* q); struct node < int vertex; struct node* next; >; struct node* createNode(int); struct Graph < int numVertices; struct node** adjLists; int* visited; >; struct Graph* createGraph(int vertices); void addEdge(struct Graph* graph, int src, int dest); void printGraph(struct Graph* graph); void bfs(struct Graph* graph, int startVertex); int main() < struct Graph* graph = createGraph(6); addEdge(graph, 0, 1); addEdge(graph, 0, 2); addEdge(graph, 1, 2); addEdge(graph, 1, 4); addEdge(graph, 1, 3); addEdge(graph, 2, 4); addEdge(graph, 3, 4); bfs(graph, 0); return 0; >void bfs(struct Graph* graph, int startVertex) < struct queue* q = createQueue(); graph->visited[startVertex] = 1; enqueue(q, startVertex); while(!isEmpty(q))< printQueue(q); int currentVertex = dequeue(q); printf("Visited %d\n", currentVertex); struct node* temp = graph->adjLists[currentVertex]; while(temp) < int adjVertex = temp->vertex; if(graph->visited[adjVertex] == 0)< graph->visited[adjVertex] = 1; enqueue(q, adjVertex); > temp = temp->next; > > > struct node* createNode(int v) < struct node* newNode = malloc(sizeof(struct node)); newNode->vertex = v; newNode->next = NULL; return newNode; > struct Graph* createGraph(int vertices) < struct Graph* graph = malloc(sizeof(struct Graph)); graph->numVertices = vertices; graph->adjLists = malloc(vertices * sizeof(struct node*)); graph->visited = malloc(vertices * sizeof(int)); int i; for (i = 0; i < vertices; i++) < graph->adjLists[i] = NULL; graph->visited[i] = 0; > return graph; > void addEdge(struct Graph* graph, int src, int dest) < // Add edge from src to dest struct node* newNode = createNode(dest); newNode->next = graph->adjLists[src]; graph->adjLists[src] = newNode; // Add edge from dest to src newNode = createNode(src); newNode->next = graph->adjLists[dest]; graph->adjLists[dest] = newNode; > struct queue* createQueue() < struct queue* q = malloc(sizeof(struct queue)); q->front = -1; q->rear = -1; return q; > int isEmpty(struct queue* q) < if(q->rear == -1) return 1; else return 0; > void enqueue(struct queue* q, int value)< if(q->rear == SIZE-1) printf("\nQueue is Full!!"); else < if(q->front == -1) q->front = 0; q->rear++; q->items[q->rear] = value; > > int dequeue(struct queue* q) < int item; if(isEmpty(q))< printf("Queue is empty"); item = -1; >else< item = q->items[q->front]; q->front++; if(q->front > q->rear)< printf("Resetting queue"); q->front = q->rear = -1; > > return item; > void printQueue(struct queue *q) < int i = q->front; if(isEmpty(q)) < printf("Queue is empty"); >else < printf("\nQueue contains \n"); for(i = q->front; i < q->rear + 1; i++) < printf("%d ", q->items[i]); > > >

BFS в C++

#include #include using namespace std; class Graph < int numVertices; list *adjLists; bool* visited; public: Graph(int vertices); void addEdge(int src, int dest); void BFS(int startVertex); >; Graph::Graph(int vertices) < numVertices = vertices; adjLists = new list[vertices]; >void Graph::addEdge(int src, int dest) < adjLists[src].push_back(dest); adjLists[dest].push_back(src); >void Graph::BFS(int startVertex) < visited = new bool[numVertices]; for(int i = 0; i < numVertices; i++) visited[i] = false; list queue; visited[startVertex] = true; queue.push_back(startVertex); list::iterator i; while(!queue.empty()) < int currVertex = queue.front(); cout > > > int main()

BFS Java код (Java code)

import java.io.*; import java.util.*; class Graph < private int numVertices; private LinkedListadjLists[]; private boolean visited[]; Graph(int v) < numVertices = v; visited = new boolean[numVertices]; adjLists = new LinkedList[numVertices]; for (int i=0; i i = adjLists[currVertex].listIterator(); while (i.hasNext()) < int adjVertex = i.next(); if (!visited[adjVertex]) < visited[adjVertex] = true; queue.add(adjVertex); >> > > public static void main(String args[]) < Graph g = new Graph(4); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(2, 3); g.addEdge(3, 3); System.out.println("Following is Breadth First Traversal "+ "(starting from vertex 2)"); g.BFS(2); >>

BFS в Python

import collections def bfs(graph, root): visited, queue = set(), collections.deque([root]) visited.add(root) while queue: vertex = queue.popleft() for neighbour in graph[vertex]: if neighbour not in visited: visited.add(neighbour) queue.append(neighbour) if __name__ == '__main__': graph = breadth_first_search(graph, 0)

Рекомендуем хостинг TIMEWEB

Рекомендуем хостинг TIMEWEB

Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Рекомендуемые статьи по этой тематике

  • Алгоритм Прима
  • Алгоритм Краскала
  • Алгоритм DFS («Depth-first search» или «Поиск в глубину»)
  • Граф. Структура данных.
  • Двоичное дерево поиска (Binary Search Tree (BST))
  • Обход дерева – центрированный (inorder), прямой (preorder) и обратный (postorder) (три основных способа обхода)

По статье задано0 вопрос(ов)

Подписка на обсуждение 4
Подписка на раздел 78

Вам это нравится? Поделитесь в социальных сетях!

Поиск в ширину

Поиск в ширину (англ. breadth-first search) — один из основных алгоритмов на графах, позволяющий находить все кратчайшие пути от заданной вершины и решать многие другие задачи.

Поиск в ширину также называют обходом — так же, как поиск в глубину и все другие обходы, он посещает все вершины графа по одному разу, только в другом порядке: по увеличению расстояния до начальной вершины.

#Описание алгоритма

На вход алгоритма подаётся невзвешенный граф и номер стартовой вершины $s$. Граф может быть как ориентированным, так и неориентированным — для алгоритма это не важно.

Основную идею алгоритма можно понимать как процесс «поджигания» графа: на нулевом шаге мы поджигаем вершину $s$, а на каждом следующем шаге огонь с каждой уже горящей вершины перекидывается на всех её соседей, в конечном счете поджигая весь граф.

Если моделировать этот процесс, то за каждую итерацию алгоритма будет происходить расширение «кольца огня» в ширину на единицу. Номер шага, на котором вершина $v$ начинает гореть, в точности равен длине её минимального пути из вершины $s$.

Обратим внимание, что путь выведется в обратном порядке.

#В неявных графах

Поиск в ширину часто применяется для поиска кратчайшего пути в неявно заданных графах.

В качестве конкретного примера, пусть у нас есть булева матрица размера $n \times n$, в которой помечено, свободна ли клетка с координатами $(x, y)$, и требуется найти кратчайший путь от $(x_s, y_t)$ до $(x_y, y_t)$ при условии, что за один шаг можно перемещаться в свободную соседнюю по вертикали или горизонтали клетку.

Можно сконвертировать граф в явный формат: как-нибудь пронумеровать все ячейки (например по формуле $x \cdot n + y$) и для каждой посмотреть на всех её соседей, добавляя ребро в $(x \pm 1, y \pm 1)$, если соответствующая клетка свободна.

Такой подход будет работать за оптимальное линейное время, однако с точки зрения реализации проще адаптировать не входные данные, а сам алгоритм обхода в глубину:

Перед запуском bfs следует убедиться, что не произойдет выход за пределы границ. Вместо того, чтобы добавлять проверки на _x < 0 || x >= n и т. п. при просмотре возможных соседей, удобно сделать следующий трюк: изначально создать матрицу a с размерностями на 2 больше, чем нужно, и на этапе чтения данных заполнять её в индексации не с нуля, а с единицы. Тогда границы матрицы всегда будут заполнены нулями (то есть помечены непроходимыми) и алгоритм никогда в них не зайдет.

#Применения и обобщения

Помимо нахождения расстояний в невзвешенном графе, обход в ширину можно использовать для многих задач, в которых работает обход в глубину, например для поиска компонент связности или проверки на двудольность.

Множественный BFS. Добавив в очередь изначально не одну, а несколько вершин, мы найдем для каждой вершины кратчайшее расстояние до одной из них.

Это полезно для задач, в которых нужно моделировать пожар, наводнение, извержение вулкана или подобные явления, в которых источник «волны» не один. Также так можно чуть быстрее находить кратчайший путь для конкретной пары вершин, запустив параллельно два обхода от каждой и остановив в тот момент, когда они встретятся.

Игры. С помощью BFS можно найти решение какой-либо задачи / игры за наименьшее число ходов, если каждое состояние системы можно представить вершиной графа, а переходы из одного состояния в другое — рёбрами графа.

В качестве (нетривиального) примера: собрать кубик Рубика за наименьшее число ходов.

Кратчайшие циклы. Чтобы найти кратчайший цикл в ориентированном невзвешенном графе, можно произвести поиск в ширину из каждой вершины. Как только в процессе обхода мы пытаемся пойти из текущей вершины по какому-то ребру в уже посещённую вершину, то это означает, что мы нашли кратчайший цикл для данной вершины, и останавливаем обход. Среди всех таких найденных циклов (по одному от каждого запуска обхода) выбираем кратчайший.

Такой алгоритм будет работать за $O(n^2)$: худшим случаем будет один большой цикл, в котором мы для каждой вершины пройдемся по всем остальным.

Ребра на кратчайшем пути. Мы можем за линейное время найти все рёбра, лежащие на каком-либо кратчайшем пути между заданной парой вершин $a$ и $b$. Для этого нужно запустить два поиска в ширину: из $a$ и из $b$.

Обозначим через $d_a$ и $d_b$ массивы кратчайших расстояний, получившиеся в результате этих обходов. Тогда каждое ребро $(u,v)$ можно проверить критерием

$$ d_a[u] + d_b[v] + 1 = d_a[b] $$

Альтернативно, можно запустить один обход из $a$, и когда он дойдет до $b$, начать рекурсивно проходиться по всем обратным ребрам, ведущим в более близкие к $a$ вершины (то есть те, для которых $d[u] = d[v] - 1$), отдельно помечая их.

Аналогично можно найти все вершины на каком-либо кратчайшем пути.

0-1 BFS. Если веса некоторых ребер могут быть нулевыми, то кратчайшие пути искать не сильно сложнее.

Ключевое наблюдение: если от вершины $a$ до вершины $b$ можно дойти по пути, состоящему из нулевых рёбер, то кратчайшие расстояния от вершины $s$ до этих вершин совпадают.

Если в нашем графе оставить только $0$-рёбра, то он распадётся на компоненты связности, в каждой из которых ответ одинаковый. Если теперь вернуть единичные рёбра и сказать, что эти рёбра соединяют не вершины, а компоненты связности, то мы сведём задачу к обычному BFS.

Получается, запустив обход, мы можем при обработке вершины $v$, у которой есть нулевые ребра в непосещенные вершины, сразу пройтись по ним и добавить все вершины нулевой компоненты, проставив им такое же расстояние, как и у $v$.

Это можно сделать и напрямую, запустив BFS внутри BFS, однако можно заметить, что достаточно при посещении вершины просто добавлять всех её непосещенных соседей по нулевым ребрам в голову очереди, чтобы обработать их раньше, чем те, которые там уже есть. Это легко сделать, если очередь заменить деком:

Также вместо дека можно завести две очереди: одну для нулевых ребер, а другую для единичных, в внутри цикла while сначала просматривать первую, а только потом, когда она станет пустой, вторую. Этот подход уже можно обобщить.

1-k BFS. Теперь веса рёбер принимают значения от $1$ до некоторого небольшого $k$, и всё так же требуется найти кратчайшие расстояния от вершины $s$, но уже в плане суммарного веса.

Наблюдение: максимальное кратчайшее расстояние в графе равно $(n - 1) \cdot k$.

Заведём для каждого расстояния $d$ очередь $q_d$, в которой будут храниться вершины, находящиеся на расстоянии $d$ от $s$ — плюс, возможно, некоторые вершины, до которых мы уже нашли путь длины $d$ от $s$, но для которых возможно существует более короткий путь. Нам потребуется $O((n - 1) \cdot k)$ очередей.

Положим изначально вершину $s$ в $q_0$, а дальше будем брать вершину из наименьшего непустого списка и класть всех её непосещенных соседей в очередь с номером $d_v + w$ и релаксировать $d_u$, не забывая при этом, что кратчайшее расстояние до неё на самом деле может быть и меньше.

  Сложность такого алгоритма будет $O(k n + m)$, поскольку каждую вершину мы можем прорелаксировать и добавить в другую очередь не более $k$ раз, а просматривать рёбра, исходящие из вершины мы будем только когда обработаем эту вершину в самый первый раз.

На самом деле, нам так много списков не нужно. Если каждое ребро имеет вес не более $k$, то в любой момент времени не более $k$ очередей будут непустыми. Мы можем завести только $k$ списков, и вместо добавления в $(d_v + w)$-ый список использовать $(d_v+w) \bmod k$.

Заметим, что алгоритм также работает когда есть общее ограничение на длину пути, а не только на вес каждого ребра. Для более общего случая, когда веса ребер могут быть любыми неотрицательными, есть алгоритм Дейкстры, который мы разберем в следующей статье.

Что такое bfs

BFS, или Breadth First Search — алгоритм обхода графа в ширину. Граф — это структура из «вершин» и «ребер», соединяющих между собой вершины. По ребрам можно передвигаться от одной вершине к другой, и BFS делает это поуровнево: сначала проходит по всем ближайшим от начальной точки вершинам, потом спускается глубже.

Освойте профессию «Data Scientist»

Выглядит это так: алгоритм начинает в заранее выбранной вершине и сначала «посещает» и отмечает всех соседей этой вершины. Потом он переходит к соседям посещенных вершин, затем — дальше по тому же принципу. Из-за характера распространения, похожего на волну, алгоритм еще называют волновым. BFS — один из двух популярных алгоритмов обхода. Второй называется DFS и подразумевает обход в глубину: сначала алгоритм проходит по ребрам «вглубь» графа.

Профессия / 24 месяца
Data Scientist

Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.

Group 1321314349 (1)

Кто пользуется BFS

  • Дата-сайентисты, которые работают с информацией и ее распространением, часто взаимодействуют с теорией графов.
  • Разработчики, имеющие дело с определенными видами задач: поиск оптимального маршрута, программирование передвижения «умных» машин, разработка интеллектуальных систем и другие.
  • Математики и другие ученые, которые работают с теорией графов как с фундаментальным научным знанием или в контексте решения практических задач.
  • Инженеры-электроники: конкретно алгоритм BFS используется при трассировке печатных плат.
  • Технические специалисты, работающие в телекоммуникационных системах. Там тоже активно применяется теория графов и в частности BFS.
  • Сетевые инженеры, так как теория графов активно используется в сетевых технологиях. BFS, например, применяют при обходе P2P-сетей, или пиринговых сетей, а на них основаны многие сетевые протоколы. В частности, пиринговую сеть реализует BitTorrent.

Станьте дата-сайентистом и решайте амбициозные задачи с помощью нейросетей

Для чего нужен BFS

  • Для решения задач поиска оптимального пути. Классической задачей считается автоматизированный поиск выхода из лабиринта.
  • Для решения задач, связанных непосредственно с теорией графов, например для поиска компонент связности. Эти задачи в свою очередь решаются в Data Science, теории сетей и электронике.
  • Для задач искусственного интеллекта, связанных с поиском решения с минимальным количеством ходов. В таком случае состояния «умной машины» представляются как вершины, а переходы между ними — как ребра.
  • Для оптимизации памяти при обходе графа в некоторых ситуациях, например для некоторых специфических структур.
  • Для работы с информацией в определенных структурах данных, таких как деревья. Их тоже можно обходить с помощью алгоритма BFS, потому что они — подвид графов.

Особенности BFS

  • Константное количество действий для каждого ребра или вершины. Это важно при расчете сложности алгоритма — при выборе оптимального метода решения той или иной задачи.
  • Отсутствие проблемы «бесконечного цикла»: алгоритм не попадет в него ни при каких условиях благодаря особенностям работы.
  • Высокая точность и надежная архитектура, которая позволяет полагаться на этот алгоритм в решении различных задач.
  • Возможность работать и с ориентированными, и с неориентированными графами. О том, чем они различаются, можно прочитать в статье про ориентированный граф.
  • Полнота алгоритма — он найдет решение, то есть кратчайший путь, и завершится на любом конечном графе. Если граф бесконечный, решение найдется только в том случае, если конечен какой-либо из его путей.
  • Возможность находить кратчайший путь в графе, если все ребра одинаковой длины. Если длины ребер разные, BFS найдет путь с минимальным количеством ребер, но он не обязательно будет самым коротким. Для поиска кратчайшего пути в таком случае будет лучше алгоритм Дейкстры.

Как работает алгоритм BFS

Алгоритм простой и интуитивно понятный. Он проходит по вершинам графа, пока в том не останется непосещенных вершин, и рассчитывает самый короткий путь до целевой вершины. Чтобы показать его работу нагляднее, представим алгоритм пошагово.

Начало работы. В качестве начальной можно выбрать любую вершину. На момент начала работы алгоритма все вершины помечены как непосещенные — их называют «белыми». Первое, что делает алгоритм, — помечает начальную вершину как посещенную (также используют термины «развернутая» или «серая»). Если она и есть целевая, на этом алгоритм завершается. Но чаще всего это не так.

Поиск соседей. Алгоритм проверяет, какие соседи есть у начальной вершины. Они добавляются в «очередь действий» в том порядке, в каком алгоритм их нашел, и тоже помечаются как «серые». Это продолжается, пока у начальной вершины не останется «белых» соседей.

Переход на следующую вершину. Когда алгоритм проходит по всем соседям начальной вершины, он помечает ее как полностью обойденную. Такие вершины еще называют «черными»: алгоритм к ним не возвращается. Затем он переходит к одной из «серых» вершин — соседей начальной. Алгоритм выбирает первую вершину в очереди. Далее действия повторяются: «соседи» вершины, кроме «черной», заносятся в очередь.

Когда и эта вершина будет пройдена, переход повторится по тому же принципу — первая вершина в очереди. В этом случае ею будет второй сосед начальной вершины — мы помним, что их добавляли в очередь первыми. И только когда соседи начальной вершины в очереди закончатся, алгоритм пойдет по следующему «уровню» вершин. Так и достигается обход в ширину.

Конец алгоритма. Если очередь оказалась пустой, это значит, что «белых» и «серых» вершин больше не осталось. Алгоритм завершится. Если при этом целевая вершина не будет достигнута, это значит, что доступа к ней из начальной точки нет.

Если целевая вершина достигается раньше, чем алгоритм пройдет по всему графу, это также может означать его завершение. Алгоритм остановится, потому что задача окажется выполнена: самый короткий путь к целевой вершине будет найден.

Как реализовать алгоритм BFS

Реализация алгоритма возможна на любом языке программирования, который вы знаете. Граф обычно представляется как массив, очередь или другая структура данных. Ее элементы — вершины, и в них хранятся сведения о других вершинах, с которыми они соединены. Иногда там напрямую есть ссылки на другие вершины — конкретная реализация зависит от языка программирования и выбранной архитектуры.

Алгоритм BFS, реализованный программно, начинает с заданного элемента этой структуры данных — это аналогично начальной вершине. Он отмечает ее посещенной, или «серой» — например, записывает в элемент какое-то значение-метку. Затем он смотрит, на какие элементы ссылается начальный, и отмечает посещенными уже их. Когда все ссылки на другие элементы в начальной вершине заканчиваются, граф помечает ее как полностью обойденную, «черную» — для этого используется другое значение-метка. Оно показывает алгоритму, что больше возвращаться в эту вершину нет смысла, — так он не «застрянет» в одних и тех же точках.

После этого алгоритм переходит по ссылке к первому найденному «соседу» этой вершины и повторяет те же действия. Это продолжается, пока все вершины не окажутся помеченными как полностью обойденные.

Вы можете подробнее познакомиться с теорией графов, ее задачами, методами их решения и программными реализациями этих методов на курсах SkillFactory.

Data Scientist

Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.

картинка (74)

Статьи по теме:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *