Что такое алгоритм Дейкстры?
Алгоритм Дейкстры — это алгоритм для поиска кратчайших расстояний от одной стартовой вершины до всех остальных вершин в графе.
Он работает, когда у ребер есть веса, но все веса должны быть неотрицательными.
Где используется?
- найти кратчайший путь от одной вершины до всех
- найти кратчайший путь между двумя вершинами
- граф с весами на ребрах
- карты, дороги, маршруты
- задачи на минимальную стоимость
Главная идея
Мы храним массив dist.
dist[v]
Это минимальное расстояние от стартовой вершины до вершины v.
В начале:
dist[остальные] = INF
Потом мы постепенно улучшаем расстояния.
значит нашли путь лучше
обновляем dist[to]
if (dist[v] + w < dist[to]) {
dist[to] = dist[v] + w;
}
Что такое relaxation?
Relaxation — это попытка улучшить расстояние до вершины.
Допустим:
ребро v → to имеет вес 3
тогда путь до to через v равен 5 + 3 = 8
Если раньше было:
Тогда новый путь лучше:
dist[to] = 8
Почему нужен priority_queue?
Каждый раз нам нужно брать вершину с минимальным текущим расстоянием.
Для этого удобно использовать:
priority_queue
Но в C++ обычный priority_queue — это max-heap. Нам нужен min-heap, поэтому пишем так:
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
В queue кладем пару:
Пример графа
Пусть есть граф:
1 --2-- 3
3 --1-- 2
2 --3-- 4
3 --7-- 4
Стартовая вершина: 1.
Кратчайшие расстояния:
| Вершина | Расстояние от 1 |
|---|---|
| 1 | 0 |
| 2 | 3 |
| 3 | 2 |
| 4 | 6 |
Почему до 2 расстояние 3?
2 + 1 = 3
Почему до 4 расстояние 6?
2 + 1 + 3 = 6
Структура графа
Так как у ребра есть вес, в списке смежности храним пару:
vector<pair<int, int>> g[N];
Здесь:
- first — вершина, куда идет ребро
- second — вес ребра
Например, если есть ребро:
Тогда добавляем:
g[1].push_back({2, 5});
g[2].push_back({1, 5});
Базовая функция Дейкстры
void dijkstra(int start) {
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
dist[start] = 0;
q.push({0, start});
while (!q.empty()) {
long long d = q.top().first;
int v = q.top().second;
q.pop();
if (d != dist[v]) {
continue;
}
for (auto edge : g[v]) {
int to = edge.first;
int w = edge.second;
if (dist[v] + w < dist[to]) {
dist[to] = dist[v] + w;
q.push({dist[to], to});
}
}
}
}
Зачем нужен if (d != dist[v])?
В priority_queue может лежать старая информация.
Например, сначала мы нашли путь до вершины 2 длиной 10. Потом нашли путь лучше — длиной 3.
{10, 2}
{3, 2}
Когда достаем старую пару {10, 2}, она уже не нужна. Поэтому мы ее пропускаем.
if (d != dist[v]) {
continue;
}
Задача 1: расстояния от одной вершины
Дан неориентированный взвешенный граф. Нужно найти кратчайшие расстояния от вершины s до всех вершин.
Input
4 5 1
1 2 5
1 3 2
3 2 1
2 4 3
3 4 7
Output
0 3 2 6
Формат:
a b w
a b w
...
Полная реализация
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
const long long INF = 4e18;
int n, m, s;
vector<pair<int, int>> g[N];
long long dist[N];
void dijkstra(int start) {
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
dist[start] = 0;
q.push({0, start});
while (!q.empty()) {
long long d = q.top().first;
int v = q.top().second;
q.pop();
if (d != dist[v]) {
continue;
}
for (auto edge : g[v]) {
int to = edge.first;
int w = edge.second;
if (dist[v] + w < dist[to]) {
dist[to] = dist[v] + w;
q.push({dist[to], to});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> s;
for (int i = 1; i <= n; i++) {
dist[i] = INF;
}
for (int i = 1; i <= m; i++) {
int a, b, w;
cin >> a >> b >> w;
g[a].push_back({b, w});
g[b].push_back({a, w});
}
dijkstra(s);
for (int i = 1; i <= n; i++) {
if (dist[i] == INF) {
cout << -1 << ' ';
} else {
cout << dist[i] << ' ';
}
}
return 0;
}
Восстановление кратчайшего пути
Если нужно не только расстояние, но и сам путь, нужен массив p.
int p[N];
Когда мы улучшаем расстояние до to, запоминаем, откуда пришли:
p[to] = v;
То есть:
Как восстановить путь?
Идем от конечной вершины назад по parent.
vector<int> path;
for (int v = finish; v != -1; v = p[v]) {
path.push_back(v);
}
reverse(path.begin(), path.end());
Например:
p[4] = 2
p[2] = 3
p[3] = 1
p[1] = -1
Задача 2: найти кратчайший путь
Дан граф, старт s и финиш t. Нужно вывести кратчайший путь из s в t.
Input
4 5 1 4
1 2 5
1 3 2
3 2 1
2 4 3
3 4 7
Output
1 3 2 4
Полная реализация с путем
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
const long long INF = 4e18;
int n, m, s, t;
vector<pair<int, int>> g[N];
long long dist[N];
int p[N];
void dijkstra(int start) {
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
dist[start] = 0;
q.push({0, start});
while (!q.empty()) {
long long d = q.top().first;
int v = q.top().second;
q.pop();
if (d != dist[v]) {
continue;
}
for (auto edge : g[v]) {
int to = edge.first;
int w = edge.second;
if (dist[v] + w < dist[to]) {
dist[to] = dist[v] + w;
p[to] = v;
q.push({dist[to], to});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> s >> t;
for (int i = 1; i <= n; i++) {
dist[i] = INF;
p[i] = -1;
}
for (int i = 1; i <= m; i++) {
int a, b, w;
cin >> a >> b >> w;
g[a].push_back({b, w});
g[b].push_back({a, w});
}
dijkstra(s);
if (dist[t] == INF) {
cout << -1;
return 0;
}
vector<int> path;
for (int v = t; v != -1; v = p[v]) {
path.push_back(v);
}
reverse(path.begin(), path.end());
for (int x : path) {
cout << x << ' ';
}
return 0;
}
Ориентированный граф
Если граф ориентированный, то ребро идет только в одну сторону.
Для ребра:
Добавляем только так:
g[a].push_back({b, w});
А вот это уже не нужно:
g[b].push_back({a, w});
Dijkstra vs BFS
| Алгоритм | Граф | Что ищет |
|---|---|---|
| BFS | невзвешенный | кратчайший путь по количеству ребер |
| Dijkstra | взвешенный, веса неотрицательные | кратчайший путь по сумме весов |
Когда нельзя использовать Дейкстру?
Нельзя использовать обычную Дейкстру, если есть отрицательные веса.
1 → 3 вес 2
3 → 2 вес -10
Здесь отрицательное ребро может сломать логику алгоритма.
Асимптотика
| Реализация | Время | Память |
|---|---|---|
| priority_queue | O((n + m) log n) | O(n + m) |
| обычный массив | O(n² + m) | O(n + m) |
В олимпиадных задачах чаще всего используют реализацию через priority_queue.
Короткий шаблон
const long long INF = 4e18;
vector<pair<int, int>> g[N];
long long dist[N];
void dijkstra(int s) {
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
dist[s] = 0;
q.push({0, s});
while (!q.empty()) {
auto cur = q.top();
q.pop();
long long d = cur.first;
int v = cur.second;
if (d != dist[v]) {
continue;
}
for (auto edge : g[v]) {
int to = edge.first;
int w = edge.second;
if (dist[v] + w < dist[to]) {
dist[to] = dist[v] + w;
q.push({dist[to], to});
}
}
}
}