Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
shortest_paths.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <memory>
18#include <utility>
19#include <vector>
20
21#include "absl/container/flat_hash_map.h"
22#include "absl/functional/bind_front.h"
23#include "absl/log/check.h"
24#include "absl/types/span.h"
31#include "ortools/base/timer.h"
33
34namespace operations_research {
35
36// TODO(user): Currently using StarGraph::kNilNode until the new Ebert
37// graphs appear; switch to kNilNode on the base Ebert graph class when
38// available.
39
40// Base path container implementation class. Defines virtual functions used to
41// fill the container (in particular from the shortest path computation
42// function).
44 public:
46 virtual ~PathContainerImpl() {}
47
48 // Initializes the container on source and destination node vectors (num_nodes
49 // is the total number of nodes in the graph containing sources and nodes).
50 // Called before adding any paths to the container.
51 virtual void Initialize(const std::vector<NodeIndex>& sources,
52 const std::vector<NodeIndex>& destinations,
53 NodeIndex num_nodes) = 0;
54
55 // Called when no more path will be added to the container.
56 virtual void Finalize() {}
57
58 // Returns the distance between node 'from' and node 'to' following the path
59 // out of 'from' and into 'to'. Note that if 'from' == 'to', the distance is
60 // not necessarily 0 if the path out of 'to' and back into 'to' has a distance
61 // greater than 0. If you do require the distance to be 0 in this case, add to
62 // the graph an arc from 'to' to itself with a length of 0.
63 // If nodes are not connected, returns kDisconnectedPathDistance.
64 virtual PathDistance GetDistance(NodeIndex from, NodeIndex to) const = 0;
65
66 // Returns the penultimate node on the path out of node 'from' into node 'to'
67 // (the direct predecessor of node 'to' on the path).
68 // If 'from' == 'to', the penultimate node is 'to' only if the shortest path
69 // from 'to' to itself is composed of the arc ('to, 'to'), which might not be
70 // the case if either this arc doesn't exist or if the length of this arc is
71 // greater than the distance of an alternate path.
72 // If nodes are not connected, returns kNilNode.
74 NodeIndex to) const = 0;
75
76 // Returns path nodes from node "from" to node "to" in a ordered vector.
77 virtual void GetPath(NodeIndex from, NodeIndex to,
78 std::vector<NodeIndex>* path) const = 0;
79
80 // Adds a path tree rooted at node 'from', and to a set of implicit
81 // destinations:
82 // - predecessor_in_path_tree[node] is the predecessor of node 'node' in the
83 // path from 'from' to 'node', or kNilNode if there is no
84 // predecessor (i.e. if 'node' is not in the path tree);
85 // - distance_to_destination[i] is the distance from 'from' to the i-th
86 // destination (see Initialize()).
88 NodeIndex from, const std::vector<NodeIndex>& predecessor_in_path_tree,
89 const std::vector<PathDistance>& distance_to_destination) = 0;
90};
91
92namespace {
93
94// Class designed to store the tree of paths from a root node to a set of nodes
95// in a very compact way (over performance).
96// Memory consumption is in O(n) (n being the size of the tree) where node
97// indices are "very" non-contiguous (extremely sparse node indices). It keeps
98// node-sorted arrays of node and parent pairs, which can be accessed in
99// O(log(n)) with a binary search.
100// The creation of the tree is done in O(n*log(n)) time.
101// Note that this class uses temporary memory for each call to Initialize which
102// is only an issue for massive parallel calls; in practice for shortest
103// paths computation, the number of threads calling Initialize is very small
104// compared to the total number of trees created.
105class PathTree {
106 public:
107 PathTree() : nodes_(), parents_() {}
108
109 void Initialize(absl::Span<const NodeIndex> paths,
110 absl::Span<const NodeIndex> destinations);
111
112 // Returns the parent (predecessor) of 'node' in the tree in
113 // O(log(path_tree_size)), where path_tree_size is the size of nodes_.
114 NodeIndex GetParent(NodeIndex node) const;
115
116 // Returns the path from node 'from' to node 'to' in the tree in
117 // O(log(path_tree_size) + path_size), where path_tree_size is the size of
118 // nodes_ and path_size is the size of the resulting path.
119 void GetPath(NodeIndex from, NodeIndex to,
120 std::vector<NodeIndex>* path) const;
121
122 private:
123 std::vector<NodeIndex> nodes_;
124 std::vector<int> parents_;
125};
126
127// Initializes the tree from a non-sparse representation of the path tree
128// represented by 'paths'. The tree is reduced to the subtree in which nodes in
129// 'destinations' are the leafs.
130void PathTree::Initialize(absl::Span<const NodeIndex> paths,
131 absl::Span<const NodeIndex> destinations) {
132 const NodeIndex kNilNode = StarGraph::kNilNode;
133 std::vector<bool> node_explored(paths.size(), false);
134 const int destination_size = destinations.size();
135 typedef std::pair<NodeIndex, NodeIndex> NodeParent;
136 std::vector<NodeParent> tree;
137 for (int i = 0; i < destination_size; ++i) {
138 NodeIndex destination = destinations[i];
139 while (!node_explored[destination]) {
140 node_explored[destination] = true;
141 tree.push_back(std::make_pair(destination, paths[destination]));
142 if (paths[destination] != kNilNode) {
143 destination = paths[destination];
144 }
145 }
146 }
147 std::sort(tree.begin(), tree.end());
148 const int num_nodes = tree.size();
149 {
150 // Using a dense_hash_map gives a huge speedup over hash_map here, mainly
151 // due to better memory management.
152 absl::flat_hash_map<NodeIndex, int> node_indices;
153
154 for (int i = 0; i < num_nodes; ++i) {
155 node_indices[tree[i].first] = i;
156 }
157 parents_.resize(num_nodes, -1);
158 for (int i = 0; i < num_nodes; ++i) {
159 if (tree[i].second == kNilNode) {
160 // dense_hash_map does not allow empty key to be used as a key.
161 parents_[i] = kNilNode;
162 } else {
163 parents_[i] =
164 gtl::FindWithDefault(node_indices, tree[i].second, kNilNode);
165 }
166 }
167 }
168 nodes_.resize(num_nodes, kNilNode);
169 for (int i = 0; i < num_nodes; ++i) {
170 nodes_[i] = tree[i].first;
171 }
172}
173
174NodeIndex PathTree::GetParent(NodeIndex node) const {
175 std::vector<NodeIndex>::const_iterator node_position =
176 std::lower_bound(nodes_.begin(), nodes_.end(), node);
177 if (node_position != nodes_.end() && *node_position == node) {
178 const int parent = parents_[node_position - nodes_.begin()];
179 if (parent != StarGraph::kNilNode) {
180 return nodes_[parent];
181 }
182 }
183 return StarGraph::kNilNode;
184}
185
186void PathTree::GetPath(NodeIndex from, NodeIndex to,
187 std::vector<NodeIndex>* path) const {
188 DCHECK(path != nullptr);
189 path->clear();
190 std::vector<NodeIndex>::const_iterator to_position =
191 std::lower_bound(nodes_.begin(), nodes_.end(), to);
192 if (to_position != nodes_.end() && *to_position == to) {
193 int current_index = to_position - nodes_.begin();
194 NodeIndex current_node = to;
195 while (current_node != from) {
196 path->push_back(current_node);
197 current_index = parents_[current_index];
198 // from and to are not connected
199 if (current_index == StarGraph::kNilNode) {
200 path->clear();
201 return;
202 }
203 current_node = nodes_[current_index];
204 }
205 path->push_back(current_node);
206 std::reverse(path->begin(), path->end());
207 }
208}
209
210// Path container which only stores distances between path nodes.
211class DistanceContainer : public PathContainerImpl {
212 public:
213 DistanceContainer() : reverse_sources_(), distances_() {}
214
215 // This type is neither copyable nor movable.
216 DistanceContainer(const DistanceContainer&) = delete;
217 DistanceContainer& operator=(const DistanceContainer&) = delete;
218 ~DistanceContainer() override {}
219 void Initialize(const std::vector<NodeIndex>& sources,
220 const std::vector<NodeIndex>& destinations,
221 NodeIndex num_nodes) override {
222 ComputeReverse(sources, num_nodes, &reverse_sources_);
223 ComputeReverse(destinations, num_nodes, &reverse_destinations_);
224 distances_.clear();
225 distances_.resize(sources.size());
226 }
227 PathDistance GetDistance(NodeIndex from, NodeIndex to) const override {
228 return distances_[reverse_sources_[from]][reverse_destinations_[to]];
229 }
230 NodeIndex GetPenultimateNodeInPath(NodeIndex from,
231 NodeIndex to) const override {
232 (void)from;
233 (void)to;
234
235 LOG(FATAL) << "Path not stored.";
236 return StarGraph::kNilNode;
237 }
238 void GetPath(NodeIndex from, NodeIndex to,
239 std::vector<NodeIndex>* path) const override {
240 (void)from;
241 (void)to;
242 (void)path;
243
244 LOG(FATAL) << "Path not stored.";
245 }
246 void StoreSingleSourcePaths(
247 NodeIndex from, const std::vector<NodeIndex>& predecessor_in_path_tree,
248 const std::vector<PathDistance>& distance_to_destination) override {
249 // DistanceContainer only stores distances and not predecessors.
250 (void)predecessor_in_path_tree;
251
252 distances_[reverse_sources_[from]] = distance_to_destination;
253 }
254
255 protected:
256 std::vector<int> reverse_sources_;
257 std::vector<int> reverse_destinations_;
258
259 private:
260 static void ComputeReverse(absl::Span<const NodeIndex> nodes,
261 NodeIndex num_nodes,
262 std::vector<int>* reverse_nodes) {
263 CHECK(reverse_nodes != nullptr);
264 const int kUnassignedIndex = -1;
265 reverse_nodes->clear();
266 reverse_nodes->resize(num_nodes, kUnassignedIndex);
267 for (int i = 0; i < nodes.size(); ++i) {
268 reverse_nodes->at(nodes[i]) = i;
269 }
270 }
271
272 std::vector<std::vector<PathDistance> > distances_;
273};
274
275// Path container which stores explicit paths and distances between path nodes.
276class InMemoryCompactPathContainer : public DistanceContainer {
277 public:
278 InMemoryCompactPathContainer() : trees_(), destinations_() {}
279
280 // This type is neither copyable nor movable.
281 InMemoryCompactPathContainer(const InMemoryCompactPathContainer&) = delete;
282 InMemoryCompactPathContainer& operator=(const InMemoryCompactPathContainer&) =
283 delete;
284 ~InMemoryCompactPathContainer() override {}
285 void Initialize(const std::vector<NodeIndex>& sources,
286 const std::vector<NodeIndex>& destinations,
287 NodeIndex num_nodes) override {
288 DistanceContainer::Initialize(sources, destinations, num_nodes);
289 destinations_ = destinations;
290 trees_.clear();
291 trees_.resize(sources.size());
292 }
293 NodeIndex GetPenultimateNodeInPath(NodeIndex from,
294 NodeIndex to) const override {
295 return trees_[reverse_sources_[from]].GetParent(to);
296 }
297 void GetPath(NodeIndex from, NodeIndex to,
298 std::vector<NodeIndex>* path) const override {
299 DCHECK(path != nullptr);
300 trees_[reverse_sources_[from]].GetPath(from, to, path);
301 }
302 void StoreSingleSourcePaths(
303 NodeIndex from, const std::vector<NodeIndex>& predecessor_in_path_tree,
304 const std::vector<PathDistance>& distance_to_destination) override {
305 DistanceContainer::StoreSingleSourcePaths(from, predecessor_in_path_tree,
306 distance_to_destination);
307 trees_[reverse_sources_[from]].Initialize(predecessor_in_path_tree,
308 destinations_);
309 }
310
311 private:
312 std::vector<PathTree> trees_;
313 std::vector<NodeIndex> destinations_;
314};
315
316// Priority queue node entry in the boundary of the Dijkstra algorithm.
317class NodeEntry {
318 public:
319 NodeEntry()
320 : heap_index_(-1),
321 distance_(0),
322 node_(StarGraph::kNilNode),
323 settled_(false),
324 is_destination_(false) {}
325 bool operator<(const NodeEntry& other) const {
326 return distance_ > other.distance_;
327 }
328 void SetHeapIndex(int h) { heap_index_ = h; }
329 int GetHeapIndex() const { return heap_index_; }
330 void set_distance(PathDistance distance) { distance_ = distance; }
331 PathDistance distance() const { return distance_; }
332 void set_node(NodeIndex node) { node_ = node; }
333 NodeIndex node() const { return node_; }
334 void set_settled(bool settled) { settled_ = settled; }
335 bool settled() const { return settled_; }
336 void set_is_destination(bool is_destination) {
337 is_destination_ = is_destination;
338 }
339 bool is_destination() const { return is_destination_; }
340
341 private:
342 int heap_index_;
343 PathDistance distance_;
344 NodeIndex node_;
345 bool settled_;
346 bool is_destination_;
347};
348
349// Updates an entry with the given distance if it's shorter, and then inserts it
350// in the priority queue (or updates it if it's there already), if needed.
351// Returns true if the entry was modified, false otherwise.
352bool InsertOrUpdateEntry(PathDistance distance, NodeEntry* entry,
353 AdjustablePriorityQueue<NodeEntry>* priority_queue) {
354 DCHECK(priority_queue != nullptr);
355 DCHECK(entry != nullptr);
356 if (!priority_queue->Contains(entry)) {
357 entry->set_distance(distance);
358 priority_queue->Add(entry);
359 return true;
360 } else if (distance < entry->distance()) {
361 entry->set_distance(distance);
362 priority_queue->NoteChangedPriority(entry);
363 return true;
364 }
365 return false;
366}
367
368// If one wants to use int64_t for either priority or NodeIndex, one should
369// consider using packed ints (putting the two bools with heap_index, for
370// example) in order to stay at 16 bytes instead of 24.
371static_assert(sizeof(NodeEntry) == 16, "node_entry_class_is_not_well_packed");
372
373// Computes shortest paths from node 'source' to nodes in 'destinations'
374// using a binary heap-based Dijkstra algorithm.
375// TODO(user): Investigate alternate implementation which wouldn't use
376// AdjustablePriorityQueue.
377template <class GraphType>
378void ComputeOneToManyInternalOnGraph(
379 const GraphType* const graph,
380 const std::vector<PathDistance>* const arc_lengths,
381 typename GraphType::NodeIndex source,
382 const std::vector<typename GraphType::NodeIndex>* const destinations,
383 PathContainerImpl* const paths) {
384 CHECK(graph != nullptr);
385 CHECK(arc_lengths != nullptr);
386 CHECK(destinations != nullptr);
387 CHECK(paths != nullptr);
388 const int num_nodes = graph->num_nodes();
389 std::vector<typename GraphType::NodeIndex> predecessor(num_nodes, -1);
391 std::vector<NodeEntry> entries(num_nodes);
392 for (const typename GraphType::NodeIndex node : graph->AllNodes()) {
393 entries[node].set_node(node);
394 }
395 // Marking destination node. This is an optimization stopping the search
396 // when all destinations have been reached.
397 for (int i = 0; i < destinations->size(); ++i) {
398 entries[(*destinations)[i]].set_is_destination(true);
399 }
400 // In this implementation the distance of a node to itself isn't necessarily
401 // 0.
402 // So we push successors of source in the queue instead of the source
403 // directly which will avoid marking the source.
404 for (const typename GraphType::ArcIndex arc : graph->OutgoingArcs(source)) {
405 const typename GraphType::NodeIndex next = graph->Head(arc);
406 if (InsertOrUpdateEntry(arc_lengths->at(arc), &entries[next],
407 &priority_queue)) {
408 predecessor[next] = source;
409 }
410 }
411 int destinations_remaining = destinations->size();
412 while (!priority_queue.IsEmpty()) {
413 NodeEntry* current = priority_queue.Top();
414 const typename GraphType::NodeIndex current_node = current->node();
415 priority_queue.Pop();
416 current->set_settled(true);
417 if (current->is_destination()) {
418 destinations_remaining--;
419 if (destinations_remaining == 0) {
420 break;
421 }
422 }
423 const PathDistance current_distance = current->distance();
424 for (const typename GraphType::ArcIndex arc :
425 graph->OutgoingArcs(current_node)) {
426 const typename GraphType::NodeIndex next = graph->Head(arc);
427 NodeEntry* const entry = &entries[next];
428 if (!entry->settled()) {
429 DCHECK_GE(current_distance, 0);
430 const PathDistance arc_length = arc_lengths->at(arc);
431 DCHECK_LE(current_distance, kDisconnectedPathDistance - arc_length);
432 if (InsertOrUpdateEntry(current_distance + arc_length, entry,
433 &priority_queue)) {
434 predecessor[next] = current_node;
435 }
436 }
437 }
438 }
439 const int destinations_size = destinations->size();
440 std::vector<PathDistance> distances(destinations_size,
442 for (int i = 0; i < destinations_size; ++i) {
443 NodeIndex node = destinations->at(i);
444 if (entries[node].settled()) {
445 distances[i] = entries[node].distance();
446 }
447 }
448 paths->StoreSingleSourcePaths(source, predecessor, distances);
449}
450} // namespace
451
453
455
457 DCHECK(container_ != nullptr);
458 return container_->GetDistance(from, to);
459}
460
462 NodeIndex to) const {
463 DCHECK(container_ != nullptr);
464 return container_->GetPenultimateNodeInPath(from, to);
465}
466
468 std::vector<NodeIndex>* path) const {
469 DCHECK(container_ != nullptr);
470 DCHECK(path != nullptr);
471 container_->GetPath(from, to, path);
472}
473
475 return container_.get();
476}
477
479 PathContainer* const path_container) {
480 CHECK(path_container != nullptr);
481 path_container->container_ = std::make_unique<DistanceContainer>();
482}
483
485 PathContainer* const path_container) {
486 CHECK(path_container != nullptr);
487 path_container->container_ = std::make_unique<InMemoryCompactPathContainer>();
488}
489
490template <class GraphType>
492 const GraphType& graph, const std::vector<PathDistance>& arc_lengths,
493 const std::vector<typename GraphType::NodeIndex>& sources,
494 const std::vector<typename GraphType::NodeIndex>& destinations,
495 int num_threads, PathContainer* const paths) {
496 if (graph.num_nodes() > 0) {
497 CHECK_EQ(graph.num_arcs(), arc_lengths.size())
498 << "Number of arcs in graph must match arc length vector size";
499 // Removing duplicate sources to allow mutex-free implementation (and it's
500 // more efficient); same with destinations for efficiency reasons.
501 std::vector<typename GraphType::NodeIndex> unique_sources = sources;
502 gtl::STLSortAndRemoveDuplicates(&unique_sources);
503 std::vector<typename GraphType::NodeIndex> unique_destinations =
504 destinations;
505 gtl::STLSortAndRemoveDuplicates(&unique_destinations);
506 WallTimer timer;
507 timer.Start();
508 PathContainerImpl* container = paths->GetImplementation();
509 container->Initialize(unique_sources, unique_destinations,
510 graph.num_nodes());
511 {
512 std::unique_ptr<ThreadPool> pool(
513 new ThreadPool("OR_Dijkstra", num_threads));
514 pool->StartWorkers();
515 for (int i = 0; i < unique_sources.size(); ++i) {
516 pool->Schedule(absl::bind_front(
517 &ComputeOneToManyInternalOnGraph<GraphType>, &graph, &arc_lengths,
518 unique_sources[i], &unique_destinations, container));
519 }
520 }
521 container->Finalize();
522 VLOG(2) << "Elapsed time to compute shortest paths: " << timer.Get() << "s";
523 }
524}
525
526template <>
528 const ListGraph<>& graph, const std::vector<PathDistance>& arc_lengths,
529 const std::vector<ListGraph<>::NodeIndex>& sources,
530 const std::vector<ListGraph<>::NodeIndex>& destinations, int num_threads,
531 PathContainer* const path_container) {
533 graph, arc_lengths, sources, destinations, num_threads, path_container);
534}
535
536template <>
538 const StaticGraph<>& graph, const std::vector<PathDistance>& arc_lengths,
539 const std::vector<StaticGraph<>::NodeIndex>& sources,
540 const std::vector<StaticGraph<>::NodeIndex>& destinations, int num_threads,
541 PathContainer* const path_container) {
543 graph, arc_lengths, sources, destinations, num_threads, path_container);
544}
545
546template <>
548 const ReverseArcListGraph<>& graph,
549 const std::vector<PathDistance>& arc_lengths,
550 const std::vector<ReverseArcListGraph<>::NodeIndex>& sources,
551 const std::vector<ReverseArcListGraph<>::NodeIndex>& destinations,
552 int num_threads, PathContainer* const path_container) {
554 graph, arc_lengths, sources, destinations, num_threads, path_container);
555}
556
557template <>
559 const ReverseArcStaticGraph<>& graph,
560 const std::vector<PathDistance>& arc_lengths,
561 const std::vector<ReverseArcStaticGraph<>::NodeIndex>& sources,
562 const std::vector<ReverseArcStaticGraph<>::NodeIndex>& destinations,
563 int num_threads, PathContainer* const path_container) {
565 graph, arc_lengths, sources, destinations, num_threads, path_container);
566}
567
568template <>
570 const ReverseArcMixedGraph<>& graph,
571 const std::vector<PathDistance>& arc_lengths,
572 const std::vector<ReverseArcMixedGraph<>::NodeIndex>& sources,
573 const std::vector<ReverseArcMixedGraph<>::NodeIndex>& destinations,
574 int num_threads, PathContainer* const path_container) {
576 graph, arc_lengths, sources, destinations, num_threads, path_container);
577}
578
579} // namespace operations_research
bool Contains(const T *val) const
void Pop()
If there are ties for the top, this returns all of them.
double Get() const
Definition timer.h:46
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
virtual void Finalize()
Called when no more path will be added to the container.
virtual void StoreSingleSourcePaths(NodeIndex from, const std::vector< NodeIndex > &predecessor_in_path_tree, const std::vector< PathDistance > &distance_to_destination)=0
virtual void Initialize(const std::vector< NodeIndex > &sources, const std::vector< NodeIndex > &destinations, NodeIndex num_nodes)=0
virtual void GetPath(NodeIndex from, NodeIndex to, std::vector< NodeIndex > *path) const =0
Returns path nodes from node "from" to node "to" in a ordered vector.
virtual PathDistance GetDistance(NodeIndex from, NodeIndex to) const =0
virtual NodeIndex GetPenultimateNodeInPath(NodeIndex from, NodeIndex to) const =0
static void BuildInMemoryCompactPathContainer(PathContainer *path_container)
NodeIndex GetPenultimateNodeInPath(NodeIndex from, NodeIndex to) const
void GetPath(NodeIndex from, NodeIndex to, std::vector< NodeIndex > *path) const
PathDistance GetDistance(NodeIndex from, NodeIndex to) const
static void BuildPathDistanceContainer(PathContainer *path_container)
Builds a path container which only stores distances between path nodes.
PathContainerImpl * GetImplementation() const
For internal use only. Returns the internal container implementation.
Block * next
std::vector< double > arc_lengths
GraphType graph
int arc
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
Definition map_util.h:36
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
problem is infeasible or unbounded (default).
Definition matchers.h:468
In SWIG mode, we don't want anything besides these top-level includes.
const PathDistance kDisconnectedPathDistance
void ComputeManyToManyShortestPathsWithMultipleThreadsInternal(const GraphType &graph, const std::vector< PathDistance > &arc_lengths, const std::vector< typename GraphType::NodeIndex > &sources, const std::vector< typename GraphType::NodeIndex > &destinations, int num_threads, PathContainer *const paths)
void ComputeManyToManyShortestPathsWithMultipleThreads(const ListGraph<> &graph, const std::vector< PathDistance > &arc_lengths, const std::vector< ListGraph<>::NodeIndex > &sources, const std::vector< ListGraph<>::NodeIndex > &destinations, int num_threads, PathContainer *const path_container)
EbertGraph< NodeIndex, ArcIndex > StarGraph
trees with all degrees equal to
int nodes
double distance
std::vector< int > reverse_destinations_
std::vector< int > reverse_sources_