图的抽象数学性质与它们被用来解决具体问题之间的主要联系就是被附加在图的顶点和边上的属性(property),比如距离(distance)、容量(capacity)、权重(weight)、颜色(color)等。根据不同的数据结构,有许多方法用来将各种 property 添加到图中,但是作用在图上的算法不需要去关心这些具体的细节。定义在章节 Property Map Concepts 中的“属性映射接口(property map interface)”为访问图中的 property 提供了一个通用方法。这里讲的是在BGL算法中用来访问各种 property 的接口。
Property Map Interface
Property map interface 明确指出每个属性都要用单独的属性映射对象(property map object)来访问。在下面的例子中,我们将展示函数 relax() 的一个实现,这个函数被用在 Dijkstra 最短路径算法中。在这个函数中,我们需要访问一条边的 weight property 和一个顶点的 distance property。我们把 relax() 写成一个模板函数,这样它就可以用在许多不同的场景中。这个函数的参数,weight 和 distance 是 property object。一般来说,BGL算法会给一个函数所需要的每个 property 传递一个 property map object。Property map interface 定义了一些函数,其中我们要用到的两个是:get() 和 put()。get() 函数接受一个 property map object,比如 distance, 和一个关键字对象(key object)作为参数。对于 distance property 我们用顶点对象 u 和 v 作为关键字。然后,get() 会返回对应顶点的 property value。Trait 类是被定义为用来提供一个通用的方法来推导特定的 property map 类型:property_map。
1 template <class Edge, class Graph, 2 class WeightPropertyMap, 3 class DistancePropertyMap> 4 bool relax(Edge e, const Graph& g, 5 WeightPropertyMap weight, 6 DistancePropertyMap distance) 7 { 8 typedef typename graph_traits<Graph>::vertex_descriptor Vertex; 9 Vertex u = source(e,g), v = target(e,g); 10 if ( get(distance, u) + get(weight, e) < get(distance, v)) { 11 put(distance, v, get(distance, u) + get(weight, e)); 12 return true; 13 } else 14 return false; 15 }
函数 get() 返回的是 property value 的一份拷贝。Property map interface 中的第三个函数,at(),它返回一个 property value 的引用(如果 map 不是 mutable 类型的,则返回类型是常量引用)。
和 STL 中的 iterator_traits 类相似,这里有一个 property_traits 类能够用来推导与 property map 相关的类型:key 和 value 的类型,还有 property map 的种类(category)(用来说明 map 是否可读、可写或者两者都可以)。在 relax() 函数中,我们可以用 property_traits 来推导 distance property 的局部变量的类型。
1 { 2 typedef typename graph_traits<Graph>::vertex_descriptor Vertex; 3 Vertex u = source(e,g), v = target(e,g); 4 typename property_traits<DistancePropertyMap>::value_type 5 du, dv; // local variables of the distance property type 6 du = get(distance, u); 7 dv = get(distance, v); 8 if (du + get(weight, e) < dv) { 9 put(distance, v, du + get(weight, e)); 10 return true; 11 } else 12 return false; 13 }
图的属性可以分为两类:内部的(interior)和外部的(interior)。
Interior Properties
以某些方式存储在图对象的“内部”,并且 property value 对象的生命周期和图对象的相同。
Exterior Properties
存储在图的“外部”并且 property value 对象的生命周期和图的相互独立。这对那些仅仅临时需要的 property 来说是很有用的,perhaps for the duration of a particular algorithm such as the color property used in breadth_first_search(). 当在一个 BGL 算法中使用 exterior properties 时,一个作为 exterior properties 的 property map object 必须作为参数传递给这个算法。
Interior Properties
一个支持内部属性(interior property)存储的图(如 adjacency_list)通过定义在 PropertyGraph 中的接口来访问它的 property map objects。其中有一个从图中获得 property map objects 的函数 get(Property, g)。第一个参数是 property 的类型,用来指明你想访问哪一种 property,第二个参数是一个图对象。一个图类型(graph type)必须用文档说明它支持访问哪一种 property (and therefore tags)。Property map 的类型依赖于 graph 的类型和当前所映射的属性。Trait 类型的作用是提供一个通用的方法来推导property map 的类型:property_map。下面的代码展示了如何为某些图 的 distance 和 weight 属性获取 property map。
property_map<Graph, vertex_distance_t>::type d = get(vertex_distance, g); property_map<Graph, edge_weight_t>::type w = get(edge_weight, g);
一般来说,BGL算法需要将它所需要的所有 property map 显式地传递给它。例如,BGL Dijkstra 最短路径算法需要四个 property map:distance,weight,color 和 vertex ID。
通常某些或者所有的 property 都会在图的内部,所以可以如下调用 Dijkstra 算法(给了图 g 和顶点 src)。
dijkstra_shortest_paths(g, src, distance_map(get(vertex_distance, g)). weight_map(get(edge_weight, g)). color_map(get(vertex_color, g)). vertex_index_map(get(vertex_index, g)));
因为指定所有的 property map 多少有点繁琐,BGL提供了一些默认行为,假设部分 property 是内部的并且可以通过 get(Property, g)来从图中访问,或者 property仅仅是内部使用,那么算法将用数组来为它自己创建 property map并且用图的 vertex index map做为数组的偏移量。下面我们展示了使用所有命名参数的默认值来调用 dijkstra_shortest_paths。这个调用和前面的Dijkstra算法的调用是相等的。
dijkstra_shortest_paths(g, src);
下一个问题是:内部 property 如何在一开始添加到图对象中?这取决于你所使用的图的类型。BGL的 adjacency_list 图使用一个属性机制(见章节 Internal Properties)来允许任意数量的 property被添加到图的边和顶点中。
Exterior Properties
这一节我们将描述两个构造外部 property 的方法,虽然那里有无数种方法来构造外部 property。
第一种方法是使用适配器类 iterator_property_map。这个类包装了一个随机访问迭代器,用它创建一个 property map。随机访问迭代器必须指向一个 property values 范围(range)的开始,并且这个 range 的长度必须是图中顶点或边的的数目(取决于它是一个顶点还是边 property map)。这个适配器还需要一个ID property map,用来映射顶点或者边描述符到 property value 的偏移量(从随机访问迭代器的偏移量)。这个 ID property map 是一个典型的图内部 property map。下面的例子展示了如何用 iterator_property_map 来给存储在数组中的 capacity 和 flow 属性创建外部 property map。这些数组是按照边的ID来索引的。边的ID通过一个 property 被加入到图中,并且ID的值是在边被加入到图中时给出的。这个例子的完整的源代码在example/exterior_properties.cpp中。其中 print_network() 函数打印出图和它的属性 flow 和 capacity 的值。
typedef adjacency_list<vecS, vecS, bidirectionalS, no_property, property<edge_index_t, std::size_t> > Graph; const int num_vertices = 9; Graph G(num_vertices); int capacity_array[] = { 10, 20, 20, 20, 40, 40, 20, 20, 20, 10 }; int flow_array[] = { 8, 12, 12, 12, 12, 12, 16, 16, 16, 8 }; // Add edges to the graph, and assign each edge an ID number. add_edge(0, 1, 0, G); // ... typedef graph_traits<Graph>::edge_descriptor Edge; typedef property_map<Graph, edge_index_t>::type EdgeID_Map; EdgeID_Map edge_id = get(edge_index, G); iterator_property_map <int*, int, int&, EdgeID_Map> capacity(capacity_array, edge_id), flow(flow_array, edge_id); print_network(G, capacity, flow);
第二种方法是用指针类型(指向property values数组的指针)作为property map。这种方法要求关键字类型必须是整型,以便关键字可以作为指针的偏移量。带有模板参数 VertexList=vecS 的 adjacency_list 使用整型作为顶点描述符(索引从0到图中顶点的数目),所以对于指针 property map 它们(顶点描述符)作为关键字是可行的。当 VertexList 不是 vecS时,那么顶点描述符就不是整型,所以就不能配合指针 property map 使用。相反的就必须使用上面所描述的使用 iterator_property_map 和 ID property map 的方法。edge_list 类也可以用整型作为顶点描述符,这取决于适配的边迭代器是如何定义的。在 example/bellman_ford.cpp 中的例子展示了通过把指针作为顶点 property map 来使用 edge_list。
指针可以做为 property map 是因为有数个重载的函数和一个特殊化的 property_traits,在头文件 boost/property_map/property_map.hpp 中, 用指针实现了 property map interface。这些函数的定义如下所列。
namespace boost { template <class T> struct property_traits<T*> { typedef T value_type; typedef ptrdiff_t key_type; typedef lvalue_property_map_tag category; }; template <class T> void put(T* pa, std::ptrdiff_t key, const T& value) { pa[key] = value; } template <class T> const T& get(const T* pa, std::ptrdiff_t key) { return pa[key]; } template <class T> const T& at(const T* pa, std::ptrdiff_t key) { return pa[key]; } template <class T> T& at(T* pa, std::ptrdiff_t key) { return pa[key]; } }
在下面的例子中,我们用数组存储图中每个顶点所代表的城市的名字,用 std::vector 存储在调用 breadth_first_search() 时所需要的顶点的颜色。因为 std::vector 的迭代器(通过调用 begin() 获得)是一个指针,指针 property map 方法也可以工作在 std::vector::iterator 上。这个例子的完整代码在 example/city_visitor.cpp 中。
1 // Definition of city_visitor omitted... 2 3 int main(int,char*[]) 4 { 5 enum { SanJose, SanFran, LA, SanDiego, Fresno, LosVegas, Reno, 6 Sacramento, SaltLake, Pheonix, N }; 7 8 // An array of vertex name properties 9 std::string names[] = { "San Jose", "San Francisco", "San Jose", 10 "San Francisco", "Los Angeles", "San Diego", 11 "Fresno", "Los Vegas", "Reno", "Sacramento", 12 "Salt Lake City", "Pheonix" }; 13 14 // Specify all the connecting roads between cities. 15 typedef std::pair<int,int> E; 16 E edge_array[] = { E(Sacramento, Reno), ... }; 17 18 // Specify the graph type. 19 typedef adjacency_list<vecS, vecS, undirectedS> Graph; 20 // Create the graph object, based on the edges in edge_array. 21 Graph G(N, edge_array, edge_array + sizeof(edge_array)/sizeof(E)); 22 23 // DFS and BFS need to "color" the vertices. 24 // Here we use std::vector as exterior property storage. 25 std::vector<default_color_type> colors(N); 26 27 cout << "*** Depth First ***" << endl; 28 depth_first_search(G, city_visitor(names), colors.begin()); 29 cout << endl; 30 31 // Get the source vertex 32 boost::graph_traits<Graph>::vertex_descriptor 33 s = vertex(SanJose, G); 34 35 cout << "*** Breadth First ***" << endl; 36 breadth_first_search(G, s, city_visitor(names), colors.begin()); 37 38 return 0; 39 }
Constructing an Exterior Property Map
实现你自己的外部 property map 不是十分困难。你只需要重载 property map concept 中所要求的那些函数中你所需要的部分。这意味着最多重载 put() 和 get() 函数并实现 operator[] 。当然,你的 property map 也需要为定义在 property_traits 中的所有的类型做嵌套定义,或者你可以为你的新 property map 创建一个 property_traits 特例。
类 iterator_property_map 的实现可以作为一个创建外部 property map 的好例子。这里我们展示一个 iterator_property_map 的简化版本,命名为 iterator_pa。
我们从定义 iterator_map 本身开始。这个适配器类使用适配的迭代器类型和 ID property map 作为模板参数。ID property map 的作用是把关键字(一般来说是顶点或边的描述符)映射为一个整型偏移量。iterator_map 需要一个 property map 所必须的三个类型定义:key_type, value_type 和 capacity。我们可以用 property_traits 得到 IDMap 的关键字类型,并且我们能够用 iterator_traits 来确定 Iterator 的值的类型。因为计划实现 at() 函数,所以我们选择 boost::lvalue_property_map_tag 作为 capacity。
template <class Iterator, class IDMap> class iterator_map { public: typedef typename boost::property_traits<IDMap>::key_type key_type; typedef typename std::iterator_traits<Iterator>::value_type value_type; typedef boost::lvalue_property_map_tag category; iterator_map(Iterator i = Iterator(), const IDMap& id = IDMap()) : m_iter(i), m_id(id) { } Iterator m_iter; IDMap m_id; };
下面我们实现三个 property map 函数,get(), put() 和 at()。在每个函数中,使用 m_id property map 将关键字对象转换为一个整型偏移量,用来作为随机访问迭代器 m_iter 的偏移量。
template <class Iter, class ID> typename std::iterator_traits<Iter>::value_type get(const iterator_map<Iter,ID>& i, typename boost::property_traits<ID>::key_type key) { return i.m_iter[i.m_id[key]]; } template <class Iter, class ID> void put(const iterator_map<Iter,ID>& i, typename boost::property_traits<ID>::key_type key, const typename std::iterator_traits<Iter>::value_type& value) { i.m_iter[i.m_id[key]] = value; } template <class Iter, class ID> typename std::iterator_traits<Iter>::reference at(const iterator_map<Iter,ID>& i, typename boost::property_traits<ID>::key_type key) { return i.m_iter[i.m_id[key]]; }
这就对了。iterator_map 类已经完成并且可以向前面使用 iterator_property_map 一样来使用。
附example代码:
example/exterior_properties.cpp
1 #include <boost/config.hpp> 2 #include <iostream> 3 #include <boost/graph/adjacency_list.hpp> 4 #include <boost/property_map/property_map.hpp> 5 6 template <class Graph, class Capacity, class Flow> 7 void print_network(Graph& G, Capacity capacity, Flow flow) 8 { 9 typedef typename boost::graph_traits<Graph>::vertex_iterator Viter; 10 typedef typename boost::graph_traits<Graph>::out_edge_iterator OutEdgeIter; 11 typedef typename boost::graph_traits<Graph>::in_edge_iterator InEdgeIter; 12 13 Viter ui, uiend; 14 for (boost::tie(ui, uiend) = boost::vertices(G); ui != uiend; ++ui) { 15 OutEdgeIter out, out_end; 16 std::cout << *ui << " "; 17 18 for(boost::tie(out, out_end) = boost::out_edges(*ui, G); out != out_end; ++out) 19 std::cout << "--(" << boost::get(capacity, *out) << ", " 20 << boost::get(flow, *out) << ")--> " << boost::target(*out,G) << " "; 21 std::cout << std::endl << " "; 22 23 InEdgeIter in, in_end; 24 for(boost::tie(in, in_end) = boost::in_edges(*ui, G); in != in_end; ++in) 25 std::cout << "<--(" << boost::get(capacity, *in) << "," << boost::get(flow, *in) << ")-- " 26 << boost::source(*in, G) << " "; 27 std::cout << std::endl; 28 } 29 } 30 31 32 int main(int , char* []) { 33 34 typedef boost::adjacency_list<boost::vecS, boost::vecS, 35 boost::bidirectionalS, boost::no_property, 36 boost::property<boost::edge_index_t, std::size_t> > Graph; 37 38 const int num_vertices = 9; 39 Graph G(num_vertices); 40 41 /* 2<----5 42 / ^ 43 / 44 V 45 0 ---->1---->3----->6--->8 46 ^ 47 / 48 V / 49 4----->7 50 */ 51 52 int capacity[] = { 10, 20, 20, 20, 40, 40, 20, 20, 20, 10 }; 53 int flow[] = { 8, 12, 12, 12, 12, 12, 16, 16, 16, 8 }; 54 55 // insert edges into the graph, and assign each edge an ID number 56 // to index into the property arrays 57 boost::add_edge(0, 1, 0, G); 58 59 boost::add_edge(1, 4, 1, G); 60 boost::add_edge(4, 7, 2, G); 61 boost::add_edge(7, 6, 3, G); 62 63 boost::add_edge(1, 3, 4, G); 64 boost::add_edge(3, 6, 5, G); 65 66 boost::add_edge(6, 5, 6, G); 67 boost::add_edge(5, 2, 7, G); 68 boost::add_edge(2, 1, 8, G); 69 70 boost::add_edge(6, 8, 9, G); 71 72 typedef boost::property_map<Graph, boost::edge_index_t>::type EdgeIndexMap; 73 EdgeIndexMap edge_id = boost::get(boost::edge_index, G); 74 75 typedef boost::iterator_property_map<int*, EdgeIndexMap, int, int&> IterMap; 76 77 print_network(G, IterMap(capacity, edge_id), IterMap(flow, edge_id)); 78 79 return 0; 80 }
example/city_visitor.cpp
1 // 2 //======================================================================= 3 // Copyright 1997, 1998, 1999, 2000 University of Notre Dame. 4 // Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek 5 // 6 // Distributed under the Boost Software License, Version 1.0. (See 7 // accompanying file LICENSE_1_0.txt or copy at 8 // http://www.boost.org/LICENSE_1_0.txt) 9 //======================================================================= 10 // 11 12 #include <boost/config.hpp> 13 #include <iostream> 14 #include <vector> 15 #include <string> 16 #include <boost/graph/adjacency_list.hpp> 17 #include <boost/graph/depth_first_search.hpp> 18 #include <boost/graph/breadth_first_search.hpp> 19 #include <boost/property_map/property_map.hpp> 20 #include <boost/graph/graph_utility.hpp> // for boost::make_list 21 22 23 /* 24 Example of using a visitor with the depth first search 25 and breadth first search algorithm 26 27 Sacramento ---- Reno ---- Salt Lake City 28 | 29 San Francisco 30 | 31 San Jose ---- Fresno 32 | 33 Los Angeles ---- Las Vegas ---- Phoenix 34 | 35 San Diego 36 37 38 The visitor has three main functions: 39 40 discover_vertex(u,g) is invoked when the algorithm first arrives at the 41 vertex u. This will happen in the depth first or breadth first 42 order depending on which algorithm you use. 43 44 examine_edge(e,g) is invoked when the algorithm first checks an edge to see 45 whether it has already been there. Whether using BFS or DFS, all 46 the edges of vertex u are examined immediately after the call to 47 visit(u). 48 49 finish_vertex(u,g) is called when after all the vertices reachable from vertex 50 u have already been visited. 51 52 */ 53 54 using namespace std; 55 using namespace boost; 56 57 58 struct city_arrival : public base_visitor<city_arrival> 59 { 60 city_arrival(string* n) : names(n) { } 61 typedef on_discover_vertex event_filter; 62 template <class Vertex, class Graph> 63 inline void operator()(Vertex u, Graph&) { 64 cout << endl << "arriving at " << names[u] << endl 65 << " neighboring cities are: "; 66 } 67 string* names; 68 }; 69 70 struct neighbor_cities : public base_visitor<neighbor_cities> 71 { 72 neighbor_cities(string* n) : names(n) { } 73 typedef on_examine_edge event_filter; 74 template <class Edge, class Graph> 75 inline void operator()(Edge e, Graph& g) { 76 cout << names[ target(e, g) ] << ", "; 77 } 78 string* names; 79 }; 80 81 struct finish_city : public base_visitor<finish_city> 82 { 83 finish_city(string* n) : names(n) { } 84 typedef on_finish_vertex event_filter; 85 template <class Vertex, class Graph> 86 inline void operator()(Vertex u, Graph&) { 87 cout << endl << "finished with " << names[u] << endl; 88 } 89 string* names; 90 }; 91 92 int main(int, char*[]) 93 { 94 95 enum { SanJose, SanFran, LA, SanDiego, Fresno, LasVegas, Reno, 96 Sacramento, SaltLake, Phoenix, N }; 97 98 string names[] = { "San Jose", "San Francisco", "Los Angeles", "San Diego", 99 "Fresno", "Las Vegas", "Reno", "Sacramento", 100 "Salt Lake City", "Phoenix" }; 101 102 typedef std::pair<int,int> E; 103 E edge_array[] = { E(Sacramento, Reno), E(Sacramento, SanFran), 104 E(Reno, SaltLake), 105 E(SanFran, SanJose), 106 E(SanJose, Fresno), E(SanJose, LA), 107 E(LA, LasVegas), E(LA, SanDiego), 108 E(LasVegas, Phoenix) }; 109 110 /* Create the graph type we want. */ 111 typedef adjacency_list<vecS, vecS, undirectedS> Graph; 112 #if defined(BOOST_MSVC) && BOOST_MSVC <= 1300 113 // VC++ has trouble with the edge iterator constructor 114 Graph G(N); 115 for (std::size_t j = 0; j < sizeof(edge_array)/sizeof(E); ++j) 116 add_edge(edge_array[j].first, edge_array[j].second, G); 117 #else 118 Graph G(edge_array, edge_array + sizeof(edge_array)/sizeof(E), N); 119 #endif 120 121 cout << "*** Depth First ***" << endl; 122 depth_first_search 123 (G, 124 visitor(make_dfs_visitor(boost::make_list(city_arrival(names), 125 neighbor_cities(names), 126 finish_city(names))))); 127 cout << endl; 128 129 /* Get the source vertex */ 130 boost::graph_traits<Graph>::vertex_descriptor 131 s = vertex(SanJose,G); 132 133 cout << "*** Breadth First ***" << endl; 134 breadth_first_search 135 (G, s, visitor(make_bfs_visitor(boost::make_list(city_arrival(names), 136 neighbor_cities(names), 137 finish_city(names))))); 138 139 return 0; 140 }