Алгоритъмът на Дейкстра в Python & C++ (Пример)

⚡ Умно обобщение

Алгоритъмът на Дейкстра изчислява най-краткия път от един изходен връх до всеки друг връх в претеглен граф с неотрицателни ръбове. Този алчен метод е в основата на Google Маршрутизиране на карти, OSPF IP маршрутизиране и безброй случаи на използване на най-краткия път в мрежата.

  • 🎯 Основна идея: Алгоритъмът на Дейкстра ненаситно разширява най-близкия непосетен връх, актуализирайки разстоянията между съседите, докато всеки достижим възел не достигне истинската си най-къса цена от източника.
  • 🔄 Срещу BFS и DFS: BFS и DFS намират всеки път, без да се вземат предвид теглата на ръбовете, докато Дейкстра минимизира общите разходи за претеглените ръбове.
  • 🧭 Пример стъпка по стъпка: Разработена 7-върхова претеглена графика показва как разстоянията се актуализират итеративно и как пътят 1-2-6-7 печели с цена 7.
  • 💻 Езиково покритие: И двете C++ намлява Python Имплементациите демонстрират версията с матрица на съседство с функция за избор на минимално разстояние.
  • ⚠️ Ограничение: Дейкстра не успява при отрицателни тегла на ръбовете, защото финализиран възел никога не се преразглежда; използвайте Bellman-Ford за графи с отрицателни ръбове.
  • 📊 Сложност: Наивната версия с масиви се изпълнява във време и пространство O(V²); опашката с приоритет намалява времето до O(E log V) за разредени графове.

Алгоритъмът за най-кратък път на Дейкстра

Кой е най-краткият път или най-късото разстояние?

Път от изходния връх до крайния връх, който струва минимална сума, е най-краткият път или най-късото разстояние. В теорията на графите е възможно да има множество маршрути от източник до крайния връх. Сред тези маршрути, ако има маршрут, който струва минимална сума, го наричаме най-краткият път.

Тук „цена“ означава броя на възлите в маршрута или сумирането на разходите на всеки ръб. Пътят може да има един или множество ръбове. Връзката между два върха се нарича „ръб“. Съществуват различни видове алгоритми за най-краткия път, като например алгоритъма на Дейкстра и алгоритъма на Белман-Форд.

Тук ще обсъдим алгоритъма на Дейкстра. Нека разгледаме следната претеглена графика:

Неориентирана претеглена графика

Неориентирана-претеглена графика

  • Терминът „претеглено“ означава цената на преместването от един възел към друг. Например, при преместване от възел 1 към възел 2, цената или теглото е 1.
  • Пътят между възел 1 и възел 2 се нарича ръб.
  • „Неориентиран“ означава, че можете да се движите от един възел към друг и обратно към предишния възел. Така че, ако се опитаме да намерим всички маршрути от възел 1 до възел 7, те ще бъдат:
Маршрут или Пътекацена
1-2-6-7(1+3+3) = 7
1-2-3-7(1+9+1) = 11
1-3-7(7+1) = 8
1-4-5-7(6+2+5) = 13

Сред тези четири маршрута виждаме, че първият маршрут струва 7. Така че, това е най-краткият път по отношение на разходите.

Най-краткият път

Най-краткият път

Как работи алгоритъмът на Дейкстра

Алгоритъмът на Дейкстра може да намери най-късото разстояние както в насочени, така и в ненасочени претеглени графи. Този алгоритъм е алчен, защото винаги избира най-късия или най-близкия възел от началото на координатната система. Терминът „алчен“ означава, че измежду набор от резултати или резултати алгоритъмът ще избере най-добрия от тях.

Тук се опитваме да намерим най-късите пътища сред всички останали маршрути. Така че, алгоритъмът на Дейкстра намира всички най-къси пътища от един възел източник. В резултат на това той се държи като алчен алгоритъм.

В раздела „пример“ по-долу ще видите стъпка по стъпка подхода. Той работи по следния начин:

Стъпка 1) Инициализирайте началния възел с 0 цена, а останалите възли с безкрайна цена.
Стъпка 2) Поддържайте масив или списък, който да съхранявате track от посетените възли.
Стъпка 3) Актуализирайте цената на възела с минималната цена. Това може да се направи чрез сравняване на текущата цена с цената на пътя (показано в примерния раздел).
Стъпка 4) Продължете стъпка 3, докато не бъдат посетени всички възли.

След като изпълним всички тези стъпки, ще намерим пътя, който струва минимум от източника до дестинацията.

Разлика между Dijkstra и BFS, DFS

Основната разлика между алгоритъма на Дейкстра и BFS-DFS е, че Дейкстра е алгоритъм за намиране на най-кратък път, докато BFS и DFS са общи алгоритми за намиране на път. В общи случаи BFS и DFS не отчитат цената на ръба при намиране на пътя. Следователно, тези алгоритми не могат да гарантират най-краткия път.

2D мрежова демонстрация на това как работи BFS

2D демонстрация на мрежа BFS

Algosketch, показващ BFS демонстрация

Тази демонстрация показва, че BFS намира само пътя. Въпреки това не се интересува от теглото на пътя. BFS (Търсене в ширина) предполага, че пътуването от един възел до друг възел ще струва само 1.

Нека видим примерна графика:

Примерна графика за демонстрация на 2D мрежа

Тук BFS намира път на ниво 2. BFS обхожда графа по ред на нивата. Така че, той се движи по следния начин:

Стъпка 1) Започнете от възел „1“ и посетете всички съседни възли 2, 3, 4.

Стъпка 2) Маркирайте възли 2, 3, 4 като ниво 1 и посетете съседните им възли. Ще продължи да изследва всички съседни възли, докато достигне целевия възел.

По отношение на DFS, той ще измине пътя от 1 до 7, както следва:

  • 1→2→3→7 (Първоначална цена 10, DFS цена 3)
  • 1→2→6→7 (Първоначална цена 7, DFS цена 3)
  • 1→3→7 (Първоначална цена 8, DFS цена 2)
  • 1→4→5→7 (Първоначална цена 13, DFS цена 3)

Както виждаме, DFS изчислява цената на пътя си с броя на ребрата. DFS прави следното:

  • DFS може да намери път от източника (началния връх) до дестинацията.
  • Не може да гарантира дали откритият път от изходния възел до дестинацията е най-краткият път или не.

Въпреки това, според алгоритъма на Дейкстра, той избира ръбовете въз основа на тяхната цена. Като алчен алгоритъм, той ще избере пътища с минимална цена.

Пример за алгоритъм на Дейкстра

Алгоритъмът на Дейкстра използва цената или теглото, за да изчисли общата цена на пътя.

Пример за алгоритъм на Дейкстра

Целта на алгоритъма на Дейкстра е да минимизира тази обща цена или тегло. В примера, показан по-горе, намираме най-добрите пътища от възел 1 до възел 7, след което изчисляваме всички разходи.

В алгоритъма на Дейкстра, той ще намери най-късите пътища чрез изчисляване на тегла. Той няма да търси всички възможни пътища. Нека демонстрираме алгоритъма на Дейкстра с пример. Например, помолени сте да намерите най-късия път от възел 1 до възел 7.

За този процес са дадени стъпки по-долу:

Стъпка 1) Инициализирайте цената на началния възел на 0. Присвоете „Инф“ към останалите възли. Това означава, че не съществува път между източника и възела или пътят все още не е посетен.

Инициализация на алгоритъма на Дейкстра

Стъпка 2) Когато изберете възел 1, той ще бъде маркиран като посетен. След това актуализирайте всички съседни възли на възел 1. 2, 3, 4 са съседните възли на възел 1.

Докато актуализираме цена, трябва да следваме процедурата по-долу:

Процедура за актуализиране на алгоритъма на Дейкстрас

Можем да актуализираме цената на всеки възел, използвайки горната формула. Например, бяхме на възел 1 и трябваше да актуализираме цената на съседните му възли 2, 3, 4. След актуализирането, цените ще изглеждат така:

Алгоритъмът на Дейкстрас след първата актуализация

Стъпка 3) За възел „2“, съседите са 6 и 3. Актуализираме цената на „6“, като сравняваме безкрайността (текущата стойност) с цената на възел 2 + цената на пътя от 2 до 6. Просто казано, възел „6“ ще има цената 1+3 или 4.

Актуализация на алгоритъма на Дейкстрас, възел 6

Възел 3 е съсед на възел 2. Ние обаче изчислихме цената му в предишната стъпка, която беше 7. Сега, ако нашият път е 1-2-3, възел 3 ще има цена 10. Път 1-2- 3 ще струват 10, докато 1 до 3 ще струват 7.

Стъпка 4) За възел 3, съседният възел е 7. Така че, сравнявайки текущата стойност на възел 7 с цената на пътя (7+1) или 8, ще актуализираме цената на възел 7. Това е 8. Така че, намираме път от възел 1 до възел 7 и той е 1→3→7. Цената е 8.

Стъпка 5) За възел 4 ще актуализираме съответно цената на съседния му възел. Така възел „5“ ще има актуализирана цена от 8. След стъпки 4 и 5, ще изглежда така:

Алгоритъм на Дейкстрас след стъпка 4 5

Сега пътят 1-3-7 има цена 8 (преди). Възел „7“ не беше маркиран като посетен, защото можем да достигнем до възел „7“ от възел „6“. Пътят „1-2-6“ имаше цена 4. Така че пътят 1-2-6-7 ще има цена 7.

Тъй като 7 < 8, най-краткият път от изходния връх „1“ до крайния връх „7“ ще бъде 1-2-6-7, а цената е 7. Преди беше 1-3-7, а цената беше 8. Така че, крайната графика ще изглежда така:

Финална графика на алгоритъма на Дейкстрас

Ръбът, маркиран с черна линия, е най-краткият ни път от 1 до 7 и ще ни струва 7.

Прякор Code Алгоритъмът на Дейкстра

Ето псевдокода за алгоритъма на Дейкстра:

Dijkstra(G, S):
  for each vertex V in G
    distance[V] <- Infinity
    previous[V] <- NULL
    if V does not equal S, then,
      (priority queue) Q.push(V)
  distance[S] = 0
  While Q is not empty
    U <- Extract the MIN from Q
    For each unvisited adjacent V of U
      TotalDistance <- distance[U] + edge_cost(U, V)
      if TotalDistance is less than distance[V], then
        distance[V] <- TotalDistance
        previous[V] <- U
  return distance, previous

C++ Прилагане на алгоритъма на Дейкстра

За прилагане на алгоритъма на Дейкстра с помощта на C++, ето кода:

#include <bits/stdc++.h>
using namespace std;
#define size 7
int minimumDistance(int distance[], bool visited[]) {
  int min = INT_MAX;
  int min_index = INT_MAX;
  for (int i = 0; i < size; i++) {
    if (!visited[i] && distance[i] <= min) {
      min = distance[i];
      min_index = i;
    }
  }
  return min_index;
}
void printParentPath(int parent[], int i) {
  if (parent[i] == -1) {
    return;
  }
  printParentPath(parent, parent[i]);
  cout << i + 1 << " ";
}
void dijkstra(int graph[size][size], int source) {
  int distance[size];
  bool visited[size];
  int parent[size];
  for (int i = 0; i < size; i++) {
    parent[0] = -1;
    distance[i] = INT_MAX;
    visited[i] = false;
  }
  distance[source] = 0;
  for (int i = 0; i < size - 1; i++) {
    int U = minimumDistance(distance, visited);
    visited[U] = true;
    for (int j = 0; j < size; j++) {
      int curr_distance = distance[U] + graph[U][j];
      if (!visited[j] && graph[U][j] &&
          curr_distance < distance[j]) {
        parent[j] = U;
        distance[j] = curr_distance;
      }
    }
  }
  cout << "Vertex\t\tDistance\tPath" << endl;
  for (int i = 1; i < size; i++) {
    cout << source + 1 << "->" << i + 1 << "\t\t" << distance[i] << "\t\t"
         << source + 1 << " ";
    printParentPath(parent, i);
    cout << endl;
  }
}
int main() {
  int graph[size][size] = {{0, 1, 7, 6, 0, 0, 0}, {1, 0, 9, 0, 0, 3, 0},
                           {7, 9, 0, 0, 0, 0, 1}, {6, 0, 0, 0, 2, 0, 0},
                           {0, 0, 0, 2, 0, 0, 0}, {0, 3, 0, 0, 0, 0, 3},
                           {0, 0, 0, 0, 5, 3, 0}};
  dijkstra(graph, 0);
}

Изход:

Vertex     Distance        Path

1->2           1             1 2
1->3           7             1 3
1->4           6             1 4
1->5           8             1 4 5
1->6           4             1 2 6
1->7           7             1 2 6 7

Python Прилагане на алгоритъма на Дейкстра

За прилагане на алгоритъма на Дейкстра с помощта на Python, ето кода:

num_of_vertex = 7
def minimumDistance(distance, visited):
    _min = 1e11
    min_index = 1e11
    for i in range(num_of_vertex):
        if not visited[i] and distance[i] <= _min:
            _min = distance[i]
            min_index = i
    return min_index

def printParentNode(parent, i):
    if parent[i] == -1:
        return
    printParentNode(parent, parent[i])
    print("{} ".format(i + 1), end="")

def dijkstra(graph, src):
    distance = list()
    visited = list()
    parent = list()
    for i in range(num_of_vertex):
        parent.append(-1)
        distance.append(1e11)
        visited.append(False)
    distance[src] = 0
    for i in range(num_of_vertex - 1):
        U = minimumDistance(distance, visited)
        visited[U] = True
        for j in range(num_of_vertex):
            curr_distance = distance[U] + graph[U][j]
            if not visited[j] and graph[U][j] and curr_distance < distance[j]:
                parent[j] = U
                distance[j] = curr_distance
    print("Vertex\t\tDistance\tPath")
    for i in range(num_of_vertex):
        print("{}->{}\t\t{}\t\t{} ".format(src + 1, i + 1, distance[i], src + 1), end="")
        printParentNode(parent, i)
        print("")

graph = [
    [0, 1, 7, 6, 0, 0, 0],
    [1, 0, 9, 0, 0, 3, 0],
    [7, 9, 0, 0, 0, 0, 1],
    [6, 0, 0, 0, 2, 0, 0],
    [0, 0, 0, 2, 0, 0, 0],
    [0, 3, 0, 0, 0, 0, 3],
    [0, 0, 0, 0, 5, 3, 0]
]
dijkstra(graph, 0)

Изход:

Vertex     Distance        Path

1->1           0              1
1->2           1              1 2
1->3           7              1 3
1->4           6              1 4
1->5           8              1 4 5
1->6           4              1 2 6
1->7           7              1 2 6 7

Виждаме, че алгоритъмът изчислява най-късото разстояние от изходния възел.

Приложение на алгоритъма на Дейкстра

Алгоритъмът на Дейкстра има широк набор от приложения. Сред тях той се използва широко в областта на мрежите. Ето някои приложения на алгоритъма на Дейкстра в реалния живот:

Дейкстра в Google България: Този алгоритъм е гръбнакът за намиране на най-кратките пътища, както можем да видим от горния фрагмент от код.

Приложение на алгоритъма на Дейкстра Google Карти

Google не използва простия алгоритъм на Дейкстра. Вместо това, той използва модифицирана версия. Когато изберете дестинация, той ви показва множество пътища в Google Карти. Сред тези пътища някои са сортирани за потребителя. Тези пътища са избрани въз основа на „време“. Така че „времето“ е цената на ръба за най-краткия път.

Дейкстра в IP маршрутизацията: IP маршрутизиране е мрежова терминология. Тя описва как вашият пакет данни се изпраща до получателя по различни пътища. Тези пътища се състоят от рутери, сървъри и друго оборудване. При IP маршрутизацията има различни видове протоколи.

Тези протоколи помагат на рутера да намери най-кратките пътища за изпращане на данните. Едно от имената на протоколите е „OSPF (Open Shortest Path First)“. OSPF използва алгоритъма на Дейкстра. Рутерът поддържа таблица с маршрути. Всеки рутер споделя своята таблица със съседните рутери. След получаване на актуализираната таблица, те трябва да изчислят всички пътища отново. В този момент рутерът използва алгоритъма на Дейкстра.

Ограничение на алгоритъма на Дейкстра

Алгоритъмът на Дейкстра не може да гарантира най-краткия път в граф с отрицателни ръбове. Алгоритъмът на Дейкстра следва следните принципи:

  • Един най-кратък път ще бъде изведен от един възел до друг.
  • След като бъде избран най-краткият път между два възела, той няма да бъде изчислен отново.

Тук забележете два примера с отрицателни ръбове.

Ограничение на отрицателните ръбове на алгоритъма на Дейкстрас

В лявата графика, има три върха. Дейкстра ще се изпълни върху графа по следния начин:

Стъпка 1) Началният връх “1” ще бъде инициализиран на нула. Останалите възли ще имат безкрайност.

Ограничение на стъпка 1 от алгоритъма на Дейкстра

Стъпка 2) Маркирайте възел „1“ като посетен и го включете в най-краткия път.

Стъпка 3) Разстоянието от изходния възел 1 до възлите „2“ и „3“ е зададено на безкрайност, тъй като най-късият път все още не е изчислен. Така че всеки път, който струва по-малко от безкрайност, ще бъде добавен към най-късия път (алчен подход).

Стъпка 4) Актуализиране на разстоянието от изходния връх „1“ на „2“. Текущото тегло ще бъде 5 (5 < безкрайност). По подобен начин актуализирайте разстоянието от възел „1“ на „3“ с тегло 3.

Ограничение на стъпка 4 от алгоритъма на Дейкстра

Стъпка 5) Сега, ако проверим най-късите разстояния от възел „1“, ще открием, че 5 е най-късото разстояние за ръб 1→2. Така че, възел „2“ ще бъде маркиран като посетен. По подобен начин, възел „3“ също ще бъде маркиран като посетен, тъй като най-късото разстояние е 3.

Ако обаче наблюдаваме, има път 1-3-2, който ще струва само 2. Но Дейкстра показва, че от възел „1“ до възел „2“ най-късото разстояние е 5. Така че, Дейкстра не е успял да изчисли правилно най-късото разстояние. Причината е, че Дейкстра е алгоритъм с алгоритъм „грейд“. Така че, след като даден възел бъде маркиран като посетен, той няма да бъде преразгледан, въпреки че може да има наличен по-кратък път. Този проблем възниква само когато ръбовете имат отрицателни разходи или ръбове с отрицателно тегло.

В този сценарий Дейкстра не успява да изчисли най-краткия път между два възела. В резултат на това този алгоритъм има някои недостатъци. За да се реши този проблем с отрицателните ръбове, се използва друг алгоритъм, наречен „алгоритъм на Белман-Форд“. Този алгоритъм може да работи с отрицателни ръбове.

Сложността на алгоритъма на Дейкстра

Реализацията по-горе използва два цикъла „за“. Тези цикли се изпълняват за броя на върховете. И така, времевата сложност е O(V²)Тук терминът „O“ е обозначение, което дава предположение за алгоритъма на Дейкстра.

Можем да съхраним графиката, използвайки „опашка с приоритет“. Опашката с приоритет е двоична структура от данни тип „heap“. Тя ще бъде по-ефективна от 2D матрица. Ребро с минимална цена ще има висок приоритет. Тогава времевата сложност ще бъде O(E log V). Тук E е броят на ръбовете, а V е броят на върховете.

Космическата сложност е O(V²), тъй като използваме матрица на съседство (2D масив). Сложността на пространството може да бъде оптимизирана с помощта на списък със съседство или структура от данни на опашка.

Въпроси и Отговори

Агентите за планиране на пътища с изкуствен интелект в роботиката, автономните превозни средства и NPC-тата в игрите използват алгоритъма на Дейкстра, за да намират маршрути с най-ниска цена върху претеглени графи. Средите за обучение с подсилване също разчитат на него, за да изчислят оптимални референтни пътища за схемата на награждаване.ping и оценка.

Да. Асистенти за кодиране с изкуствен интелект като GitHub Copilot и GPT могат да генерират алгоритъма на Дейкстра в Python, C++ или Java, включително варианти на опашки с приоритет, използващи купчини. Те могат също така да отпечатат действителния най-кратък път или да адаптират кода към графи, съхранявани като списъци със съседство.

Използвайки прост масив за намиране на минималния възел, алгоритъмът на Дейкстра се изпълнява за време O(V²). С двоична опашка с приоритет пада до O((V + E) log V), а с Фибоначи достига O(E + V log V), най-добро за разредени графи.

Дейкстра финализира връх веднага щом избере текущото минимално разстояние. По-късен отрицателен ръб може да направи по-дълъг път по-евтин, но финализираният връх никога не се посещава повторно, така че алгоритъмът отчита неправилно най-късо разстояние.

Изберете метода на Дейкстра, когато теглото на всяко ръбче е неотрицателно, защото е по-бърз при O((V+E) log V). Изберете метода на Белман-Форд, когато ръбовете могат да бъдат отрицателни или трябва да откриете цикли с отрицателно тегло; времето за изпълнение O(V·E) е компромисът.

Google Картите използват варианти и наследници на Дийкстра, включително A* и ContracЙерархии на управлението, настроени за пътни мрежи и трафик в реално време. Основната идея за алчно разширяване чрез минимални натрупани разходи все още е основният принос на Дейкстра.

A* разширява метода на Дейкстра, като добавя евристична оценка на разстоянието до целта, разширявайки по-малко възли, когато е налична добра евристика. Дейкстра изследва във всички посоки, докато A* насочва търсенето към целта, което го прави по-бързо на практика.

Освен картите, Dijkstra захранва OSPF и IS-IS протоколи за маршрутизиране в интернет, оптимизация на мрежовата топология, маршрутизиране на телефонни разговори, планиране на движението на роботиката, заявки за най-кратки връзки в социалните мрежи и минимизиране на разходите за полети на авиокомпании.

Обобщете тази публикация с: