Дан ориентированный граф
$G = (V, E)$ , а также вершина$s$ .
Найти длину кратчайшего пути от$s$ до каждой из вершин графа. Длина пути — количество рёбер в нём.
BFS — breadth-first search, или же поиск в ширину.
Этот алгоритм позволяет решать следующую задачу.
Алгоритм работает следующим образом.
- Создадим массив
$dist$ расстояний. Изначально$dist[s] = 0$ (поскольку расстояний от вершины до самой себя равно$0$ ) и$dist[v] = \infty$ для$v \neq s$ . - Создадим очередь
$q$ . Изначально в$q$ добавим вершину$s$ . - Пока очередь
$q$ непуста, делаем следующее:- Извлекаем вершину
$v$ из очереди. - Рассматриваем все рёбра
$(v, u) \in E$ . Для каждого такого ребра пытаемся сделать релаксацию: если$dist[v] + 1 < dist[u]$ , то мы делаем присвоение$dist[u] = dist[v] + 1$ и добавляем вершину$u$ в очередь.
- Извлекаем вершину
Визуализации:
Можно представить, что мы поджигаем вершину
Заметьте, что этот алгоритм очень похож на DFS — достаточно заменить очередь на стек и поиск в ширину станет поиском в глубину. Действительно, оба алгоритма при обработке вершины просто записывают всех непосещенных соседей, в которые из неё есть ребро, в структуру данных, и после этого выбирает следующую вершину для обработки в структуре данных. В DFS это стек (благодаря рекурсии), поэтому мы сначала записываем соседа, идем в обрабатываем его полностью, а потом начинаем обрабатывать следующего соседа. В BFS это очередь, поэтому мы кидаем сразу всех соседей, а потом начинаем обрабатывать вообще другую вершину - ту непосещенную, которую мы положили в очередь раньше всего.
Оба алгоритма позволяют обойти граф целиком - посетить каждую вершину ровно один раз. Поэтому они оба подходят для таких задач как:
- поиск компонент связности
- проверка графа на двудольность
- построение остова
n
— количество вершин в графе; adj
— список смежности
vector<int> bfs(int s) {
// длина любого кратчайшего пути не превосходит n - 1,
// поэтому n - достаточное значение для "бесконечности";
// после работы алгоритма dist[v] = n, если v недостижима из s
vector<int> dist(n, n);
dist[s] = 0;
queue<int> q;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (int u : adj[v]) {
if (dist[u] > dist[v] + 1) {
dist[u] = dist[v] + 1;
q.push(u);
}
}
}
return dist;
}
Обозначение:
Лемма 1.
Пусть
$(u, v) \in E$ , тогда$d(v) \leq d(u) + 1$ .
Действительно, существует путь из
Лемма 2.
Рассмотрим кратчайший путь от
$s$ до$v$ . Обозначим его как$u_1, u_2, \dots u_k$ ($u_1 = s$ и$u_k = v$ , а также$k = d(v) + 1$ ).
Тогда$\forall (i < k): d(u_i) + 1 = d(u_{i + 1})$ .
Действительно, пусть для какого-то
Утверждение.
- Расстояния до тех вершин, которые были добавлены в очередь, посчитаны корректно.
- Вершины лежат в очереди в порядке неубывания расстояния, притом разность между кратчайшими расстояними до вершин в очереди не превосходит
$1$ .
Докажем это по индукции по количеству итераций алгоритма (итерация — извлечение вершины из очереди и дальнейшая релаксация).
База очевидна.
Переход. Сначала докажем первую часть. Предположим, что
Теперь докажем вторую часть. По предположению индукции в очереди лежали некоторые вершины
Из доказанного следует, что каждая достижимая из
Если дан неориентированный граф, его можно рассматривать как ориентированный граф с двумя обратными друг другу ориентированными рёбрами.
Пусть теперь заданы 2 вершины
Поддерживать этот массив просто: при релаксации нужно просто запоминать, из какой вершины мы прорелаксировали в данную. Также будем считать, что
Восстановление пути делается с конца. Мы знаем последнюю вершину пути — это
// теперь bfs принимает 2 вершины, между которыми ищется пути
// bfs возвращает кратчайший путь из s в t, или же пустой vector, если пути нет
vector<int> bfs(int s, int t) {
vector<int> dist(n, n);
vector<int> p(n, -1);
dist[s] = 0;
queue<int> q;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (int u : adj[v]) {
if (dist[u] > dist[v] + 1) {
p[u] = v;
dist[u] = dist[v] + 1;
q.push(u);
}
}
}
// если пути не существует, возвращаем пустой vector
if (dist[t] == n) {
return {};
}
vector<int> path;
while (t != -1) {
path.push_back(t);
t = p[t];
}
// путь был рассмотрен в обратном порядке, поэтому его нужно перевернуть
reverse(path.begin(), path.end());
return path;
}
Дан ориентированный граф
$G$ , найти все вершины, которые принадлежат хотя бы одному кратчайшему пути из$s$ в$t$ .
Запустим из вершины
Теперь очевидно, что
Найти цикл минимальной длины в ориентированном графе.
Попытаемся из каждой вершины найти кратчайший цикл, проходящий через неё, с помощью BFS. Это делается аналогично обычному BFS: мы должны найти расстояний от вершины до самой себя, при этом не считая, что оно равно
Итого, у нас
Дан взвешенный ориентированный граф
$G = (V, E)$ , а также вершина$s$ . Длина ребра$(u, v)$ равна$w(u, v)$ . Длины всех рёбер неотрицательные.
Найти длину кратчайшего пути от$s$ до каждой из вершин графа. Длина пути — сумма длин рёбер в нём.
Алгоритм Дейкстры решает приведённую выше задачу. Он работает следующим образом.
- Создать массив
$dist$ расстояний. Изначально$dist[s] = 0$ и$dist[v] = \infty$ для$v \neq s$ . - Создать булёв массив
$used$ ,$used[v] = 0$ для всех вершин$v$ — в нём мы будем отмечать, совершалась ли релаксация из вершины. - Пока существует вершина
$v$ такая, что$used[v] = 0$ и$dist[v] \neq \infty$ , притом, если таких вершин несколько, то$v$ — вершина с минимальным$dist[v]$ , делать следующее:- Пометить, что мы совершали релаксацию из вершины
$v$ , то есть присвоить$used[v] = 1$ . - Рассматриваем все рёбра
$(v, u) \in E$ . Для каждого ребра пытаемся сделать релаксацию: если$dist[v] + w(v, u) < dist[u]$ , присвоить$dist[u] = dist[v] + w(v, u)$ .
- Пометить, что мы совершали релаксацию из вершины
Иными словами, алгоритм на каждом шаге находит вершину, до которой расстояние сейчас минимально и из которой ещё не была произведена релаксация, и делает её.
Посчитаем, за сколько работает алгоритм. Мы
Рёбра будем хранить как pair<int, int>
, где первое число пары — куда оно ведёт; а второе — длина ребра.
// INF - infinity - бесконечность
const long long INF = (long long) 1e18 + 1;
vector<long long> dijkstra(int s) {
vector<long long> dist(n, INF);
dist[s] = 0;
vector<bool> used(n);
while (true) {
// находим вершину, из которой будем релаксировать
int v = -1;
for (int i = 0; i < n; i++) {
if (!used[i] && (v == -1 || dist[i] < dist[v])) {
v = i;
}
}
// если не нашли подходящую вершину, прекращаем работу алгоритма
if (v == -1) {
break;
}
for (auto &e : adj[v]) {
int u = e.first;
int len = e.second;
if (dist[u] > dist[v] + len) {
dist[u] = dist[v] + len;
}
}
}
return dist;
}
Восстановление пути в алгоритме Дейкстры делается аналогично восстановлению пути в BFS (и любой динамике).
Искать вершину с минимальным
- Извлечь минимум (чтобы обработать новую вершину)
- Удалить вершину по индексу (чтобы уменьшить
$dist$ до какого-то соседа) - Добавить новую вершину (чтобы уменьшить
$dist$ до какого-то соседа)
Для этого используют, например, кучу или сет. Удобно помимо сета хранить сам массив dist, который его дублирует, но хранит элементы по порядку. Тогда, чтобы заменить значение
Данный алгоритм будет работать за
Заметьте, что этот алгоритм не лучше и не хуже, чем без сета, который работает за