Skip to content

Commit a4a89fc

Browse files
committed
fix: dijkstra
1 parent 121a259 commit a4a89fc

File tree

4 files changed

+161
-0
lines changed

4 files changed

+161
-0
lines changed

docs/algorithms/11-dijkstra/README.md

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Djikstra's algorithm
2+
3+
![img.png](img.png) [source](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra)
4+
5+
Dijkstra's algorithm is a graph traversal algorithm similar to BFS, but it takes into account the weight of the edges. It uses a priority list to visit the nodes with the smallest cost first and a set to keep track of the visited nodes. A came_from map can be used to store the parent node of each node to create a pathfinding algorithm.
6+
7+
It uses the producer-consumer pattern, where the producer is the algorithm that adds the nodes to the priority queue and the consumer is the algorithm that removes the nodes from the priority queue and do the work.
8+
9+
The algorithm is greedy and works well with positive weights. It is not optimal for negative weights, for that you should use the Bellman-Ford algorithm.
10+
11+
![img_2.png](img_2.png)
12+
13+
## Data Structure
14+
15+
For the graph, we will use an adjacency list:
16+
17+
```c++
18+
// node registry
19+
// K is the key type for indexing the nodes, usually it can be a string or an integer
20+
// Node is the Node type to store node related data
21+
unordered_map<K, N> nodes;
22+
23+
// K is the key type of the index
24+
// W is the weight type of the edge, usually it can be an integer or a float
25+
// W can be more robust and become a struct, for example, to store the weight and the edge name
26+
// if W is a struct, remember no implement the < operator for the priority queue work
27+
// unordered_map is used to exploit the O(1) access time and be tolerant to sparse keys
28+
unordered_map<K, unordered_map<K, W>> edges;
29+
30+
// usage
31+
// the cost from node A to node B is 5
32+
edges["A"]["B"] = 5;
33+
// if you want to make it bidirectional set the opposite edge too
34+
// edges["B"]["A"] = 5;
35+
```
36+
37+
For the algoritm to work we will need a priority queue to store the nodes to be visited:
38+
39+
```c++
40+
// priority queue to store the nodes to be visited
41+
// C is the W type and stores the accumulated cost to reach the node
42+
// K is the key type of the index of the node
43+
priority_queue<pair<C, K>> frontier;
44+
```
45+
46+
For the visited nodes we will use a set:
47+
48+
```c++
49+
// set to store the visited nodes
50+
// K is the key type of the index of the node
51+
unordered_set<K> visited;
52+
```
53+
54+
## Algorithm
55+
56+
List of visualizations:
57+
58+
- https://www.cs.usfca.edu/~galles/visualization/Dijkstra.html
59+
- https://visualgo.net/en/sssp
60+
- https://qiao.github.io/PathFinding.js/visual/
61+
62+
![img_1.png](img_1.png)
63+
64+
Example of Dijkstra's algorithm in C++ to build a path from the start node to the end node:
65+
66+
```c++
67+
#include <iostream>
68+
#include <unordered_map>
69+
#include <unordered_set>
70+
#include <string>
71+
#include <queue>
72+
using namespace std;
73+
74+
// Dijikstra
75+
struct Node {
76+
// add your custom data here
77+
string name;
78+
};
79+
80+
// nodes indexed by id
81+
unordered_map<uint64_t, Node> nodes;
82+
// edges indexed by source id and destination id, the value is the
83+
unordered_map<uint64_t, unordered_map<uint64_t, double>> edges;
84+
// priority queue for the frontier
85+
// this could be declared inside the Dijkstra function
86+
priority_queue<pair<double, uint64_t>> frontier;
87+
88+
// optionally, in order to create a pathfinding, use came_from map to store the parent node
89+
unordered_map<uint64_t, uint64_t> came_from;
90+
// cost to reach the node so far
91+
unordered_map<uint64_t, double> cost_so_far;
92+
93+
void Visit(Node* node){
94+
// add your custom code here
95+
cout << node->name << endl;
96+
}
97+
98+
void Dijkstra(uint64_t start_id) {
99+
cout << "Visiting nodes:" << endl;
100+
// clear the costs so far
101+
cost_so_far.clear();
102+
// boostrap the frontier
103+
// 0 means the cost to reach the start node is 0
104+
frontier.emplace(0, start_id);
105+
cost_so_far[start_id] = 0;
106+
// while there are nodes to visit
107+
while (!frontier.empty()) {
108+
// get the node with the lowest cost
109+
auto [cost, current_id] = frontier.top();
110+
frontier.pop();
111+
// get the node
112+
Node* current = &nodes[current_id];
113+
// visit the node
114+
Visit(current);
115+
// for each neighbor
116+
for (const auto& [neighbor_id, edge_cost] : edges[current_id]) {
117+
// calculate the new cost to reach the neighbor
118+
double new_cost = cost_so_far[current_id] + edge_cost;
119+
// if the neighbor is not visited yet or the new cost is less than the previous cost
120+
if (!cost_so_far.contains(neighbor_id) || new_cost < cost_so_far[neighbor_id]) {
121+
// update the cost
122+
cost_so_far[neighbor_id] = new_cost;
123+
// add the neighbor to the frontier
124+
frontier.emplace(new_cost, neighbor_id);
125+
// update the parent node
126+
came_from[neighbor_id] = current_id;
127+
}
128+
}
129+
}
130+
}
131+
132+
int main() {
133+
// build the graph
134+
nodes[0] = {"A"}; // this will be our start
135+
nodes[1] = {"B"};
136+
nodes[2] = {"C"};
137+
nodes[3] = {"D"}; // this will be our end
138+
// store the edges costs
139+
edges[0][1] = 1;
140+
edges[0][2] = 2;
141+
edges[0][3] = 100; // this is a very expensive edge
142+
edges[1][3] = 3;
143+
edges[2][3] = 1;
144+
// the path from 0 to 3 is A -> C -> D even though the edge A -> D have less steps
145+
Dijkstra(0);
146+
// print the path from the end to the start
147+
cout << "Path:" << endl;
148+
uint64_t index = 3;
149+
// prevents infinite loop if the end is unreachable
150+
if(!came_from.contains(index)) {
151+
cout << "No path found" << endl;
152+
return 0;
153+
}
154+
while (index != 0) {
155+
cout << nodes[index].name << endl;
156+
index = came_from[index];
157+
}
158+
cout << nodes[0].name << endl;
159+
return 0;
160+
}
161+
```

docs/algorithms/11-dijkstra/img.png

2.23 MB
Loading

docs/algorithms/11-dijkstra/img_1.png

678 KB
Loading

docs/algorithms/11-dijkstra/img_2.png

182 KB
Loading

0 commit comments

Comments
 (0)