Кратчайшие пути: поиск в ширину

Как навигатор находит дорогу, а социальная сеть — «рукопожатия» между людьми? Через поиск в ширину.

Поиск в ширину (BFS) — обход графа по «слоям», который находит кратчайший путь по числу рёбер от стартовой вершины до всех остальных.

Графы описывают дороги, друзей в соцсети, ссылки между страницами. Естественный вопрос — найти кратчайший маршрут. Когда все рёбра «равноценны», задачу решает элегантный алгоритм поиска в ширину.

Идея «волны»

Представьте, что от стартовой вершины расходится волна. Сначала она достигает всех непосредственных соседей (расстояние 1), затем их соседей (расстояние 2) и так далее. BFS обрабатывает вершины именно в таком порядке, используя очередь. Поскольку волна доходит до вершины кратчайшим путём первой, расстояние, записанное при первом посещении, и есть минимальное число рёбер.

Реализация BFS

from collections import deque

graph = {
    0: [1, 2],
    1: [0, 3],
    2: [0, 3, 4],
    3: [1, 2, 5],
    4: [2, 5],
    5: [3, 4],
}

def shortest_distances(graph, start):
    dist = {start: 0}
    queue = deque([start])
    while queue:
        node = queue.popleft()
        for neighbor in graph[node]:
            if neighbor not in dist:
                dist[neighbor] = dist[node] + 1
                queue.append(neighbor)
    return dist

distances = shortest_distances(graph, 0)
for v in sorted(distances):
    print(f"кратчайший путь 0 -> {v}: {distances[v]} рёбер")

Вывод:

кратчайший путь 0 -> 0: 0 рёбер
кратчайший путь 0 -> 1: 1 рёбер
кратчайший путь 0 -> 2: 1 рёбер
кратчайший путь 0 -> 3: 2 рёбер
кратчайший путь 0 -> 4: 2 рёбер
кратчайший путь 0 -> 5: 3 рёбер

Как работает под капотом

Сердце алгоритма — очередь (структура «первым пришёл — первым вышел»). Мы кладём в неё стартовую вершину, затем в цикле вынимаем вершину из начала и добавляем в конец всех её непосещённых соседей, записав им расстояние на единицу больше. Поскольку вершины выходят из очереди в порядке возрастания расстояния, к каждой мы приходим кратчайшим путём первый же раз — повторно её не обновляем (проверка neighbor not in dist). Так BFS гарантированно даёт минимум рёбер. Именно этот алгоритм лежит в основе подсчёта «рукопожатий» в соцсетях: расстояние между двумя людьми — это длина кратчайшей цепочки знакомств.

Частые ошибки

BFS находит кратчайший путь по числу рёбер, но не учитывает «вес» (длину дороги, время): для взвешенных графов нужен алгоритм Дейкстры. Не используйте стек вместо очереди — получится поиск в глубину, который кратчайшесть не гарантирует. И обязательно помечайте посещённые вершины, иначе в графе с циклами алгоритм зациклится.

Итог

  • BFS обходит граф «волнами» по слоям расстояния.
  • Находит кратчайший путь по числу рёбер.
  • Использует очередь и пометки посещённых вершин.
  • Для взвешенных рёбер нужен Дейкстра, не BFS.
Проверьте себя
1. Что находит поиск в ширину (BFS)?
AСамый длинный путь
BКратчайший путь по числу рёбер от старта до вершин
CВсе циклы в графе
DМинимальное остовное дерево
2. Какая структура данных лежит в основе BFS?
AСтек
BОчередь
CКуча
DДерево
3. Когда BFS НЕ годится для поиска кратчайшего пути?
AКогда граф большой
BКогда у рёбер разные веса (длины) — тогда нужен алгоритм Дейкстры
CКогда граф связный
DКогда вершин больше 100