Дейкстра алгоритмі деген не?
Дейкстра алгоритмі — бір start вершинадан барлық басқа вершинаға дейінгі ең қысқа расстояние табатын алгоритм.
Бұл алгоритм реброларда вес болған кезде қолданылады. Бірақ барлық вес теріс емес болуы керек.
Қай жерде қолданылады?
- бір вершинадан барлық вершинаға shortest distance табу
- екі вершина арасындағы shortest path табу
- реброларында вес бар графтарда
- карта, жол, маршрут задачаларында
- минималды стоимость табу керек болса
Негізгі идея
Біз dist массивін сақтаймыз.
dist[v]
Бұл start вершинадан v вершинасына дейінгі ең қысқа расстояние.
Басында:
dist[қалғандары] = INF
Кейін біз расстояниелерді біртіндеп жақсартамыз.
онда жақсырақ путь таптық
dist[to] жаңартамыз
if (dist[v] + w < dist[to]) {
dist[to] = dist[v] + w;
}
Relaxation деген не?
Relaxation — бір вершинаға дейінгі distance-ты жақсарту әрекеті.
Мысалы:
v → to ребросының весі 3
онда to-ға v арқылы бару: 5 + 3 = 8
Егер бұрын:
Жаңа жол жақсырақ:
dist[to] = 8
Неге priority_queue керек?
Әр қадамда біз ең кіші distance бар вершина алуымыз керек.
Ол үшін C++ ішінде:
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 ішіне pair саламыз:
Граф мысалы
Мынадай граф болсын:
1 --2-- 3
3 --1-- 2
2 --3-- 4
3 --7-- 4
Start вершина: 1.
Ең қысқа distance:
| Вершина | 1-ден distance |
|---|---|
| 1 | 0 |
| 2 | 3 |
| 3 | 2 |
| 4 | 6 |
Неге 2-ге distance 3?
2 + 1 = 3
Неге 4-ке distance 6?
2 + 1 + 3 = 6
Граф структурасы
Реброда вес болғандықтан, adjacency list ішінде pair сақтаймыз:
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 вершинасына distance 10 таптық. Кейін жақсырақ жол тауып, distance 3 болды.
{10, 2}
{3, 2}
Егер біз ескі {10, 2} pair-ды алсақ, ол енді керек емес. Сондықтан оны өткізіп жібереміз.
if (d != dist[v]) {
continue;
}
Задача 1: бір вершинадан distance
Неориентированный weighted graph берілген. s вершинасынан барлық вершинаға дейінгі shortest distance табу керек.
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;
}
Shortest path қалпына келтіру
Егер тек distance емес, жолдың өзін шығару керек болса, p массиві керек.
int p[N];
Біз to вершинасына distance жақсартқанда, қайдан келгенімізді сақтаймыз:
p[to] = v;
Бұл дегеніміз:
Path қалай қалпына келеді?
Finish вершинадан бастап 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: shortest path шығару
Граф, start s және finish t берілген. s-тен t-ға дейінгі shortest path шығару керек.
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
Path-пен толық реализация
#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 | невзвешенный | ребро саны бойынша shortest path |
| Dijkstra | weighted, вес теріс емес | вес суммасы бойынша shortest path |
Дейкстраны қашан қолдануға болмайды?
Егер графта negative weight болса, ordinary Дейкстраны қолдануға болмайды.
1 → 3 вес 2
3 → 2 вес -10
Negative edge алгоритм логикасын бұзуы мүмкін.
Асимптотика
| Реализация | Уақыт | Память |
|---|---|---|
| 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});
}
}
}
}