1 | //======================================================================= |
---|
2 | // Copyright (C) 2005 Jong Soo Park <jongsoo.park -at- gmail.com> |
---|
3 | // |
---|
4 | // Distributed under the Boost Software License, Version 1.0. |
---|
5 | // (See accompanying file LICENSE_1_0.txt or copy at |
---|
6 | // http://www.boost.org/LICENSE_1_0.txt) |
---|
7 | //======================================================================= |
---|
8 | // Dominator tree computation |
---|
9 | #ifndef BOOST_GRAPH_DOMINATOR_HPP |
---|
10 | #define BOOST_GRAPH_DOMINATOR_HPP |
---|
11 | |
---|
12 | #include <boost/config.hpp> |
---|
13 | #include <deque> |
---|
14 | #include <set> |
---|
15 | #include <boost/graph/depth_first_search.hpp> |
---|
16 | |
---|
17 | namespace boost { |
---|
18 | namespace detail { |
---|
19 | /** |
---|
20 | * An extended time_stamper which also records vertices for each dfs number |
---|
21 | */ |
---|
22 | template<class TimeMap, class VertexVector, class TimeT, class Tag> |
---|
23 | class time_stamper_with_vertex_vector |
---|
24 | : public base_visitor< |
---|
25 | time_stamper_with_vertex_vector<TimeMap, VertexVector, TimeT, Tag> > |
---|
26 | { |
---|
27 | public : |
---|
28 | typedef Tag event_filter; |
---|
29 | time_stamper_with_vertex_vector(TimeMap timeMap, VertexVector& v, |
---|
30 | TimeT& t) |
---|
31 | : timeStamper_(timeMap, t), v_(v) { } |
---|
32 | |
---|
33 | template<class Graph> |
---|
34 | void |
---|
35 | operator()(const typename property_traits<TimeMap>::key_type& v, |
---|
36 | const Graph& g) |
---|
37 | { |
---|
38 | timeStamper_(v, g); |
---|
39 | v_[timeStamper_.m_time] = v; |
---|
40 | } |
---|
41 | |
---|
42 | private : |
---|
43 | time_stamper<TimeMap, TimeT, Tag> timeStamper_; |
---|
44 | VertexVector& v_; |
---|
45 | }; |
---|
46 | |
---|
47 | /** |
---|
48 | * A convenient way to create a time_stamper_with_vertex_vector |
---|
49 | */ |
---|
50 | template<class TimeMap, class VertexVector, class TimeT, class Tag> |
---|
51 | time_stamper_with_vertex_vector<TimeMap, VertexVector, TimeT, Tag> |
---|
52 | stamp_times_with_vertex_vector(TimeMap timeMap, VertexVector& v, TimeT& t, |
---|
53 | Tag) |
---|
54 | { |
---|
55 | return time_stamper_with_vertex_vector<TimeMap, VertexVector, TimeT, |
---|
56 | Tag>(timeMap, v, t); |
---|
57 | } |
---|
58 | |
---|
59 | template<class Graph, class IndexMap, class TimeMap, class PredMap, |
---|
60 | class DomTreePredMap> |
---|
61 | class dominator_visitor |
---|
62 | { |
---|
63 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; |
---|
64 | typedef typename graph_traits<Graph>::vertices_size_type VerticesSizeType; |
---|
65 | |
---|
66 | public : |
---|
67 | /** |
---|
68 | * @param g [in] the target graph of the dominator tree |
---|
69 | * @param entry [in] the entry node of g |
---|
70 | * @param domTreePredMap [out] the immediate dominator map |
---|
71 | * (parent map in dominator tree) |
---|
72 | */ |
---|
73 | dominator_visitor(const Graph& g, const Vertex& entry, |
---|
74 | DomTreePredMap domTreePredMap) |
---|
75 | : semi_(num_vertices(g)), |
---|
76 | ancestor_(num_vertices(g), graph_traits<Graph>::null_vertex()), |
---|
77 | samedom_(ancestor_), |
---|
78 | best_(semi_), |
---|
79 | semiMap_(make_iterator_property_map(semi_.begin(), |
---|
80 | get(vertex_index, g))), |
---|
81 | ancestorMap_(make_iterator_property_map(ancestor_.begin(), |
---|
82 | get(vertex_index, g))), |
---|
83 | bestMap_(make_iterator_property_map(best_.begin(), |
---|
84 | get(vertex_index, g))), |
---|
85 | buckets_(num_vertices(g)), |
---|
86 | bucketMap_(make_iterator_property_map(buckets_.begin(), |
---|
87 | get(vertex_index, g))), |
---|
88 | entry_(entry), |
---|
89 | domTreePredMap_(domTreePredMap), |
---|
90 | samedomMap(make_iterator_property_map(samedom_.begin(), |
---|
91 | get(vertex_index, g))) |
---|
92 | { |
---|
93 | } |
---|
94 | |
---|
95 | void |
---|
96 | operator()(const Vertex& n, const TimeMap& dfnumMap, |
---|
97 | const PredMap& parentMap, const Graph& g) |
---|
98 | { |
---|
99 | if (n == entry_) return; |
---|
100 | |
---|
101 | const VerticesSizeType numOfVertices = num_vertices(g); |
---|
102 | const Vertex p(get(parentMap, n)); |
---|
103 | Vertex s(p); |
---|
104 | |
---|
105 | // 1. Calculate the semidominator of n, |
---|
106 | // based on the semidominator thm. |
---|
107 | // * Semidominator thm. : To find the semidominator of a node n, |
---|
108 | // consider all predecessors v of n in the CFG (Control Flow Graph). |
---|
109 | // - If v is a proper ancestor of n in the spanning tree |
---|
110 | // (so dfnum(v) < dfnum(n)), then v is a candidate for semi(n) |
---|
111 | // - If v is a non-ancestor of n (so dfnum(v) > dfnum(n)) |
---|
112 | // then for each u that is an ancestor of v (or u = v), |
---|
113 | // Let semi(u) be a candidate for semi(n) |
---|
114 | // of all these candidates, the one with lowest dfnum is |
---|
115 | // the semidominator of n. |
---|
116 | |
---|
117 | // For each predecessor of n |
---|
118 | typename graph_traits<Graph>::in_edge_iterator inItr, inEnd; |
---|
119 | for (tie(inItr, inEnd) = in_edges(n, g); inItr != inEnd; ++inItr) |
---|
120 | { |
---|
121 | const Vertex v = source(*inItr, g); |
---|
122 | // To deal with unreachable nodes |
---|
123 | if (get(dfnumMap, v) < 0 || get(dfnumMap, v) >= numOfVertices) |
---|
124 | continue; |
---|
125 | |
---|
126 | Vertex s2; |
---|
127 | if (get(dfnumMap, v) <= get(dfnumMap, n)) |
---|
128 | s2 = v; |
---|
129 | else |
---|
130 | s2 = get(semiMap_, ancestor_with_lowest_semi_(v, dfnumMap)); |
---|
131 | |
---|
132 | if (get(dfnumMap, s2) < get(dfnumMap, s)) |
---|
133 | s = s2; |
---|
134 | } |
---|
135 | put(semiMap_, n, s); |
---|
136 | |
---|
137 | // 2. Calculation of n's dominator is deferred until |
---|
138 | // the path from s to n has been linked into the forest |
---|
139 | get(bucketMap_, s).push_back(n); |
---|
140 | get(ancestorMap_, n) = p; |
---|
141 | get(bestMap_, n) = n; |
---|
142 | |
---|
143 | // 3. Now that the path from p to v has been linked into |
---|
144 | // the spanning forest, these lines calculate the dominator of v, |
---|
145 | // based on the dominator thm., or else defer the calculation |
---|
146 | // until y's dominator is known |
---|
147 | // * Dominator thm. : On the spanning-tree path below semi(n) and |
---|
148 | // above or including n, let y be the node |
---|
149 | // with the smallest-numbered semidominator. Then, |
---|
150 | // |
---|
151 | // idom(n) = semi(n) if semi(y)=semi(n) or |
---|
152 | // idom(y) if semi(y) != semi(n) |
---|
153 | typename std::deque<Vertex>::iterator buckItr; |
---|
154 | for (buckItr = get(bucketMap_, p).begin(); |
---|
155 | buckItr != get(bucketMap_, p).end(); |
---|
156 | ++buckItr) |
---|
157 | { |
---|
158 | const Vertex v(*buckItr); |
---|
159 | const Vertex y(ancestor_with_lowest_semi_(v, dfnumMap)); |
---|
160 | if (get(semiMap_, y) == get(semiMap_, v)) |
---|
161 | put(domTreePredMap_, v, p); |
---|
162 | else |
---|
163 | put(samedomMap, v, y); |
---|
164 | } |
---|
165 | |
---|
166 | get(bucketMap_, p).clear(); |
---|
167 | } |
---|
168 | |
---|
169 | protected : |
---|
170 | |
---|
171 | /** |
---|
172 | * Evaluate function in Tarjan's path compression |
---|
173 | */ |
---|
174 | const Vertex |
---|
175 | ancestor_with_lowest_semi_(const Vertex& v, const TimeMap& dfnumMap) |
---|
176 | { |
---|
177 | const Vertex a(get(ancestorMap_, v)); |
---|
178 | |
---|
179 | if (get(ancestorMap_, a) != graph_traits<Graph>::null_vertex()) |
---|
180 | { |
---|
181 | const Vertex b(ancestor_with_lowest_semi_(a, dfnumMap)); |
---|
182 | |
---|
183 | put(ancestorMap_, v, get(ancestorMap_, a)); |
---|
184 | |
---|
185 | if (get(dfnumMap, get(semiMap_, b)) < |
---|
186 | get(dfnumMap, get(semiMap_, get(bestMap_, v)))) |
---|
187 | put(bestMap_, v, b); |
---|
188 | } |
---|
189 | |
---|
190 | return get(bestMap_, v); |
---|
191 | } |
---|
192 | |
---|
193 | std::vector<Vertex> semi_, ancestor_, samedom_, best_; |
---|
194 | PredMap semiMap_, ancestorMap_, bestMap_; |
---|
195 | std::vector< std::deque<Vertex> > buckets_; |
---|
196 | |
---|
197 | iterator_property_map<typename std::vector<std::deque<Vertex> >::iterator, |
---|
198 | IndexMap> bucketMap_; |
---|
199 | |
---|
200 | const Vertex& entry_; |
---|
201 | DomTreePredMap domTreePredMap_; |
---|
202 | |
---|
203 | public : |
---|
204 | |
---|
205 | PredMap samedomMap; |
---|
206 | }; |
---|
207 | |
---|
208 | } // namespace detail |
---|
209 | |
---|
210 | /** |
---|
211 | * @brief Build dominator tree using Lengauer-Tarjan algorithm. |
---|
212 | * It takes O((V+E)log(V+E)) time. |
---|
213 | * |
---|
214 | * @pre dfnumMap, parentMap and verticesByDFNum have dfs results corresponding |
---|
215 | * indexMap. |
---|
216 | * If dfs has already run before, |
---|
217 | * this function would be good for saving computations. |
---|
218 | * @pre Unreachable nodes must be masked as |
---|
219 | * graph_traits<Graph>::null_vertex in parentMap. |
---|
220 | * @pre Unreachable nodes must be masked as |
---|
221 | * (std::numeric_limits<VerticesSizeType>::max)() in dfnumMap. |
---|
222 | * |
---|
223 | * @param domTreePredMap [out] : immediate dominator map (parent map |
---|
224 | * in dom. tree) |
---|
225 | * |
---|
226 | * @note reference Appel. p. 452~453. algorithm 19.9, 19.10. |
---|
227 | * |
---|
228 | * @todo : Optimization in Finding Dominators in Practice, Loukas Georgiadis |
---|
229 | */ |
---|
230 | template<class Graph, class IndexMap, class TimeMap, class PredMap, |
---|
231 | class VertexVector, class DomTreePredMap> |
---|
232 | void |
---|
233 | lengauer_tarjan_dominator_tree_without_dfs |
---|
234 | (const Graph& g, |
---|
235 | const typename graph_traits<Graph>::vertex_descriptor& entry, |
---|
236 | const IndexMap& indexMap, |
---|
237 | TimeMap dfnumMap, PredMap parentMap, VertexVector& verticesByDFNum, |
---|
238 | DomTreePredMap domTreePredMap) |
---|
239 | { |
---|
240 | // Typedefs and concept check |
---|
241 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; |
---|
242 | typedef typename graph_traits<Graph>::vertices_size_type VerticesSizeType; |
---|
243 | |
---|
244 | function_requires< BidirectionalGraphConcept<Graph> >(); |
---|
245 | |
---|
246 | const VerticesSizeType numOfVertices = num_vertices(g); |
---|
247 | if (numOfVertices == 0) return; |
---|
248 | |
---|
249 | // 1. Visit each vertex in reverse post order and calculate sdom. |
---|
250 | detail::dominator_visitor<Graph, IndexMap, TimeMap, PredMap, DomTreePredMap> |
---|
251 | visitor(g, entry, domTreePredMap); |
---|
252 | |
---|
253 | VerticesSizeType i; |
---|
254 | for (i = 0; i < numOfVertices; ++i) |
---|
255 | { |
---|
256 | const Vertex u(verticesByDFNum[numOfVertices - 1 - i]); |
---|
257 | if (u != graph_traits<Graph>::null_vertex()) |
---|
258 | visitor(u, dfnumMap, parentMap, g); |
---|
259 | } |
---|
260 | |
---|
261 | // 2. Now all the deferred dominator calculations, |
---|
262 | // based on the second clause of the dominator thm., are performed |
---|
263 | for (i = 0; i < numOfVertices; ++i) |
---|
264 | { |
---|
265 | const Vertex n(verticesByDFNum[i]); |
---|
266 | |
---|
267 | if (n == entry || n == graph_traits<Graph>::null_vertex()) |
---|
268 | continue; |
---|
269 | |
---|
270 | Vertex u = get(visitor.samedomMap, n); |
---|
271 | if (u != graph_traits<Graph>::null_vertex()) |
---|
272 | { |
---|
273 | put(domTreePredMap, n, get(domTreePredMap, u)); |
---|
274 | } |
---|
275 | } |
---|
276 | } |
---|
277 | |
---|
278 | /** |
---|
279 | * Unlike lengauer_tarjan_dominator_tree_without_dfs, |
---|
280 | * dfs is run in this function and |
---|
281 | * the result is written to dfnumMap, parentMap, vertices. |
---|
282 | * |
---|
283 | * If the result of dfs required after this algorithm, |
---|
284 | * this function can eliminate the need of rerunning dfs. |
---|
285 | */ |
---|
286 | template<class Graph, class IndexMap, class TimeMap, class PredMap, |
---|
287 | class VertexVector, class DomTreePredMap> |
---|
288 | void |
---|
289 | lengauer_tarjan_dominator_tree |
---|
290 | (const Graph& g, |
---|
291 | const typename graph_traits<Graph>::vertex_descriptor& entry, |
---|
292 | const IndexMap& indexMap, |
---|
293 | TimeMap dfnumMap, PredMap parentMap, VertexVector& verticesByDFNum, |
---|
294 | DomTreePredMap domTreePredMap) |
---|
295 | { |
---|
296 | // Typedefs and concept check |
---|
297 | typedef typename graph_traits<Graph>::vertices_size_type VerticesSizeType; |
---|
298 | |
---|
299 | function_requires< BidirectionalGraphConcept<Graph> >(); |
---|
300 | |
---|
301 | // 1. Depth first visit |
---|
302 | const VerticesSizeType numOfVertices = num_vertices(g); |
---|
303 | if (numOfVertices == 0) return; |
---|
304 | |
---|
305 | VerticesSizeType time = |
---|
306 | (std::numeric_limits<VerticesSizeType>::max)(); |
---|
307 | std::vector<default_color_type> |
---|
308 | colors(numOfVertices, color_traits<default_color_type>::white()); |
---|
309 | depth_first_visit |
---|
310 | (g, entry, |
---|
311 | make_dfs_visitor |
---|
312 | (make_pair(record_predecessors(parentMap, on_tree_edge()), |
---|
313 | detail::stamp_times_with_vertex_vector |
---|
314 | (dfnumMap, verticesByDFNum, time, on_discover_vertex()))), |
---|
315 | make_iterator_property_map(colors.begin(), indexMap)); |
---|
316 | |
---|
317 | // 2. Run main algorithm. |
---|
318 | lengauer_tarjan_dominator_tree_without_dfs(g, entry, indexMap, dfnumMap, |
---|
319 | parentMap, verticesByDFNum, |
---|
320 | domTreePredMap); |
---|
321 | } |
---|
322 | |
---|
323 | /** |
---|
324 | * Use vertex_index as IndexMap and make dfnumMap, parentMap, verticesByDFNum |
---|
325 | * internally. |
---|
326 | * If we don't need the result of dfs (dfnumMap, parentMap, verticesByDFNum), |
---|
327 | * this function would be more convenient one. |
---|
328 | */ |
---|
329 | template<class Graph, class DomTreePredMap> |
---|
330 | void |
---|
331 | lengauer_tarjan_dominator_tree |
---|
332 | (const Graph& g, |
---|
333 | const typename graph_traits<Graph>::vertex_descriptor& entry, |
---|
334 | DomTreePredMap domTreePredMap) |
---|
335 | { |
---|
336 | // typedefs |
---|
337 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; |
---|
338 | typedef typename graph_traits<Graph>::vertices_size_type VerticesSizeType; |
---|
339 | typedef typename property_map<Graph, vertex_index_t>::const_type IndexMap; |
---|
340 | typedef |
---|
341 | iterator_property_map<typename std::vector<VerticesSizeType>::iterator, |
---|
342 | IndexMap> TimeMap; |
---|
343 | typedef |
---|
344 | iterator_property_map<typename std::vector<Vertex>::iterator, IndexMap> |
---|
345 | PredMap; |
---|
346 | |
---|
347 | // Make property maps |
---|
348 | const VerticesSizeType numOfVertices = num_vertices(g); |
---|
349 | if (numOfVertices == 0) return; |
---|
350 | |
---|
351 | const IndexMap indexMap = get(vertex_index, g); |
---|
352 | |
---|
353 | std::vector<VerticesSizeType> dfnum(numOfVertices, 0); |
---|
354 | TimeMap dfnumMap(make_iterator_property_map(dfnum.begin(), indexMap)); |
---|
355 | |
---|
356 | std::vector<Vertex> parent(numOfVertices, |
---|
357 | graph_traits<Graph>::null_vertex()); |
---|
358 | PredMap parentMap(make_iterator_property_map(parent.begin(), indexMap)); |
---|
359 | |
---|
360 | std::vector<Vertex> verticesByDFNum(parent); |
---|
361 | |
---|
362 | // Run main algorithm |
---|
363 | lengauer_tarjan_dominator_tree(g, entry, |
---|
364 | indexMap, dfnumMap, parentMap, |
---|
365 | verticesByDFNum, domTreePredMap); |
---|
366 | } |
---|
367 | |
---|
368 | /** |
---|
369 | * Muchnick. p. 182, 184 |
---|
370 | * |
---|
371 | * using iterative bit vector analysis |
---|
372 | */ |
---|
373 | template<class Graph, class IndexMap, class DomTreePredMap> |
---|
374 | void |
---|
375 | iterative_bit_vector_dominator_tree |
---|
376 | (const Graph& g, |
---|
377 | const typename graph_traits<Graph>::vertex_descriptor& entry, |
---|
378 | const IndexMap& indexMap, |
---|
379 | DomTreePredMap domTreePredMap) |
---|
380 | { |
---|
381 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; |
---|
382 | typedef typename graph_traits<Graph>::vertex_iterator vertexItr; |
---|
383 | typedef typename graph_traits<Graph>::vertices_size_type VerticesSizeType; |
---|
384 | typedef |
---|
385 | iterator_property_map<typename std::vector< std::set<Vertex> >::iterator, |
---|
386 | IndexMap> vertexSetMap; |
---|
387 | |
---|
388 | function_requires<BidirectionalGraphConcept<Graph> >(); |
---|
389 | |
---|
390 | // 1. Finding dominator |
---|
391 | // 1.1. Initialize |
---|
392 | const VerticesSizeType numOfVertices = num_vertices(g); |
---|
393 | if (numOfVertices == 0) return; |
---|
394 | |
---|
395 | vertexItr vi, viend; |
---|
396 | tie(vi, viend) = vertices(g); |
---|
397 | const std::set<Vertex> N(vi, viend); |
---|
398 | |
---|
399 | bool change = true; |
---|
400 | |
---|
401 | std::vector< std::set<Vertex> > dom(numOfVertices, N); |
---|
402 | vertexSetMap domMap(make_iterator_property_map(dom.begin(), indexMap)); |
---|
403 | get(domMap, entry).clear(); |
---|
404 | get(domMap, entry).insert(entry); |
---|
405 | |
---|
406 | while (change) |
---|
407 | { |
---|
408 | change = false; |
---|
409 | for (tie(vi, viend) = vertices(g); vi != viend; ++vi) |
---|
410 | { |
---|
411 | if (*vi == entry) continue; |
---|
412 | |
---|
413 | std::set<Vertex> T(N); |
---|
414 | |
---|
415 | typename graph_traits<Graph>::in_edge_iterator inItr, inEnd; |
---|
416 | for (tie(inItr, inEnd) = in_edges(*vi, g); inItr != inEnd; ++inItr) |
---|
417 | { |
---|
418 | const Vertex p = source(*inItr, g); |
---|
419 | |
---|
420 | std::set<Vertex> tempSet; |
---|
421 | std::set_intersection(T.begin(), T.end(), |
---|
422 | get(domMap, p).begin(), |
---|
423 | get(domMap, p).end(), |
---|
424 | std::inserter(tempSet, tempSet.begin())); |
---|
425 | T.swap(tempSet); |
---|
426 | } |
---|
427 | |
---|
428 | T.insert(*vi); |
---|
429 | if (T != get(domMap, *vi)) |
---|
430 | { |
---|
431 | change = true; |
---|
432 | get(domMap, *vi).swap(T); |
---|
433 | } |
---|
434 | } // end of for (tie(vi, viend) = vertices(g) |
---|
435 | } // end of while(change) |
---|
436 | |
---|
437 | // 2. Build dominator tree |
---|
438 | for (tie(vi, viend) = vertices(g); vi != viend; ++vi) |
---|
439 | get(domMap, *vi).erase(*vi); |
---|
440 | |
---|
441 | Graph domTree(numOfVertices); |
---|
442 | |
---|
443 | for (tie(vi, viend) = vertices(g); vi != viend; ++vi) |
---|
444 | { |
---|
445 | if (*vi == entry) continue; |
---|
446 | |
---|
447 | // We have to iterate through copied dominator set |
---|
448 | const std::set<Vertex> tempSet(get(domMap, *vi)); |
---|
449 | typename std::set<Vertex>::const_iterator s; |
---|
450 | for (s = tempSet.begin(); s != tempSet.end(); ++s) |
---|
451 | { |
---|
452 | typename std::set<Vertex>::iterator t; |
---|
453 | for (t = get(domMap, *vi).begin(); t != get(domMap, *vi).end(); ) |
---|
454 | { |
---|
455 | typename std::set<Vertex>::iterator old_t = t; |
---|
456 | ++t; // Done early because t may become invalid |
---|
457 | if (*old_t == *s) continue; |
---|
458 | if (get(domMap, *s).find(*old_t) != get(domMap, *s).end()) |
---|
459 | get(domMap, *vi).erase(old_t); |
---|
460 | } |
---|
461 | } |
---|
462 | } |
---|
463 | |
---|
464 | for (tie(vi, viend) = vertices(g); vi != viend; ++vi) |
---|
465 | { |
---|
466 | if (*vi != entry && get(domMap, *vi).size() == 1) |
---|
467 | { |
---|
468 | Vertex temp = *get(domMap, *vi).begin(); |
---|
469 | put(domTreePredMap, *vi, temp); |
---|
470 | } |
---|
471 | } |
---|
472 | } |
---|
473 | |
---|
474 | template<class Graph, class DomTreePredMap> |
---|
475 | void |
---|
476 | iterative_bit_vector_dominator_tree |
---|
477 | (const Graph& g, |
---|
478 | const typename graph_traits<Graph>::vertex_descriptor& entry, |
---|
479 | DomTreePredMap domTreePredMap) |
---|
480 | { |
---|
481 | typename property_map<Graph, vertex_index_t>::const_type |
---|
482 | indexMap = get(vertex_index, g); |
---|
483 | |
---|
484 | iterative_bit_vector_dominator_tree(g, entry, indexMap, domTreePredMap); |
---|
485 | } |
---|
486 | } // namespace boost |
---|
487 | |
---|
488 | #endif // BOOST_GRAPH_DOMINATOR_HPP |
---|