Алгоритми · Теорія графів
📅 Липень 2026 ⏱ ≈ 11 хв читання 🎯 Середній рівень · Останнє оновлення: 9 липня 2026 р.

Алгоритм Дейкстри: доведення коректності

Кожен вчить запускати алгоритм Дейкстри; значно менше людей вчать, чому він працює. Доведення тримається на одному-єдиному інваріанті (властивості, яка залишається незмінно істинною на кожному кроці алгоритму), що зберігається на кожній ітерації, і саме воно пояснює, чому алгоритм тихо видає неправильні відповіді, щойно з'являється від'ємна вага ребра.

Коротко: Ця стаття доводить, чому алгоритм Дейкстри справді знаходить найкоротші шляхи: оцінка відстані ніколи не опускається нижче істинного значення, а вершина, яку алгоритм обирає наступною, вже має остаточно правильну відстань. Також на конкретному прикладі показано, чому від'ємні ваги ребер ламають це доведення, і наведено реалізацію на пріоритетній черзі з аналізом складності.

Постановка задачі та позначення

Нехай G = (V, E) — орієнтований зважений граф з невід'ємними вагами ребер w(u, v) ≥ 0, і вершина-джерело s. Позначимо δ(s, v) як справжню відстань найкоротшого шляху від s до v (величину, яку ми хочемо обчислити). Алгоритм Дейкстри підтримує для кожної вершини v оцінку d[v], яка починається з ∞ (крім d[s] = 0) і лише зменшується.

Інваріант, який маємо довести: у момент, коли вершина u стає «завершеною» (востаннє вилучена з фронтиру), d[u] = δ(s, u)

Алгоритм

Дейкстра підтримує множину S «завершених» вершин (чия найкоротша відстань остаточно визначена) і повторно вилучає незавершену вершину u з найменшим d[u], додаючи її до S, а потім релаксує кожне вихідне ребро (u, v):

RELAX(u, v, w):
  якщо d[v] > d[u] + w(u, v):
    d[v] = d[u] + w(u, v)
    prev[v] = u

Усе доведення коректності спирається на одну властивість RELAX: вона ніколи не робить d[v] меншим за δ(s, v) — вона може лише наближатись до істини зверху, ніколи не «перестрибуючи» знизу.

Інваріант релаксації

Лема (властивість верхньої межі): у будь-який момент виконання алгоритму d[v] ≥ δ(s, v) для кожної вершини v.

Доведення індукцією за кількістю викликів RELAX. Спочатку d[s] = 0 = δ(s, s), а d[v] = ∞ ≥ δ(s, v) для всіх інших v — базовий випадок тривіально виконується. Припустимо, що інваріант виконується перед викликом RELAX(u, v, w). Якщо виклик змінює d[v], нове значення дорівнює d[u] + w(u, v). За індуктивним припущенням d[u] ≥ δ(s, u), тому:

d[v]_нове = d[u] + w(u, v) ≥ δ(s, u) + w(u, v) ≥ δ(s, v)
останній крок: δ(s,v) ≤ δ(s,u) + w(u,v) — це нерівність трикутника для найкоротших шляхів — проходження через u ніколи не може перевершити прямий найкоротший шлях

Отже, інваріант зберігається при кожному виклику RELAX, і за індукцією виконується протягом усього виконання. Цей єдиний факт — d ніколи не опускається нижче істини — є несучою стіною всього доведення.

Доведення індукцією за порядком завершення

Теорема: коли алгоритм Дейкстри додає вершину u до S (завершує її), d[u] = δ(s, u).

Доведення сильною індукцією за порядком завершення вершин. Нехай u — k-та завершена вершина, і припустимо, що теорема виконується для перших k − 1 завершених вершин.

Розгляньмо справжній найкоротший шлях P від s до u. Нехай y — перша вершина на P, яка ще не в S у момент завершення u (можливо y = u, якщо весь шлях уже завершений до u), а x — її попередник на P (x ∈ S, або x = s). Оскільки всі ваги ребер невід'ємні, і оскільки x був завершений до u, RELAX(x, y, ·) було викликано, коли x завершувався, тому за доведеним вище інваріантом:

d[y] ≤ δ(s, x) + w(x, y) = δ(s, y)  (за індуктивним припущенням для x, і оскільки y лежить на найкоротшому шляху через x)
разом з лемою про верхню межу: d[y] ≥ δ(s, y)  → d[y] = δ(s, y)

Тепер, оскільки ваги ребер невід'ємні, кожна вершина після y на шляху P (включно з самою u) знаходиться не ближче до s, ніж y: δ(s, y) ≤ δ(s, u). Але Дейкстра обрав саме u (не y) як вершину з мінімальним значенням d серед усіх незавершених вершин, тому d[u] ≤ d[y] = δ(s, y) ≤ δ(s, u). Разом з лемою про верхню межу d[u] ≥ δ(s, u), отримуємо d[u] = δ(s, u).

Суть: крок «d[u] ≤ d[y]» виконується лише тому, що алгоритм завжди вилучає мінімальне значення d серед незавершених. А крок «δ(s,y) ≤ δ(s,u)» виконується лише тому, що ваги невід'ємні — шлях ніколи не може стати коротшим, продовжившись за y. Обидва кроки — саме там, де від'ємні ваги зруйнували б доведення.

Жадібний аргумент обміну

Еквівалентний, більш інтуїтивний спосіб побачити той самий результат: Дейкстра — це жадібний алгоритм, а жадібні алгоритми коректні саме тоді, коли виконуються і «властивість жадібного вибору», і «оптимальна підструктура».

Оскільки обидві властивості виконуються при невід'ємних вагах, аргумент обміну доходить висновку: заміна жадібного вибору будь-яким іншим не може покращити рішення, тому жадібна стратегія оптимальна на кожному кроці, а отже, глобально оптимальна.

Чому від'ємні ваги ламають доведення

Розгляньмо s → a (вага 2), s → b (вага 1), a → b (вага −3). Після релаксації обох ребер із s: d[a] = 2, d[b] = 1, тому Дейкстра завершує b першим (найменша тимчасова відстань) — ще до того, як буде оброблено вершину a, яка утримує єдине ребро, здатне покращити відстань до b. Щойно b завершено, алгоритм більше ніколи не релаксує ребро, що веде до нього.

КрокЗавершеноd[a]d[b]Коректно?
0{s}21✓ поки що
1{s, b}21✗ b завершено зарано — a→b ще не релаксовано
2{s, a, b}21✗ справжнє δ(s,b) = 2+(−3) = −1, але d[b] заморожено на 1
Корінь проблеми: індуктивний крок доведення передбачав, що «все, що йде після y на найкоротшому шляху, знаходиться не ближче, ніж y» — а це вимагає невід'ємних ваг. З від'ємними вагами шлях може ставати коротшим навіть після завершення вершини, і жадібний принцип «заверши мінімум і ніколи не повертайся» більше не безпечний. Алгоритм Беллмана-Форда (O(VE), допускає від'ємні ваги, виявляє від'ємні цикли) або алгоритм Джонсона (перезважування + Дейкстра) — правильні інструменти, коли можливі від'ємні ребра.

Реалізація на пріоритетній черзі

function dijkstra(graph, source) {
  // graph: Map<вершина, Array<[сусід, вага]>>, всі ваги >= 0
  const dist = new Map();
  const prev = new Map();
  const settled = new Set();
  const pq = new MinHeap();  // бінарна мін-купа за ключем-відстанню

  for (const v of graph.keys()) dist.set(v, Infinity);
  dist.set(source, 0);
  pq.push(source, 0);

  while (!pq.isEmpty()) {
    const u = pq.pop();          // вершина з найменшою тимчасовою відстанню
    if (settled.has(u)) continue;  // застарілий запис купи — пропустити
    settled.add(u);                // u тепер остаточно завершено

    for (const [v, w] of graph.get(u)) {
      if (settled.has(v)) continue;
      const candidate = dist.get(u) + w;
      if (candidate < dist.get(v)) {   // крок RELAX
        dist.set(v, candidate);
        prev.set(v, u);
        pq.push(v, candidate);
      }
    }
  }
  return { dist, prev };
}
«Застарілий запис купи — пропустити»: звичайна бінарна купа не підтримує ефективний decrease-key, тому більшість реалізацій просто додають нову пару (вершина, відстань) при кожній релаксації і відкидають застарілі, вже завершені записи при вилученні. Це коштує сталого множника розміру купи, але зберігає код простим.

Підсумок складності

Пріоритетна чергаExtract-minDecrease-keyЗагальна складність
Масив (лінійний перебір)O(V)O(1)O(V²)
Бінарна купаO(log V)O(log V)O((V + E) log V)
Купа ФібоначчіO(log V) амортизованоO(1) амортизованоO(E + V log V)

Для щільних графів (E ≈ V²) проста реалізація на масиві насправді найшвидша на практиці через низькі сталі множники. Для розріджених графів — типового випадку в дорожніх мережах та ігрових картах — стандартним вибором є версія з бінарною купою.

🗺️ Подивитись, як працює Дейкстра наживо

Крок за кроком пройдіть інваріант релаксації на сітці лабіринту, порівняйте з A* та BFS поруч

Відкрити симуляцію →