Дан ориентированный граф
, а также вершина .
Найти длину кратчайшего пути отдо каждой из вершин графа. Длина пути — количество рёбер в нём.
BFS — breadth-first search, или же поиск в ширину.
Этот алгоритм позволяет решать следующую задачу.
Алгоритм работает следующим образом.
Визуализации:
https://visualgo.net/mn/dfsbfs
https://www.hackerearth.com/practice/algorithms/graphs/breadth-first-search/visualize/
Можно представить, что мы поджигаем вершину
Заметьте, что этот алгоритм очень похож на DFS — достаточно заменить очередь на стек и поиск в ширину станет поиском в глубину. Действительно, оба алгоритма при обработке вершины просто записывают всех непосещенных соседей, в которые из неё есть ребро, в структуру данных, и после этого выбирает следующую вершину для обработки в структуре данных. В DFS это стек (благодаря рекурсии), поэтому мы сначала записываем соседа, идем в обрабатываем его полностью, а потом начинаем обрабатывать следующего соседа. В BFS это очередь, поэтому мы кидаем сразу всех соседей, а потом начинаем обрабатывать вообще другую вершину - ту непосещенную, которую мы положили в очередь раньше всего.
Оба алгоритма позволяют обойти граф целиком - посетить каждую вершину ровно один раз. Поэтому они оба подходят для таких задач как: * поиск компонент связности * проверка графа на двудольность * построение остова
n
— количество вершин в графе; adj
— список смежности
<int> bfs(int s) {
vector// длина любого кратчайшего пути не превосходит n - 1,
// поэтому n - достаточное значение для "бесконечности";
// после работы алгоритма dist[v] = n, если v недостижима из s
<int> dist(n, n);
vector[s] = 0;
dist<int> q;
queue.push(s);
q
while (!q.empty()) {
int v = q.front();
.pop();
qfor (int u : adj[v]) {
if (dist[u] > dist[v] + 1) {
[u] = dist[v] + 1;
dist.push(u);
q}
}
}
return dist;
}
Обозначение:
Лемма 1. > Пусть
Действительно, существует путь из
Лемма 2. > Рассмотрим кратчайший путь от
> Тогда
Действительно, пусть для какого-то
Утверждение. > 1. Расстояния до тех вершин, которые были добавлены в очередь, посчитаны корректно. > 2. Вершины лежат в очереди в порядке неубывания расстояния, притом разность между кратчайшими расстояними до вершин в очереди не превосходит
Докажем это по индукции по количеству итераций алгоритма (итерация — извлечение вершины из очереди и дальнейшая релаксация).
База очевидна.
Переход. Сначала докажем первую часть. Предположим, что
Теперь докажем вторую часть. По предположению индукции в очереди лежали некоторые вершины
Из доказанного следует, что каждая достижимая из
Если дан неориентированный граф, его можно рассматривать как ориентированный граф с двумя обратными друг другу ориентированными рёбрами.
Пусть теперь заданы 2 вершины
Поддерживать этот массив просто: при релаксации нужно просто запоминать, из какой вершины мы прорелаксировали в данную. Также будем считать, что
Восстановление пути делается с конца. Мы знаем последнюю вершину пути — это
// теперь bfs принимает 2 вершины, между которыми ищется пути
// bfs возвращает кратчайший путь из s в t, или же пустой vector, если пути нет
<int> bfs(int s, int t) {
vector<int> dist(n, n);
vector<int> p(n, -1);
vector[s] = 0;
dist<int> q;
queue.push(s);
q
while (!q.empty()) {
int v = q.front();
.pop();
qfor (int u : adj[v]) {
if (dist[u] > dist[v] + 1) {
[u] = v;
p[u] = dist[v] + 1;
dist.push(u);
q}
}
}
// если пути не существует, возвращаем пустой vector
if (dist[t] == n) {
return {};
}
<int> path;
vectorwhile (t != -1) {
.push_back(t);
path= p[t];
t }
// путь был рассмотрен в обратном порядке, поэтому его нужно перевернуть
(path.begin(), path.end());
reversereturn path;
}
Дан ориентированный граф
, найти все вершины, которые принадлежат хотя бы одному кратчайшему пути из в .
Запустим из вершины
Теперь очевидно, что
Найти цикл минимальной длины в ориентированном графе.
Попытаемся из каждой вершины найти кратчайший цикл, проходящий через неё, с помощью BFS. Это делается аналогично обычному BFS: мы должны найти расстояний от вершины до самой себя, при этом не считая, что оно равно
Итого, у нас
Дан взвешенный ориентированный граф
, а также вершина . Длина ребра равна . Длины всех рёбер неотрицательные.
Найти длину кратчайшего пути отдо каждой из вершин графа. Длина пути — сумма длин рёбер в нём.
Алгоритм Дейкстры решает приведённую выше задачу. Он работает следующим образом.
Иными словами, алгоритм на каждом шаге находит вершину, до которой расстояние сейчас минимально и из которой ещё не была произведена релаксация, и делает её.
Посчитаем, за сколько работает алгоритм. Мы
Рёбра будем хранить как pair<int, int>
, где первое число пары — куда оно ведёт; а второе — длина ребра.
// INF - infinity - бесконечность
const long long INF = (long long) 1e18 + 1;
<long long> dijkstra(int s) {
vector<long long> dist(n, INF);
vector[s] = 0;
dist<bool> used(n);
vector
while (true) {
// находим вершину, из которой будем релаксировать
int v = -1;
for (int i = 0; i < n; i++) {
if (!used[i] && (v == -1 || dist[i] < dist[v])) {
= i;
v }
}
// если не нашли подходящую вершину, прекращаем работу алгоритма
if (v == -1) {
break;
}
for (auto &e : adj[v]) {
int u = e.first;
int len = e.second;
if (dist[u] > dist[v] + len) {
[u] = dist[v] + len;
dist}
}
}
return dist;
}
Восстановление пути в алгоритме Дейкстры делается аналогично восстановлению пути в BFS (и любой динамике).
Искать вершину с минимальным
Для этого используют, например, кучу или сет. Удобно помимо сета хранить сам массив dist, который его дублирует, но хранит элементы по порядку. Тогда, чтобы заменить значение
Данный алгоритм будет работать за
Заметьте, что этот алгоритм не лучше и не хуже, чем без сета, который работает за