Поиск в ширину
При поиске в ширину (breadth-first search — BFS) вершины графа посещаются в порядке возрастания расстояния от начальной вершины. Следовательно, используя поиск в ширину, мы сможем вычислить расстояния от начальной вершины до всех остальных. Однако реализовать поиск в ширину труднее, чем поиск в глубину.
В процессе поиска в ширину мы обходим вершины уровень за уровнем. Сначала посещаются вершины, стоящие от начальной на расстоянии \(1\), затем — на расстоянии \(2\) и т.д. Процесс продолжается, пока не останется не посещенных вершин.
На рисунке ниже показано, как происходит обход графа при поиске в ширину. Предположим, что поиск начинается с вершины \(1\). Сначала мы посещаем вершины \(2\) и \(4\), удаленные на растояние \(1\), затем — вершины \(3\) и \(5\), удаленные на расстояние \(2\), и, наконец, вершину \(6\), удаленную на расстояние \(3\).

Реализация
Поиск в ширину труднее реализовать, чем поиск в глубину, потому что алгоритм посещает вершины, находящиеся в разных частях графа. Типичная реализация основана на очереди вершин. На каждом шаге обрабатывается следующий узел из очереди.
В приведенном ниже коде предполагается, что граф представлен списками смежности и что определены следующие структуры данных:
q = deque()
used = [False] * n
dist = [INF] * n
Очередь q содержит вершины, подлежащие обработке в порядке возрастания расстояния. Новые вершины добавляются в конец очереди, а обрабатывается вершина, находящаяся в начале очереди. В массиве used хранится информация о том, какие вершины уже посещались, а в массиве dist — расстояния от начальной вершины до всех остальных вершин графа.
Поиск, начинающийся в вершине x, реализуется следующим образом:
used[x] = True
dist[x] = 0
q.append(x)
while q:
u = q.popleft()
# обработать вершину u
for v in g[u]:
if used[v]:
continue
used[v] = True
dist[u] = dist[v] + 1
q.append(v)
Временная сложность поиска в ширину, как и поиска в глубину, равна O(n+m), где n — число вершин, а m — число рёбер.
0-1 BFS
Задача
Дан связный неориентированный граф. Вес его рёбер может быть равен только значениям \(0\) или \(1\). Найдите кратчайшее расстояние между вершинами s и f.
Решение
Если от вершины u до вершины v можно дойти по пути, состоящему из рёбер веса \(0\), то кратчайшие расстояния до этих вершин совпадают.
Заметим тогда, что если в нашем графе оставить только \(0\)-рёбра, то он распадётся на компоненты связности (возможно, некоторые будут иметь размер \(1\)), в каждой из которых ответ одинаковый. Если теперь вернуть \(1\)-рёбра, и сказать, что эти рёбра соединяют не вершины, а компоненты связности, то мы сведём задачу к обычному BFS.
Чтобы решить исходную задачу, необходимо при посещении первой вершины из компоненты обойти всю компоненту, проставив во всех вершинах такой же ответ, как и у первой вершины. Для этого можно запустить BFS на \(0\)-рёбрах.
Заметим, что на самом деле нам не нужно запускать BFS внутри BFS, достаточно при посещении вершины добавлять всех её непосещённых соседей по \(0\)-рёбрам в голову очереди, чтобы обработать их раньше, чем следующие в очереди. Для добавления вершин не только в хвост, но и в голову можно использовать дек.
from collections import deque
INF = 1 << 32
n, m, s, f = map(int, input().split())
s -= 1
f -= 1
g = [[] for _ in range(n)]
for _ in range(m):
u, v, w = map(int, input().split())
u -= 1
v -= 1
g[u].append((v, w))
g[v].append((u, w))
dist = [INF] * n
dq = deque([s])
dist[s] = 0
while dq:
u = dq.popleft()
for v, w in g[u]:
if dist[u] + w >= dist[v]:
continue
dist[v] = dist[u] + w
if w == 0:
dq.appendleft(v)
else:
dq.append(v)
print(f"{dist[f]}")
5 5 1 3
1 2 0
2 3 1
3 4 0
4 5 1
1 5 1
1