forked from google/or-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
multi_dijkstra.h
202 lines (185 loc) · 8.35 KB
/
multi_dijkstra.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Runs multiple Dijkstra searches simultaneously (single-threaded, but growing
// their search radii at the same time) on the same graph.
// Supports custom arc length functors, and custom stopping criterion and
// tracking.
//
// EXAMPLE:
// With two sources and a custom stopping criterion that stops the first
// Dijkstra when it has settled 1000 nodes and the second when it has reached
// the search radius 123.45:
//
// ListGraph<> graph; // From ortools/graph/graph.h
// ... build the graph ...
// int source1 = ... , source2 = ...;
// vector<double> arc_lengths(graph.num_arcs(), 0);
// ... set the arc lengths ...
// int num_nodes_to_settle_in_first_search = 1000;
// std::vector<absl::flat_hash_map<int, DistanceAndParentArc<double>>>
// reached_nodes; // One map per source: node -> DistanceAndParentArc.
// reached_nodes = MultiDijkstra<double>(
// graph,
// [&arc_lengths](int a) { return arc_lengths[a]; },
// {{source1}, {source2}},
// [&num_nodes_to_settle_in_first_search](
// int settled_node, int source_index, double settled_distance) {
// if (source_index == 0) {
// return --num_nodes_to_settle_in_first_search == 0;
// } else {
// return settled_distance >= 123.45;
// }
// });
#ifndef OR_TOOLS_GRAPH_MULTI_DIJKSTRA_H_
#define OR_TOOLS_GRAPH_MULTI_DIJKSTRA_H_
#include <iostream>
#include <ostream>
#include <queue>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "ortools/base/map_util.h"
namespace operations_research {
template <class DistanceType>
struct DistanceAndParentArc {
DistanceType distance;
int parent_arc; // -1 means "no parent" (i.e. we're at the root).
bool operator==(const DistanceAndParentArc& other) const {
return distance == other.distance && parent_arc == other.parent_arc;
}
};
template <class DistanceType>
std::ostream& operator<<(
std::ostream& out,
DistanceAndParentArc<DistanceType> distance_and_parent_arc);
// Runs multiple Dijkstra searches simultaneously on the same graph, in a single
// thread. All the Dijkstras share the same priority queue: their search radius
// will grow "simulatenously".
//
// Moreover, each individual Dijkstra search can have several nodes as its
// "source", and the stopping criterion for each Dijkstra search is highly
// customizable: the user controls it via a "settled node callback",
// called every time a node is settled. See the API below.
//
// The Dijkstra are sparse: the global space complexity will be linear in the
// number of search states explored. Ditto for the time complexity, with an
// additional logarithmic factor caused by the priority queue.
//
// This class has many similarities with BoundedDijkstraWrapper from
// ./bounded_dijkstra.h but adds the overhead of tracking the source index for
// every search state, and of the sparse (but slower) node hash maps.
//
// Arguments:
// - "graph": the graph. The Graph class must support the interface of
// ortools/graph/graph.h: OutgoingArcs(), Head(), Tail().
// - "arc_length_functor": we will call arc_length_functor(a) on every
// arc "a" explored, where "a" is cast to an int. It should return the arc's
// length, which is statically cast to DistanceType.
// - "source_sets" contains the sources. Each source is itself a set of nodes.
// - "settled_node_callback" will be called every time we settled a node, with 3
// arguments (node, source index, distance of node from that source).
// If it returns true, the Dijkstra search from that source will stop.
//
// Returns the list of Dijkstra search: for each source #s, the element #s of
// the returned vector will map every node explored in the Dijkstra from source
// #s to its distance and parent arc.
template <class DistanceType, class Graph, class ArcLengthFunctor,
class SettledNodeCallbackType>
std::vector<absl::flat_hash_map<int, DistanceAndParentArc<DistanceType>>>
MultiDijkstra(const Graph& graph, ArcLengthFunctor arc_length_functor,
const std::vector<std::vector<int>>& source_sets,
SettledNodeCallbackType settled_node_callback);
// =============================================================================
// Implementation of the function templates
// =============================================================================
template <class DistanceType>
std::ostream& operator<<(
std::ostream& out,
DistanceAndParentArc<DistanceType> distance_and_parent_arc) {
return out << "{distance=" << distance_and_parent_arc.distance
<< ", parent_arc=" << distance_and_parent_arc.parent_arc << "}";
}
template <class DistanceType, class Graph, class ArcLengthFunctor,
class SettledNodeCallbackType>
std::vector<absl::flat_hash_map<int, DistanceAndParentArc<DistanceType>>>
MultiDijkstra(const Graph& graph, ArcLengthFunctor arc_length_functor,
const std::vector<std::vector<int>>& source_sets,
SettledNodeCallbackType settled_node_callback) {
// Initialize the return data structure and the priority queue.
const int num_sources = source_sets.size();
std::vector<absl::flat_hash_map<int, DistanceAndParentArc<DistanceType>>>
reached_from(num_sources); // Also the returned output!
struct State {
DistanceType distance;
int node;
int source_index;
};
struct StateComparator {
bool operator()(const State& s0, const State& s1) const {
if (s0.distance != s1.distance) return s0.distance > s1.distance;
if (s0.node != s1.node) return s0.node > s1.node;
return s0.source_index > s1.source_index;
}
};
StateComparator pq_state_comparator;
std::priority_queue<State, std::vector<State>, StateComparator> pq(
pq_state_comparator);
std::vector<bool> dijkstra_is_active(num_sources, false);
int num_active_dijkstras = 0;
for (int source_index = 0; source_index < num_sources; ++source_index) {
if (!source_sets[source_index].empty()) {
dijkstra_is_active[source_index] = true;
++num_active_dijkstras;
}
for (const int node : source_sets[source_index]) {
if (gtl::InsertIfNotPresent(
&reached_from[source_index],
{node, {/*distance=*/0, /*parent_arc=*/-1}})) {
pq.push({/*distance=*/0, /*node=*/node, /*source_index=*/source_index});
}
}
}
// Main Dijkstra loop.
while (!pq.empty() && num_active_dijkstras > 0) {
const State state = pq.top();
pq.pop();
if (!dijkstra_is_active[state.source_index]) continue;
// Dijkstra optimization: ignore states that don't correspond to the optimal
// (such states have been preceded by better states in the queue order,
// without being deleted since priority_queue doesn't have erase()).
if (gtl::FindOrDie(reached_from[state.source_index], state.node).distance <
state.distance) {
continue;
}
if (settled_node_callback(state.node, state.source_index, state.distance)) {
dijkstra_is_active[state.source_index] = false;
--num_active_dijkstras;
continue;
}
for (const int arc : graph.OutgoingArcs(state.node)) {
const DistanceType distance = arc_length_functor(arc) + state.distance;
const int head_node = graph.Head(arc);
auto insertion_result = reached_from[state.source_index].insert(
{head_node, {/*distance=*/distance, /*parent_arc=*/arc}});
if (!insertion_result.second) { // Already reached...
if (insertion_result.first->second.distance <= distance) continue;
insertion_result.first->second = {/*distance=*/distance,
/*parent_arc=*/arc};
}
pq.push(State{/*distance=*/distance, /*node=*/head_node,
/*source_index=*/state.source_index});
}
}
return reached_from;
}
} // namespace operations_research
#endif // OR_TOOLS_GRAPH_MULTI_DIJKSTRA_H_