1 | 1 |
/* -*- mode: C++; indent-tabs-mode: nil; -*- |
2 | 2 |
* |
3 | 3 |
* This file is a part of LEMON, a generic C++ optimization library. |
4 | 4 |
* |
5 | 5 |
* Copyright (C) 2003-2009 |
6 | 6 |
* Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport |
7 | 7 |
* (Egervary Research Group on Combinatorial Optimization, EGRES). |
8 | 8 |
* |
9 | 9 |
* Permission to use, modify and distribute this software is granted |
10 | 10 |
* provided that this copyright notice appears in all copies. For |
11 | 11 |
* precise terms see the accompanying LICENSE file. |
12 | 12 |
* |
13 | 13 |
* This software is provided "AS IS" with no warranty of any kind, |
14 | 14 |
* express or implied, and with no claim as to its suitability for any |
15 | 15 |
* purpose. |
16 | 16 |
* |
17 | 17 |
*/ |
18 | 18 |
|
19 | 19 |
#ifndef LEMON_SUURBALLE_H |
20 | 20 |
#define LEMON_SUURBALLE_H |
21 | 21 |
|
22 | 22 |
///\ingroup shortest_path |
23 | 23 |
///\file |
24 | 24 |
///\brief An algorithm for finding arc-disjoint paths between two |
25 | 25 |
/// nodes having minimum total length. |
26 | 26 |
|
27 | 27 |
#include <vector> |
28 | 28 |
#include <limits> |
29 | 29 |
#include <lemon/bin_heap.h> |
30 | 30 |
#include <lemon/path.h> |
31 | 31 |
#include <lemon/list_graph.h> |
32 | 32 |
#include <lemon/maps.h> |
33 | 33 |
|
34 | 34 |
namespace lemon { |
35 | 35 |
|
36 | 36 |
/// \addtogroup shortest_path |
37 | 37 |
/// @{ |
38 | 38 |
|
39 | 39 |
/// \brief Algorithm for finding arc-disjoint paths between two nodes |
40 | 40 |
/// having minimum total length. |
41 | 41 |
/// |
42 | 42 |
/// \ref lemon::Suurballe "Suurballe" implements an algorithm for |
43 | 43 |
/// finding arc-disjoint paths having minimum total length (cost) |
44 | 44 |
/// from a given source node to a given target node in a digraph. |
45 | 45 |
/// |
46 | 46 |
/// Note that this problem is a special case of the \ref min_cost_flow |
47 | 47 |
/// "minimum cost flow problem". This implementation is actually an |
48 | 48 |
/// efficient specialized version of the \ref CapacityScaling |
49 | 49 |
/// "Successive Shortest Path" algorithm directly for this problem. |
50 | 50 |
/// Therefore this class provides query functions for flow values and |
51 | 51 |
/// node potentials (the dual solution) just like the minimum cost flow |
52 | 52 |
/// algorithms. |
53 | 53 |
/// |
54 | 54 |
/// \tparam GR The digraph type the algorithm runs on. |
55 | 55 |
/// \tparam LEN The type of the length map. |
56 | 56 |
/// The default value is <tt>GR::ArcMap<int></tt>. |
57 | 57 |
/// |
58 |
/// \warning Length values should be \e non-negative |
|
58 |
/// \warning Length values should be \e non-negative. |
|
59 | 59 |
/// |
60 | 60 |
/// \note For finding node-disjoint paths this algorithm can be used |
61 | 61 |
/// along with the \ref SplitNodes adaptor. |
62 | 62 |
#ifdef DOXYGEN |
63 | 63 |
template <typename GR, typename LEN> |
64 | 64 |
#else |
65 | 65 |
template < typename GR, |
66 | 66 |
typename LEN = typename GR::template ArcMap<int> > |
67 | 67 |
#endif |
68 | 68 |
class Suurballe |
69 | 69 |
{ |
70 | 70 |
TEMPLATE_DIGRAPH_TYPEDEFS(GR); |
71 | 71 |
|
72 | 72 |
typedef ConstMap<Arc, int> ConstArcMap; |
73 | 73 |
typedef typename GR::template NodeMap<Arc> PredMap; |
74 | 74 |
|
75 | 75 |
public: |
76 | 76 |
|
77 | 77 |
/// The type of the digraph the algorithm runs on. |
78 | 78 |
typedef GR Digraph; |
79 | 79 |
/// The type of the length map. |
80 | 80 |
typedef LEN LengthMap; |
81 | 81 |
/// The type of the lengths. |
82 | 82 |
typedef typename LengthMap::Value Length; |
83 | 83 |
#ifdef DOXYGEN |
84 | 84 |
/// The type of the flow map. |
85 | 85 |
typedef GR::ArcMap<int> FlowMap; |
86 | 86 |
/// The type of the potential map. |
87 | 87 |
typedef GR::NodeMap<Length> PotentialMap; |
88 | 88 |
#else |
89 | 89 |
/// The type of the flow map. |
90 | 90 |
typedef typename Digraph::template ArcMap<int> FlowMap; |
91 | 91 |
/// The type of the potential map. |
92 | 92 |
typedef typename Digraph::template NodeMap<Length> PotentialMap; |
93 | 93 |
#endif |
94 | 94 |
|
95 | 95 |
/// The type of the path structures. |
96 | 96 |
typedef SimplePath<GR> Path; |
97 | 97 |
|
98 | 98 |
private: |
99 | 99 |
|
100 | 100 |
// ResidualDijkstra is a special implementation of the |
101 | 101 |
// Dijkstra algorithm for finding shortest paths in the |
102 | 102 |
// residual network with respect to the reduced arc lengths |
103 | 103 |
// and modifying the node potentials according to the |
104 | 104 |
// distance of the nodes. |
105 | 105 |
class ResidualDijkstra |
106 | 106 |
{ |
107 | 107 |
typedef typename Digraph::template NodeMap<int> HeapCrossRef; |
108 | 108 |
typedef BinHeap<Length, HeapCrossRef> Heap; |
109 | 109 |
|
110 | 110 |
private: |
111 | 111 |
|
112 | 112 |
// The digraph the algorithm runs on |
113 | 113 |
const Digraph &_graph; |
114 | 114 |
|
115 | 115 |
// The main maps |
116 | 116 |
const FlowMap &_flow; |
117 | 117 |
const LengthMap &_length; |
118 | 118 |
PotentialMap &_potential; |
119 | 119 |
|
120 | 120 |
// The distance map |
121 | 121 |
PotentialMap _dist; |
122 | 122 |
// The pred arc map |
123 | 123 |
PredMap &_pred; |
124 | 124 |
// The processed (i.e. permanently labeled) nodes |
125 | 125 |
std::vector<Node> _proc_nodes; |
126 | 126 |
|
127 | 127 |
Node _s; |
128 | 128 |
Node _t; |
129 | 129 |
|
130 | 130 |
public: |
131 | 131 |
|
132 | 132 |
/// Constructor. |
133 | 133 |
ResidualDijkstra( const Digraph &graph, |
134 | 134 |
const FlowMap &flow, |
135 | 135 |
const LengthMap &length, |
136 | 136 |
PotentialMap &potential, |
137 | 137 |
PredMap &pred, |
138 | 138 |
Node s, Node t ) : |
139 | 139 |
_graph(graph), _flow(flow), _length(length), _potential(potential), |
140 | 140 |
_dist(graph), _pred(pred), _s(s), _t(t) {} |
141 | 141 |
|
142 | 142 |
/// \brief Run the algorithm. It returns \c true if a path is found |
143 | 143 |
/// from the source node to the target node. |
144 | 144 |
bool run() { |
145 | 145 |
HeapCrossRef heap_cross_ref(_graph, Heap::PRE_HEAP); |
146 | 146 |
Heap heap(heap_cross_ref); |
147 | 147 |
heap.push(_s, 0); |
148 | 148 |
_pred[_s] = INVALID; |
149 | 149 |
_proc_nodes.clear(); |
150 | 150 |
|
151 | 151 |
// Process nodes |
152 | 152 |
while (!heap.empty() && heap.top() != _t) { |
153 | 153 |
Node u = heap.top(), v; |
154 | 154 |
Length d = heap.prio() + _potential[u], nd; |
155 | 155 |
_dist[u] = heap.prio(); |
156 | 156 |
heap.pop(); |
157 | 157 |
_proc_nodes.push_back(u); |
158 | 158 |
|
159 | 159 |
// Traverse outgoing arcs |
160 | 160 |
for (OutArcIt e(_graph, u); e != INVALID; ++e) { |
161 | 161 |
if (_flow[e] == 0) { |
162 | 162 |
v = _graph.target(e); |
163 | 163 |
switch(heap.state(v)) { |
164 | 164 |
case Heap::PRE_HEAP: |
165 | 165 |
heap.push(v, d + _length[e] - _potential[v]); |
166 | 166 |
_pred[v] = e; |
167 | 167 |
break; |
168 | 168 |
case Heap::IN_HEAP: |
169 | 169 |
nd = d + _length[e] - _potential[v]; |
170 | 170 |
if (nd < heap[v]) { |
171 | 171 |
heap.decrease(v, nd); |
172 | 172 |
_pred[v] = e; |
173 | 173 |
} |
174 | 174 |
break; |
175 | 175 |
case Heap::POST_HEAP: |
176 | 176 |
break; |
177 | 177 |
} |
178 | 178 |
} |
179 | 179 |
} |
180 | 180 |
|
181 | 181 |
// Traverse incoming arcs |
182 | 182 |
for (InArcIt e(_graph, u); e != INVALID; ++e) { |
183 | 183 |
if (_flow[e] == 1) { |
184 | 184 |
v = _graph.source(e); |
185 | 185 |
switch(heap.state(v)) { |
186 | 186 |
case Heap::PRE_HEAP: |
187 | 187 |
heap.push(v, d - _length[e] - _potential[v]); |
188 | 188 |
_pred[v] = e; |
189 | 189 |
break; |
190 | 190 |
case Heap::IN_HEAP: |
191 | 191 |
nd = d - _length[e] - _potential[v]; |
192 | 192 |
if (nd < heap[v]) { |
193 | 193 |
heap.decrease(v, nd); |
194 | 194 |
_pred[v] = e; |
195 | 195 |
} |
196 | 196 |
break; |
197 | 197 |
case Heap::POST_HEAP: |
198 | 198 |
break; |
199 | 199 |
} |
200 | 200 |
} |
201 | 201 |
} |
202 | 202 |
} |
203 | 203 |
if (heap.empty()) return false; |
204 | 204 |
|
205 | 205 |
// Update potentials of processed nodes |
206 | 206 |
Length t_dist = heap.prio(); |
207 | 207 |
for (int i = 0; i < int(_proc_nodes.size()); ++i) |
208 | 208 |
_potential[_proc_nodes[i]] += _dist[_proc_nodes[i]] - t_dist; |
209 | 209 |
return true; |
210 | 210 |
} |
211 | 211 |
|
212 | 212 |
}; //class ResidualDijkstra |
213 | 213 |
|
214 | 214 |
private: |
215 | 215 |
|
216 | 216 |
// The digraph the algorithm runs on |
217 | 217 |
const Digraph &_graph; |
218 | 218 |
// The length map |
219 | 219 |
const LengthMap &_length; |
220 | 220 |
|
221 | 221 |
// Arc map of the current flow |
222 | 222 |
FlowMap *_flow; |
223 | 223 |
bool _local_flow; |
224 | 224 |
// Node map of the current potentials |
225 | 225 |
PotentialMap *_potential; |
226 | 226 |
bool _local_potential; |
227 | 227 |
|
228 | 228 |
// The source node |
229 | 229 |
Node _source; |
230 | 230 |
// The target node |
231 | 231 |
Node _target; |
232 | 232 |
|
233 | 233 |
// Container to store the found paths |
234 | 234 |
std::vector< SimplePath<Digraph> > paths; |
235 | 235 |
int _path_num; |
236 | 236 |
|
237 | 237 |
// The pred arc map |
238 | 238 |
PredMap _pred; |
239 | 239 |
// Implementation of the Dijkstra algorithm for finding augmenting |
240 | 240 |
// shortest paths in the residual network |
241 | 241 |
ResidualDijkstra *_dijkstra; |
242 | 242 |
|
243 | 243 |
public: |
244 | 244 |
|
245 | 245 |
/// \brief Constructor. |
246 | 246 |
/// |
247 | 247 |
/// Constructor. |
248 | 248 |
/// |
249 | 249 |
/// \param graph The digraph the algorithm runs on. |
250 | 250 |
/// \param length The length (cost) values of the arcs. |
251 | 251 |
Suurballe( const Digraph &graph, |
252 | 252 |
const LengthMap &length ) : |
253 | 253 |
_graph(graph), _length(length), _flow(0), _local_flow(false), |
254 | 254 |
_potential(0), _local_potential(false), _pred(graph) |
255 |
{ |
|
256 |
LEMON_ASSERT(std::numeric_limits<Length>::is_integer, |
|
257 |
"The length type of Suurballe must be integer"); |
|
258 |
} |
|
255 |
{} |
|
259 | 256 |
|
260 | 257 |
/// Destructor. |
261 | 258 |
~Suurballe() { |
262 | 259 |
if (_local_flow) delete _flow; |
263 | 260 |
if (_local_potential) delete _potential; |
264 | 261 |
delete _dijkstra; |
265 | 262 |
} |
266 | 263 |
|
267 | 264 |
/// \brief Set the flow map. |
268 | 265 |
/// |
269 | 266 |
/// This function sets the flow map. |
270 | 267 |
/// If it is not used before calling \ref run() or \ref init(), |
271 | 268 |
/// an instance will be allocated automatically. The destructor |
272 | 269 |
/// deallocates this automatically allocated map, of course. |
273 | 270 |
/// |
274 | 271 |
/// The found flow contains only 0 and 1 values, since it is the |
275 | 272 |
/// union of the found arc-disjoint paths. |
276 | 273 |
/// |
277 | 274 |
/// \return <tt>(*this)</tt> |
278 | 275 |
Suurballe& flowMap(FlowMap &map) { |
279 | 276 |
if (_local_flow) { |
280 | 277 |
delete _flow; |
281 | 278 |
_local_flow = false; |
282 | 279 |
} |
283 | 280 |
_flow = ↦ |
284 | 281 |
return *this; |
285 | 282 |
} |
286 | 283 |
|
287 | 284 |
/// \brief Set the potential map. |
288 | 285 |
/// |
289 | 286 |
/// This function sets the potential map. |
290 | 287 |
/// If it is not used before calling \ref run() or \ref init(), |
291 | 288 |
/// an instance will be allocated automatically. The destructor |
292 | 289 |
/// deallocates this automatically allocated map, of course. |
293 | 290 |
/// |
294 | 291 |
/// The node potentials provide the dual solution of the underlying |
295 | 292 |
/// \ref min_cost_flow "minimum cost flow problem". |
296 | 293 |
/// |
297 | 294 |
/// \return <tt>(*this)</tt> |
298 | 295 |
Suurballe& potentialMap(PotentialMap &map) { |
299 | 296 |
if (_local_potential) { |
300 | 297 |
delete _potential; |
301 | 298 |
_local_potential = false; |
302 | 299 |
} |
303 | 300 |
_potential = ↦ |
304 | 301 |
return *this; |
305 | 302 |
} |
306 | 303 |
|
307 | 304 |
/// \name Execution Control |
308 | 305 |
/// The simplest way to execute the algorithm is to call the run() |
309 | 306 |
/// function. |
310 | 307 |
/// \n |
311 | 308 |
/// If you only need the flow that is the union of the found |
312 | 309 |
/// arc-disjoint paths, you may call init() and findFlow(). |
313 | 310 |
|
314 | 311 |
/// @{ |
315 | 312 |
|
316 | 313 |
/// \brief Run the algorithm. |
317 | 314 |
/// |
318 | 315 |
/// This function runs the algorithm. |
319 | 316 |
/// |
320 | 317 |
/// \param s The source node. |
321 | 318 |
/// \param t The target node. |
322 | 319 |
/// \param k The number of paths to be found. |
323 | 320 |
/// |
324 | 321 |
/// \return \c k if there are at least \c k arc-disjoint paths from |
325 | 322 |
/// \c s to \c t in the digraph. Otherwise it returns the number of |
326 | 323 |
/// arc-disjoint paths found. |
327 | 324 |
/// |
328 | 325 |
/// \note Apart from the return value, <tt>s.run(s, t, k)</tt> is |
329 | 326 |
/// just a shortcut of the following code. |
330 | 327 |
/// \code |
331 | 328 |
/// s.init(s); |
332 | 329 |
/// s.findFlow(t, k); |
333 | 330 |
/// s.findPaths(); |
334 | 331 |
/// \endcode |
335 | 332 |
int run(const Node& s, const Node& t, int k = 2) { |
336 | 333 |
init(s); |
337 | 334 |
findFlow(t, k); |
338 | 335 |
findPaths(); |
339 | 336 |
return _path_num; |
340 | 337 |
} |
341 | 338 |
|
342 | 339 |
/// \brief Initialize the algorithm. |
343 | 340 |
/// |
344 | 341 |
/// This function initializes the algorithm. |
345 | 342 |
/// |
346 | 343 |
/// \param s The source node. |
347 | 344 |
void init(const Node& s) { |
348 | 345 |
_source = s; |
349 | 346 |
|
350 | 347 |
// Initialize maps |
351 | 348 |
if (!_flow) { |
352 | 349 |
_flow = new FlowMap(_graph); |
353 | 350 |
_local_flow = true; |
354 | 351 |
} |
355 | 352 |
if (!_potential) { |
356 | 353 |
_potential = new PotentialMap(_graph); |
357 | 354 |
_local_potential = true; |
358 | 355 |
} |
359 | 356 |
for (ArcIt e(_graph); e != INVALID; ++e) (*_flow)[e] = 0; |
360 | 357 |
for (NodeIt n(_graph); n != INVALID; ++n) (*_potential)[n] = 0; |
361 | 358 |
} |
362 | 359 |
|
363 | 360 |
/// \brief Execute the algorithm to find an optimal flow. |
364 | 361 |
/// |
365 | 362 |
/// This function executes the successive shortest path algorithm to |
366 | 363 |
/// find a minimum cost flow, which is the union of \c k (or less) |
367 | 364 |
/// arc-disjoint paths. |
368 | 365 |
/// |
369 | 366 |
/// \param t The target node. |
370 | 367 |
/// \param k The number of paths to be found. |
371 | 368 |
/// |
372 | 369 |
/// \return \c k if there are at least \c k arc-disjoint paths from |
373 | 370 |
/// the source node to the given node \c t in the digraph. |
374 | 371 |
/// Otherwise it returns the number of arc-disjoint paths found. |
375 | 372 |
/// |
376 | 373 |
/// \pre \ref init() must be called before using this function. |
377 | 374 |
int findFlow(const Node& t, int k = 2) { |
378 | 375 |
_target = t; |
379 | 376 |
_dijkstra = |
380 | 377 |
new ResidualDijkstra( _graph, *_flow, _length, *_potential, _pred, |
381 | 378 |
_source, _target ); |
382 | 379 |
|
383 | 380 |
// Find shortest paths |
384 | 381 |
_path_num = 0; |
385 | 382 |
while (_path_num < k) { |
386 | 383 |
// Run Dijkstra |
387 | 384 |
if (!_dijkstra->run()) break; |
388 | 385 |
++_path_num; |
389 | 386 |
|
390 | 387 |
// Set the flow along the found shortest path |
391 | 388 |
Node u = _target; |
392 | 389 |
Arc e; |
393 | 390 |
while ((e = _pred[u]) != INVALID) { |
394 | 391 |
if (u == _graph.target(e)) { |
395 | 392 |
(*_flow)[e] = 1; |
396 | 393 |
u = _graph.source(e); |
397 | 394 |
} else { |
398 | 395 |
(*_flow)[e] = 0; |
399 | 396 |
u = _graph.target(e); |
400 | 397 |
} |
401 | 398 |
} |
402 | 399 |
} |
403 | 400 |
return _path_num; |
404 | 401 |
} |
405 | 402 |
|
406 | 403 |
/// \brief Compute the paths from the flow. |
407 | 404 |
/// |
408 | 405 |
/// This function computes the paths from the found minimum cost flow, |
409 | 406 |
/// which is the union of some arc-disjoint paths. |
410 | 407 |
/// |
411 | 408 |
/// \pre \ref init() and \ref findFlow() must be called before using |
412 | 409 |
/// this function. |
413 | 410 |
void findPaths() { |
414 | 411 |
FlowMap res_flow(_graph); |
415 | 412 |
for(ArcIt a(_graph); a != INVALID; ++a) res_flow[a] = (*_flow)[a]; |
416 | 413 |
|
417 | 414 |
paths.clear(); |
418 | 415 |
paths.resize(_path_num); |
419 | 416 |
for (int i = 0; i < _path_num; ++i) { |
420 | 417 |
Node n = _source; |
421 | 418 |
while (n != _target) { |
422 | 419 |
OutArcIt e(_graph, n); |
423 | 420 |
for ( ; res_flow[e] == 0; ++e) ; |
424 | 421 |
n = _graph.target(e); |
425 | 422 |
paths[i].addBack(e); |
426 | 423 |
res_flow[e] = 0; |
427 | 424 |
} |
428 | 425 |
} |
429 | 426 |
} |
430 | 427 |
|
431 | 428 |
/// @} |
432 | 429 |
|
433 | 430 |
/// \name Query Functions |
434 | 431 |
/// The results of the algorithm can be obtained using these |
435 | 432 |
/// functions. |
436 | 433 |
/// \n The algorithm should be executed before using them. |
437 | 434 |
|
438 | 435 |
/// @{ |
439 | 436 |
|
440 | 437 |
/// \brief Return the total length of the found paths. |
441 | 438 |
/// |
442 | 439 |
/// This function returns the total length of the found paths, i.e. |
443 | 440 |
/// the total cost of the found flow. |
444 | 441 |
/// The complexity of the function is O(e). |
445 | 442 |
/// |
446 | 443 |
/// \pre \ref run() or \ref findFlow() must be called before using |
447 | 444 |
/// this function. |
448 | 445 |
Length totalLength() const { |
449 | 446 |
Length c = 0; |
450 | 447 |
for (ArcIt e(_graph); e != INVALID; ++e) |
451 | 448 |
c += (*_flow)[e] * _length[e]; |
452 | 449 |
return c; |
453 | 450 |
} |
454 | 451 |
|
455 | 452 |
/// \brief Return the flow value on the given arc. |
456 | 453 |
/// |
457 | 454 |
/// This function returns the flow value on the given arc. |
458 | 455 |
/// It is \c 1 if the arc is involved in one of the found arc-disjoint |
459 | 456 |
/// paths, otherwise it is \c 0. |
460 | 457 |
/// |
461 | 458 |
/// \pre \ref run() or \ref findFlow() must be called before using |
462 | 459 |
/// this function. |
463 | 460 |
int flow(const Arc& arc) const { |
464 | 461 |
return (*_flow)[arc]; |
465 | 462 |
} |
466 | 463 |
|
467 | 464 |
/// \brief Return a const reference to an arc map storing the |
468 | 465 |
/// found flow. |
469 | 466 |
/// |
470 | 467 |
/// This function returns a const reference to an arc map storing |
471 | 468 |
/// the flow that is the union of the found arc-disjoint paths. |
472 | 469 |
/// |
473 | 470 |
/// \pre \ref run() or \ref findFlow() must be called before using |
474 | 471 |
/// this function. |
475 | 472 |
const FlowMap& flowMap() const { |
476 | 473 |
return *_flow; |
477 | 474 |
} |
478 | 475 |
|
479 | 476 |
/// \brief Return the potential of the given node. |
480 | 477 |
/// |
481 | 478 |
/// This function returns the potential of the given node. |
482 | 479 |
/// The node potentials provide the dual solution of the |
483 | 480 |
/// underlying \ref min_cost_flow "minimum cost flow problem". |
484 | 481 |
/// |
485 | 482 |
/// \pre \ref run() or \ref findFlow() must be called before using |
486 | 483 |
/// this function. |
487 | 484 |
Length potential(const Node& node) const { |
488 | 485 |
return (*_potential)[node]; |
489 | 486 |
} |
490 | 487 |
|
491 | 488 |
/// \brief Return a const reference to a node map storing the |
492 | 489 |
/// found potentials (the dual solution). |
493 | 490 |
/// |
494 | 491 |
/// This function returns a const reference to a node map storing |
495 | 492 |
/// the found potentials that provide the dual solution of the |
496 | 493 |
/// underlying \ref min_cost_flow "minimum cost flow problem". |
497 | 494 |
/// |
498 | 495 |
/// \pre \ref run() or \ref findFlow() must be called before using |
499 | 496 |
/// this function. |
500 | 497 |
const PotentialMap& potentialMap() const { |
501 | 498 |
return *_potential; |
502 | 499 |
} |
503 | 500 |
|
504 | 501 |
/// \brief Return the number of the found paths. |
505 | 502 |
/// |
506 | 503 |
/// This function returns the number of the found paths. |
507 | 504 |
/// |
508 | 505 |
/// \pre \ref run() or \ref findFlow() must be called before using |
509 | 506 |
/// this function. |
510 | 507 |
int pathNum() const { |
511 | 508 |
return _path_num; |
512 | 509 |
} |
513 | 510 |
|
514 | 511 |
/// \brief Return a const reference to the specified path. |
0 comments (0 inline)