Обход слоями: сначала всё, до чего один шаг, потом — два. Первое посещение вершины оказывается кратчайшим путём, если все переходы равноценны.
Ключевые тезисы
- по числу рёбер — и только по числу: разные веса ломают корректность, а не скорость.
- Вершина помечается при добавлении в , иначе очередь раздувается до O(E).
- Мультистарт, 0-1 и обход по состояниям — те же десять строк с другим содержимым .
Какую задачу решает
«За сколько ходов», «минимальное число замен», «сколько шагов до выхода» — задачи, где путей экспоненциально много, а нужен кратчайший. Перебор путей безнадёжен, а находит какой-нибудь путь и выдаёт его за ответ.
устроен так, что получается сам собой: вершины обрабатываются слоями, и первое посещение любой вершины оказывается кратчайшим — при условии, что все переходы стоят одинаково. Это условие и есть единственная граница применимости приёма.
Подробный разбор
«За сколько ходов конь дойдёт до клетки», «какое минимальное число замен превращает одно слово в другое», «сколько шагов до выхода из лабиринта» — во всех случаях путей экспоненциально много, и перебирать их бессмысленно.
задачу тоже не решает: он найдёт какой-нибудь путь, но не кратчайший, — и это самая частая подмена на собеседовании, потому что на маленьких тестах результат часто совпадает.
Обходить вершины слоями: сначала все, до которых один шаг, потом все, до которых два. Тогда первое посещение вершины и есть до неё.
Дисциплина «первым пришёл — первым вышел» держит этот порядок сама: никогда не отдаст вершину дальнего слоя раньше, чем закончится ближний.
A
/ \
B C
/ \
D E
слой 0: A
слой 1: B C
слой 2: D E| Шаг | Извлекли | Добавили в | после шага | Расстояния |
|---|---|---|---|---|
| 1 | A | B, C | B C | A = 0, B = 1, C = 1 |
| 2 | B | D, E | C D E | D = 2, E = 2 |
| 3 | C | — | D E | без изменений |
| 4 | D | — | E | без изменений |
| 5 | E | — | пусто | обход завершён |
Вершины B и C помечены посещёнными в момент добавления в , а не в извлечения. Поэтому на третьем шаге C не добавляет соседей повторно и очередь не раздувается.
from collections import deque
def bfs(graph, start):
dist = {start: 0}
parent = {start: None}
queue = deque([start])
while queue:
v = queue.popleft()
for u in graph[v]:
if u in dist: # уже посещена или уже в очереди
continue
dist[u] = dist[v] + 1
parent[u] = v # для восстановления пути
queue.append(u)
return dist, parent- Время — : каждая вершина извлекается один раз, каждое ребро просматривается один раз.
- Память — : в в худшем случае лежит целый слой.
- Предусловие — стоимость всех переходов одинакова; иначе это Дейкстра.
- Путь восстанавливается по
parent— от цели к старту, затем разворачивается.
Индукция по слоям. Пусть все вершины на расстоянии уже извлечены в порядке и получили верные расстояния. Их соседи, ещё не помеченные, находятся ровно на расстоянии : ближе они быть не могут (иначе были бы помечены раньше), дальше — тоже (есть путь через соседа). отдаёт их только после всех вершин слоя , значит порядок сохраняется и на следующем слое.
Отсюда же правило пометки. Если помечать вершину при извлечении, она успеет попасть в столько раз, сколько у неё соседей: на плотном очередь раздувается до вместо . Обход останется корректным, но память вырастет — и на разреженных тестах это незаметно, поэтому ошибка доживает до продакшена.
Как только рёбра получают разные веса, аргумент ломается: сосед может оказаться дороже, чем обход в два дешёвых шага. Первое посещение перестаёт быть кратчайшим — и нужен другой алгоритм.
| Сигнал в условии | Что применять |
|---|---|
| «Минимальное число шагов, ходов, замен, переходов» | |
| Все переходы равноценны | |
| Лабиринт, сетка, «за сколько распространится» | обход по сетке, часто мультистарт |
| «Все заражённые распространяются одновременно» | мультистарт: все источники в сразу |
| Переходы стоят по-разному | не , а |
| Нужен любой путь или все пути |
| Вариант | Что меняется | Задача |
|---|---|---|
| По сетке | соседи — четыре или восемь направлений | лабиринт, острова, заливка |
| Мультистарт | в кладутся все источники сразу | гниющие апельсины, расстояние до ближайшей воды |
| 0-1 | дек: ребро веса 0 — в начало, веса 1 — в конец | минимум переключений, стен, пересадок |
| По состояниям | вершина — кортеж, а не точка | позиция плюс собранные ключи или остаток топлива |
| Двунаправленный | две волны навстречу | огромный с известной целью |
| Алгоритм Кана | вершин с нулевой степенью входа |
- Пометка при извлечении вместо добавления. раздувается; на плотном это переполнение памяти.
- Разные веса рёбер. даст неверный ответ — молча.
- Неполный ключ состояния. Если вершина — кортеж, в посещённые кладётся кортеж целиком, иначе обход срежет правильный путь.
- Границы сетки. Проверять их нужно до обращения к клетке, а не после.
- Список вместо очереди.
list.pop(0)в Python стоит — обход становится квадратичным без единой ошибки в логике.
| Приём | Когда он лучше | Разница |
|---|---|---|
| Обход в глубину | нужна связность, циклы, все варианты | не даёт кратчайшего пути |
| Дейкстра | у переходов разная стоимость | вместо обычной |
| 0-1 | веса только 0 и 1 | дек вместо кучи, остаётся |
| A* | цель известна и есть оценка расстояния | эвристика сокращает перебор |
| Двунаправленный поиск | ветвится сильно, цель известна | две волны вместо одной |
- Почему вершину нужно помечать при добавлении в , а не при извлечении?
- , где все рёбра весят 1, кроме одного веса 5: что вернёт и почему это неверно?
- Мультистарт: чем расстояния при нескольких источниках отличаются от расстояний от одного?
- Лабиринт с ключами и дверями: что здесь является вершиной?
- Как восстановить сам путь, а не только его длину?
Чем заменяют
Видеолекции
Тайм-кода именно на эту тему в записях нет. Но глава «Алгоритмы и структуры данных» разобрана в курсе целиком — с той оговоркой, что место в записи придётся искать самому.
Связанные темы
Алгоритмы на графах
Graph Traversal85%
Обходы графа: семейство · Алгоритмы и структуры данныхЗонтичная тема: что общего у обоих обходов, как увидеть граф в задаче, где он не назван, и по каким двум вопросам выбирается обход. Сами обходы разобраны отдельно.
Depth-First Search85%
Поиск в глубину · Алгоритмы и структуры данныхИдти вглубь до упора и возвращаться. Порядок завершения вершин даёт топологическую сортировку, три цвета — обнаружение циклов, откат состояния — перебор с возвратом.
Shortest Paths85%
Кратчайшие пути · Алгоритмы и структуры данныхДейкстра для неотрицательных весов, Беллман — Форд для отрицательных, A* — когда есть оценка расстояния до цели.
Graph Basics85%
Основы графов · Графы и сетиВершины, рёбра, веса и направления. Матрица смежности и список рёбер — два способа хранить одно и то же.
Centrality85%
Центральности · Графы и сетиМеры важности вершины: по числу связей, по посредничеству, по близости и по влиянию соседей.
PageRank85%
PageRank · Графы и сетиСтационарное распределение случайного блуждания по графу с телепортацией. Классический алгоритм ранжирования, который до сих пор используется как признак.
Всё, что названо в описании, разобрано здесь же или в соседней теме — переходы под каждым определением.
Пометка при добавлении
Не при извлечении: иначе раздувается до размера числа рёбер.
Разбор ниже: Почему это работаетМультистарт
Несколько источников в сразу — расстояние до ближайшего из них.
Разбор ниже: Вариации и параметрыТема встречается в тесте по направлениям — ошибки приведут обратно на эту страницу.
Глава «Алгоритмы и структуры данных» последний раз правилась . Нашли ошибку — напишите.