Означення 1. Запровадимо такі поняття:
Граф, у якому кожному ребру (кожній дузі) поставлено у відповідність певне невід'ємне число, яке називають вагою або довжиною ребра (дуги), називають зваженим графом.
Довжиною шляху (машруту) у зваженому графі називають суму довжин ланок цього шляху (машруту).
Для формулювання алгоритмів пошуку найкоротшого шляху скористаємося такими позначеннями для матриць-рядків A = (a1; a2; ... ; an), B = (b1; b2; ... ; bn) і числа c:
Методом «від супротивного» можна довести таке твердження.
Теорема 1. Якщо v1 v2 …
vj – 1 vj vj + 1 …
vk – 1 vk vk + 1 … vn є найкоротшим шляхом між вершинами v1 і vn зваженого графу, то шлях vj vj + 1 ... vk – 1 vk
є найкоротшим шляхом між вершинами vj і vk .
Не обмежуючи загальності міркувань, обмежимося пошуком найкоротшого шляху від вершини v1 до вершини vn з максимальним номером.
У першому алгоритмі кожній вершині vk поставимо у відповідність пару (m; vj ), де:
m — найменшша з розглянутих довжин шляху від вершини v1 до вершини vk;
vj — попередня для vk вершина на такому шляху з найменшшою довжиною.
Спочатку кожній вершині поставимо у відповідність пару (+ ∞ ;0).
Алгоритм Дейкстри (Dijkstra's algorithm)
Після того, як вершина vk(m; vj) стає сталою, для кожної вершини vi, що суміжна з вершиною vk, знаходимо суму m і довжину ребра (дуги) з vk до vi. Якщо знайдена сума менша за знайдену поточну відстань від v1 до vi, то замінюємо цю поточну відстань на знайдену суму, а другу координату вершини vi — на vk.
Знаходимо мінімум відстаней, приписаних змінним вершинам. Першу вершину з такою відстанню від v1 оголошуємо сталою.
Якщо vn ще не оголошено сталою вершиною, то повертаємося на виконання пункту 2.
Якщо vn оголошено сталою вершиною, то відстань, приписана цій вершині, є найменшою довжиною шляху від v1 до vn.
Один з можливих найкоротших шляхів від v1 до vn відновлюємо «з кінця», починаючи з вершини vn, переходом до попередньої вершини найкоротшого шляху (див. другу координату пари, приписаної до кожної вершини).
Подамо приклад програми мовою Turbo Pascal 7.0 з коментарями щодо структури вхідних та вихідних даних.
{$I+}{$N+} {Верхні межі:} const nv_max=2000; {кількості вершин} ne_max=6000; {кількості ребер } l_max=2147483647; {довжини шляху} start=1; {Номери вершини,} finish=9; {між якими шукаємо шлях} type pointe=^edge; edge=record {Ребро з вершини:} v: word; {суміжна вершина} w: longint; {вага ребра} n: pointe end; {наступне ребро, інцендентне даній вершині} pointer=array[1..nv_max] of pointe; previous=array[1..nv_max] of word; length=array[1..nv_max] of longint; constant=array[1..nv_max] of byte; var p:^previous; l:^length; e: pointe; n0,n:^pointer; c:^constant; nv, {Кількість вершин} ne, {Кількість ребер} j, {Номер точки, оголошеної сталою} j1,j2: word; w12: longint; o: text; {Кроки 2-3 алгоритму Дейкстри} procedure STEP (var j: word); var k,vmin: word; stop: boolean; s,lmin: longint; BEGIN e:=n0^[j]; stop:=false; REPEAT if c^[e^.v]=0 then begin s:= l^[j]+e^.w; if s < l^[e^.v] then begin l^[e^.v]:=s; p^[e^.v]:=j end end; if e^.n <> nil then e:=e^.n else stop:=true; UNTIL stop; {Пошук вершини j, до якої найменша з невстановлених відстаней} s:=l_max; for k:=1 to nv do if c^[k]=0 then if s > l^[k] then begin s:= l^[k]; j:=k end; c^[j]:=1 END; BEGIN nv:=0; new(n0); new(n); for j:=1 to nv_max do n0^[j]:=nil; assign(o,'DIJKSTRA.DAT'); reset(o); {Зчитування рядків вхідного файлу, кожний з яких містить трійку чисел: номери кінців ребра й вага ребра} REPEAT inc(ne); readln(o,j1,j2,w12); if j1 > nv then nv:=j1; if j2 > nv then nv:=j2; new(e); if n0^[j1]=nil then n0^[j1] :=e else n^[j1]^.n:=e; n^[j1]:=e; e^.v:=j2; e^.w:=w12; e^.n:=nil; new(e); if n0^[j2]=nil then n0^[j2] :=e else n^[j2]^.n:=e; n^[j2]:=e; e^.v:=j1; e^.w:=w12; e^.n:=nil; UNTIL seekeof(o); close(o); dispose(n); new(p); new(l); new(c); for j:=1 to nv do begin l^[j]:=l_max; p^[j]:=0; c^[j]:=0 end; c^[start]:=1; l^[start]:=0; j:=start; repeat STEP(j) until j=finish; {Запис у вихідний файл 1) довжини найкоротшого шляху; 2) послідовнисті номерів вершин шляху у зворотньому порядку} assign (o,'DIJKSTRA.RES'); rewrite(o); writeln(o,l^[finish]); write (o,finish); j:=finish; repeat j:=p^[j]; write(o,' ',j) until j=start; writeln(o); close (o) END.
Подану програму можна удосконалити щодо економного використання оперативної пам'яті та процесорного часу, подавши інформацію про інцендентні ребра чергами.
У наступному алгоритмі скористаємося такими позначеннями:
W — матриця, що має n рядків і n стовпчиків;
W(i, j) — елемент такої матриці, розташований у i-му рядку й j-му стовпчику. Цей елемент дорівнює відстані від вершини vi до вершини vj для суміжних вершин та +∞ в іншому випадку, W(j, j) = 0;
W(j) — j-ий рядок матриці W;
Pr(j) — елемент матриці-рядка Pr — дорівнює номеру вершини, що беспосередньо передує вершині vj на найкоротшому шляху від v1;
P(j) — елемент матриці-рядка P, що дорівнює справжній найменшій відстані від вершини v1 до vj;
T(j) — елемент матриці-рядка T, що дорівнює поточній найменшій (серед розглянутих) відстані від вершини v1 до vj;
V — номер вершини, яку оголошеною сталою останньою;
S — матриця-рядок для зберігання суми P(V) + W(V).
Тут всі матриці-рядки мають n елементів.
Алгоритм Дейкстри (матричне подання)
Надаємо таких величин: V = 1; P(1) = 0; T(1) = + ∞ ; W(j, 1) = + ∞ при 1 ≤ j ≤ n (вершину 1 оголошено сталою).
Якщо i відмінне від n, то повертаємося на виконання пункту 2.
Якщо i = n, то P(i) є найменшою довжиною шляху від v1 до vn.
Один з можливих найкоротших шляхів від v1 до vn відновлюємо «з кінця», починаючи з вершини vn, переходом до попередньої вершини найкоротшого шляху (за елементами матриці-рядка Pr).
На початку виконання наступного алгоритму зміст елементів матриці W такий самий, що й у попередньому алгоритмі. Алгоритм полягає у перетворенні матриці W з метою заміни елементів, що спочатку дорівнюють +∞ , на довжини найкоротших маршрутів з кількістю ланок до 2, 3, ... (n – 1), що проходять через вершини v1, v2, ... , vn
Алгоритм Флойда-Уоршолла (Floyd-Warshall algorithm)
Розглянемо всі j у межах від 1 до n, що задовольняють умову 0 < W(j, 1) < +∞ . Тобто розглянемо всі додатні елементи першого стовпчика матриці W. Для кожного такого j замінимо j-ий рядок матриці W на min(W(j), W(j, 1) + W(1)).
Розглянемо всі j у межах від 1 до n, що задовольняють умову 0 < W(j, 2) < +∞ . Тобто розглянемо всі додатні елементи другого стовпчика матриці W. Для кожного такого j замінимо j-ий рядок матриці W на min(W(j), W(j, 2) + W(2)).
Розглянемо всі j у межах від 1 до n, що задовольняють умову 0 < W(j, 3) < +∞ . Тобто розглянемо всі додатні елементи третього стовпчика матриці W. Для кожного такого j замінимо j-ий рядок матриці W на min(W(j), W(j, 3) + W (3))…
Виконання алгоритму продовжувати, поки всі стопчики не буде переглянуто.
Алгоритм можна записати ще таким чином:
змінюючи k від 1 до n включно,
змінюючи i від 1 до n включно,
змінюючи j від 1 до n включно:
замінимо W(i, j) на min(W(i, j), W(i, k) + W(k, j)).