Алгоритм найкоротшого шляху Дейкстри в Java

1. Огляд

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

Основною метою алгоритму є визначення найкоротшого шляху між початковим вузлом та рештою графіка.

2. Найкоротша проблема шляху з Дейкстрою

Враховуючи позитивно зважений графік та початковий вузол (A), Дейкстра визначає найкоротший шлях та відстань від джерела до всіх пунктів призначення на графіку:

Основна ідея алгоритму Дейкстри полягає в постійній елімінації довших шляхів між початковим вузлом та усіма можливими пунктами призначення.

Щоб відстежувати процес, нам потрібно мати два окремі набори вузлів, урегульованих і невлаштованих.

Встановлені вузли - це ті, що мають відому мінімальну відстань від джерела. Набір неурегульованих вузлів збирає вузли, до яких ми можемо дістатись із джерела, але ми не знаємо мінімальної відстані від початкового вузла.

Ось список кроків, яких потрібно виконати для вирішення СПП за допомогою Дейкстри:

  • Встановіть відстань startNode на нуль.
  • Встановіть для всіх інших відстаней нескінченне значення.
  • Ми додаємо початковий вузол до неврегульованого набору вузлів.
  • Хоча набір неурегульованих вузлів не є порожнім, ми:
    • Виберіть вузол оцінки з набору неурегульованих вузлів, вузол оцінки повинен бути тим, що має найменшу відстань від джерела.
    • Обчисліть нові відстані до прямих сусідів, дотримуючись найменшу відстань при кожному оцінюванні.
    • Додайте сусідів, які ще не врегульовані, до неврегульованих вузлів.

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

2.1. Ініціалізація

Перш ніж ми почнемо вивчати всі шляхи в графіку, нам спочатку потрібно ініціалізувати всі вузли з нескінченною відстанню та невідомим попередником, крім джерела.

В рамках процесу ініціалізації нам потрібно призначити значення 0 вузлу A (ми знаємо, що відстань від вузла A до вузла A дорівнює 0, очевидно)

Отже, кожен вузол в іншій частині графіка буде відрізнятись від попередника та відстані:

Щоб завершити процес ініціалізації, нам потрібно додати вузол A до неврегульованих вузлів, встановити його таким чином, щоб його вибирали першим на етапі оцінки. Майте на увазі, набір усталених вузлів все ще порожній.

2.2. Оцінка

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

Ідея полягає в тому, щоб додати вагу краю до відстані вузла оцінки, а потім порівняти його з відстанню призначення. наприклад, для вузла B 0 + 10 менше, ніж INFINITY, тому нова відстань для вузла B дорівнює 10, а новий попередник - A, те саме стосується вузла C.

Потім вузол A переміщується з неурегульованих вузлів, встановлених на узгоджені вузли.

Вузли B і C додаються до неврегульованих вузлів, оскільки їх можна досягти, але їх потрібно оцінити.

Тепер, коли у нас є два вузли в неврегульованому наборі, ми вибираємо той, що має найменшу відстань (вузол B), після чого повторюємо, поки не врегулюємо всі вузли на графіку:

Ось таблиця, яка узагальнює ітерації, виконані під час етапів оцінки:

Ітерація Нерешені Влаштувались EvaluationNode A B C. D Е F
1 A - A 0 А-10 А-15 X-∞ X-∞ X-∞
2 B, C A B 0 А-10 А-15 В-22 X-∞ В-25
3 C, F, D A, B C. 0 А-10 А-15 В-22 С-25 В-25
4 D, E, F A, B, C D 0 А-10 А-15 В-22 Д-24 D-23
5 Е, Ж А Б В Г F 0 А-10 А-15 В-22 Д-24 D-23
6 Е A, B, C, D, F Е 0 А-10 А-15 В-22 Д-24 D-23
Остаточний - ВСІ НІЯКОГО 0 А-10 А-15 В-22 Д-24 D-23

Наприклад, позначення B-22 означає, що вузол B є безпосереднім попередником із загальною відстанню 22 від вузла A.

Нарешті, ми можемо розрахувати найкоротші шляхи від вузла A наступним чином:

  • Вузол B: A -> B (загальна відстань = 10)
  • Вузол C: A -> C (загальна відстань = 15)
  • Вузол D: A -> B -> D (загальна відстань = 22)
  • Вузол E: A -> B -> D -> E (загальна відстань = 24)
  • Вузол F: A -> B -> D -> F (загальна відстань = 23)

3. Впровадження Java

У цій простій реалізації ми представимо графік як набір вузлів:

public class Graph { private Set nodes = new HashSet(); public void addNode(Node nodeA) { nodes.add(nodeA); } // getters and setters }

A node can be described with a name, a LinkedList in reference to the shortestPath, a distance from the source, and an adjacency list named adjacentNodes:

public class Node { private String name; private List shortestPath = new LinkedList(); private Integer distance = Integer.MAX_VALUE; Map adjacentNodes = new HashMap(); public void addDestination(Node destination, int distance) { adjacentNodes.put(destination, distance); } public Node(String name) { this.name = name; } // getters and setters }

The adjacentNodes attribute is used to associate immediate neighbors with edge length. This is a simplified implementation of an adjacency list, which is more suitable for the Dijkstra algorithm than the adjacency matrix.

As for the shortestPath attribute, it is a list of nodes that describes the shortest path calculated from the starting node.

By default, all node distances are initialized with Integer.MAX_VALUE to simulate an infinite distance as described in the initialization step.

Now, let's implement the Dijkstra algorithm:

public static Graph calculateShortestPathFromSource(Graph graph, Node source) { source.setDistance(0); Set settledNodes = new HashSet(); Set unsettledNodes = new HashSet(); unsettledNodes.add(source); while (unsettledNodes.size() != 0) { Node currentNode = getLowestDistanceNode(unsettledNodes); unsettledNodes.remove(currentNode); for (Entry  adjacencyPair: currentNode.getAdjacentNodes().entrySet()) { Node adjacentNode = adjacencyPair.getKey(); Integer edgeWeight = adjacencyPair.getValue(); if (!settledNodes.contains(adjacentNode)) { calculateMinimumDistance(adjacentNode, edgeWeight, currentNode); unsettledNodes.add(adjacentNode); } } settledNodes.add(currentNode); } return graph; }

The getLowestDistanceNode() method, returns the node with the lowest distance from the unsettled nodes set, while the calculateMinimumDistance() method compares the actual distance with the newly calculated one while following the newly explored path:

private static Node getLowestDistanceNode(Set  unsettledNodes) { Node lowestDistanceNode = null; int lowestDistance = Integer.MAX_VALUE; for (Node node: unsettledNodes) { int nodeDistance = node.getDistance(); if (nodeDistance < lowestDistance) { lowestDistance = nodeDistance; lowestDistanceNode = node; } } return lowestDistanceNode; }
private static void CalculateMinimumDistance(Node evaluationNode, Integer edgeWeigh, Node sourceNode) { Integer sourceDistance = sourceNode.getDistance(); if (sourceDistance + edgeWeigh < evaluationNode.getDistance()) { evaluationNode.setDistance(sourceDistance + edgeWeigh); LinkedList shortestPath = new LinkedList(sourceNode.getShortestPath()); shortestPath.add(sourceNode); evaluationNode.setShortestPath(shortestPath); } }

Now that all the necessary pieces are in place, let's apply the Dijkstra algorithm on the sample graph being the subject of the article:

Node nodeA = new Node("A"); Node nodeB = new Node("B"); Node nodeC = new Node("C"); Node nodeD = new Node("D"); Node nodeE = new Node("E"); Node nodeF = new Node("F"); nodeA.addDestination(nodeB, 10); nodeA.addDestination(nodeC, 15); nodeB.addDestination(nodeD, 12); nodeB.addDestination(nodeF, 15); nodeC.addDestination(nodeE, 10); nodeD.addDestination(nodeE, 2); nodeD.addDestination(nodeF, 1); nodeF.addDestination(nodeE, 5); Graph graph = new Graph(); graph.addNode(nodeA); graph.addNode(nodeB); graph.addNode(nodeC); graph.addNode(nodeD); graph.addNode(nodeE); graph.addNode(nodeF); graph = Dijkstra.calculateShortestPathFromSource(graph, nodeA); 

Після обчислення для кожного вузла на графіку встановлюються атрибути shortestPath та distance , ми можемо їх перебирати, щоб перевірити, чи результати точно відповідають тому, що було знайдено в попередньому розділі.

4. Висновок

У цій статті ми побачили, як алгоритм Дейкстри вирішує SPP та як його реалізувати на Java.

З реалізацією цього простого проекту можна ознайомитись у наступному посиланні на проект GitHub.