GraphLib
Bearbeitung der Aufgabe Mini Graph Library für OOP WiSe 2023/24
Loading...
Searching...
No Matches
dijkstra.h
1#ifndef GRAPHLIB_ALGORITHMS_DIJKSTRA_H
2#define GRAPHLIB_ALGORITHMS_DIJKSTRA_H
3
4#include <cassert>
5#include <list>
6#include <optional>
7#include <queue>
8#include <set>
9#include <unordered_map>
10#include <vector>
11
14
15namespace GraphLib {
16
17namespace Internal {
18
24template <valid_weighted_graph_type G>
26public:
27 /* Iterator concept requirements */
28
29 using difference_type = std::ptrdiff_t;
31 using value_type = G::id_type;
32
39 DijkstraIterator(const G& graph, const value_type start) : graph(graph), start(start)
40 {
41 assert(graph.node_count() > 0);
42 assert(graph.node_exists(start));
43
44 frontier.push(priority_pair_type(edge_weight_type {}, start));
45 node_lookup[start] = {start, edge_weight_type {}};
46 }
47
51 const DijkstraIterator begin() const
52 {
53 return DijkstraIterator(graph, start);
54 }
55
59 const DijkstraIterator end() const
60 {
62 end.done = true;
63 return end;
64 }
65
69 std::pair<value_type, value_type> operator*() const
70 {
71 const value_type best = frontier.top().value;
72 return {best, node_lookup.at(best).first};
73 }
74
79 {
80 if (!done)
82 return *this;
83 }
84
85 bool operator==(const DijkstraIterator& other) const
86 {
87 if (!(other.graph == graph && other.start == start && other.done == done))
88 return false;
89 else if (done)
90 return true;
91 else
92 return other.iterations == iterations;
93 }
94
95 bool operator!=(const DijkstraIterator& other) const
96 {
97 return !(*this == other);
98 }
99
100 /* State inspection methods */
101
106 {
107 return node_lookup.at(node).first;
108 }
109
114 {
115 return node_lookup.at(node).second;
116 }
117
121 bool has_seen(const value_type node) const
122 {
123 return node_lookup.contains(node);
124 }
125
126private:
127 /* Implementation details */
128
129 using edge_weight_type = typename G::edge_type::weight_type;
131 using priority_queue_type =
132 std::priority_queue<priority_pair_type, std::vector<priority_pair_type>, std::greater<priority_pair_type>>;
133
134 const G& graph;
135 const value_type start;
136 bool done {false};
137 unsigned long iterations {0};
138
139 // maps all encountered nodes to their predecessor and best known costs
140 std::unordered_map<value_type, std::pair<value_type, edge_weight_type>> node_lookup {};
141
142 // holds the nodes already part of the spanning tree we're constructing
143 std::set<value_type> closed_nodes {};
144
145 // holds nodes reachable from the edge of the explored ("closed") graph
146 // sorted by their cost. duplicates and elements whose nodes have since
147 // been added to the closed list are possible because updating the heap
148 // is expensive, and therefore need to be removed when encountered.
149 priority_queue_type frontier {};
150
155 {
156 assert(!done);
157
158 // pick the best node from the queue and remove it, then get its neighbors.
159 // using structured bindings here, they work in the order of the public fields
160 const auto [best_cost, best_node] = frontier.top();
161 frontier.pop();
162 const auto best_neighbors = graph.get_neighbors(best_node);
163
164 // scan the picked node's neighbors and update their costs if better
165 for (auto const node : best_neighbors) {
166 if (closed_nodes.contains(node))
167 continue;
168
169 if (graph.get_edge(best_node, node).weight < 0)
170 throw std::out_of_range("Graph with negative edge weight not permitted");
171
172 const auto new_cost = best_cost + graph.get_edge_weight(best_node, node);
173 if (!(node_lookup.contains(node) && new_cost >= node_lookup[node].second)) {
174 node_lookup[node] = {best_node, new_cost};
175 frontier.push(priority_pair_type(new_cost, node));
176 }
177 }
178
179 closed_nodes.insert(best_node);
180
181 // remove any revealed entries of closed nodes
182 while (!frontier.empty() && closed_nodes.contains(frontier.top().value)) {
183 frontier.pop();
184 }
185
186 if (frontier.empty())
187 done = true;
188
189 iterations++;
190 }
191};
192
193} // namespace Internal
194
199
208template <Internal::valid_weighted_graph_type G>
209G dijkstra(const G& graph, const typename G::id_type start)
210{
211 if (!graph.node_exists(start))
212 throw std::invalid_argument("Start node not in the passed graph");
213
214 auto spanning_tree = G {};
215 const auto explorer = Internal::DijkstraIterator<G>(graph, start);
216
217 for (const auto [best, best_pred] : explorer) {
218 spanning_tree.add_node_with_id(best, graph.get_node_data(best));
219 if (best != start) {
220 const auto edge = graph.get_edge(best_pred, best);
221 spanning_tree.add_edge(best_pred, best, edge.weight);
222 }
223 }
224
225 return spanning_tree;
226}
227
237template <Internal::valid_weighted_graph_type G>
238std::optional<std::list<typename G::id_type>> uniform_cost_search(const G& graph, const typename G::id_type start,
239 const typename G::id_type target)
240{
241 if (!(graph.node_exists(start) && graph.node_exists(start)))
242 throw std::invalid_argument("Start and/or target node not in the passed graph");
243
244 auto explorer = Internal::DijkstraIterator<G>(graph, start);
245
246 for (; explorer != explorer.end(); ++explorer)
247 if ((*explorer).first == target)
248 break;
249
250 if (!explorer.has_seen(target))
251 return std::nullopt;
252
253 auto path = std::list<typename G::id_type> {};
254 path.push_front(target);
255
256 typename G::id_type current = target;
257 while (current != start) {
258 current = explorer.get_current_pred(current);
259 path.push_front(current);
260 }
261
262 return path;
263}
264
265} // namespace GraphLib
266
267#endif
Iterator that incrementally explores the shortest paths from a start node using Dijkstra's algorithm.
Definition dijkstra.h:25
const DijkstraIterator end() const
Returns an iterator that marks the end of the traversal.
Definition dijkstra.h:59
const DijkstraIterator begin() const
Returns an iterator at the beginning of the Dijkstra traversal.
Definition dijkstra.h:51
std::pair< value_type, value_type > operator*() const
Returns the current node and its predecessor on the shortest path.
Definition dijkstra.h:69
value_type get_current_cost(const value_type node) const
Returns the current cost to reach a node from the start.
Definition dijkstra.h:113
bool has_seen(const value_type node) const
Checks whether a node has been encountered during traversal.
Definition dijkstra.h:121
value_type get_current_pred(const value_type node) const
Returns the current predecessor of a node in the shortest path tree.
Definition dijkstra.h:105
void perform_update()
Performs one iteration of the Dijkstra algorithm, expanding the best node.
Definition dijkstra.h:154
G::id_type value_type
Type used to represent node identifiers, as defined by the graph type.
Definition dijkstra.h:31
DijkstraIterator & operator++()
Advances the iterator to the next node in the shortest path tree.
Definition dijkstra.h:78
DijkstraIterator(const G &graph, const value_type start)
Constructs a Dijkstra iterator starting at a given node.
Definition dijkstra.h:39
Defines C++20 concepts used for type constraints in the GraphLib library.
std::optional< std::list< typename G::id_type > > uniform_cost_search(const G &graph, const typename G::id_type start, const typename G::id_type target)
Finds the lowest-cost path from start to target using Dijkstra's algorithm.
Definition dijkstra.h:238
G dijkstra(const G &graph, const typename G::id_type start)
Computes the shortest-path spanning tree from a start node using Dijkstra's algorithm.
Definition dijkstra.h:209
A pair combining a priority and a value, supporting comparison by priority.
Definition util.h:23
Utility structures used internally by GraphLib.