Boost.Graph是一个图的封装,在《数据结构》的教科书里,一般都会讲到数组、链表、队列、堆栈、堆、树、图论等。其中前面几个已经在C++标准库(STL)中实现了(如vector,list,stack,queue,heap等),却没有提供一个与树或图对应的实现,实在说不过去。于是这个艰巨而又光荣的任务就落到了作为标准库的预备役的Boost身上, Boost.Graph诞生啦!
Boost.Graph分成数据结构和算法两个大部分(为了便于区分,后文称"图结构"和"图算法"),其中"图结构"相当于 STL里的容器,自带了邻接表(adjacency_list)、邻接矩阵(adjacency_matrix)和CSR图(compressed_sparse_row_graph)三种。“图算法”相当于STL里的算法,就象std::sort不能用于std::list 一样,对于不同的“图结构”,“图算法”也有不同的适用范围。
“图算法”的适用范围按“概念(Concept)”划分,“图结构”根据它的结构特点被分为“可变图”、“关联图”、“双向图”、“邻接图”、“点表 图”、“边表图”、“属性图”、“可变属性图”几个概念。一种“图结构”可能支持其中的一部分概念而不支持其它的概念,于是也就决定了这种“图结构”所支 持的“图算法”。
图的概念以及对应的算法:
表格中g为图对象,u,v为源和目标顶点,e为边,iter为边的迭代器类型,p为谓词函数(或函数对象)概念 Concept | 支持函数 | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
可变图(Mutable Graph) 允许添加、删除顶点和边 |
| ||||||||||||||||||||
关联图(Incidence Graph) |
| ||||||||||||||||||||
双向图(Bidirectional Graph) |
| ||||||||||||||||||||
邻接图(Adjacency Graph) |
| ||||||||||||||||||||
点表图(Vertex List Graph) |
| ||||||||||||||||||||
边表图(Edge List Graph) |
| ||||||||||||||||||||
属性图(Property Graph) 可以为每个顶点和边加入附加属性 |
| ||||||||||||||||||||
可变属性图(Mutable Property Graph) |
|
Boost.Graph的三个图结构
邻接表(adjacency_list)
邻接表为每个顶点维护一份线性表(数组、链表等),表中的每个节点就是与该顶点相连的其它顶点(见下图)。这种方式比较方便添加删除顶点和边,扩展性比较强(所以它支持前文中所有图概念),但是查询的速度不及后面将讲的另外两个。
类声明:
adjacency_list<OutEdgeList, VertexList, Directed, VertexProperties, EdgeProperties, GraphProperties, EdgeList>
前两个模版参数(OutEdgeList,VertexList)分别表示每个顶点出边集合的数据结构和图中顶点集合的数据结构,可选的有:
参数 | 选用的数据结构 |
---|---|
vecS | std::vector |
listS | std::list |
slistS | std::slist |
setS | std::set |
multisetS | std::multiset |
hash_setS | std::hash_set |
可以根据使用方式决定选择哪种数据结构,比如经常插入删除使用listS,经常遍历使用vecS。
第三个参数(Directed)表示图的形式,可选项有:
参数 | 表示形式 |
---|---|
bidirectionalS | 表示一个既有出边也有入边的有向图 |
directedS | 表示一个只能存取出边的有向图 |
undirectedS | 表示一个无向图 |
构造函数:
- adjacency_list()
- //建立一个空的邻接表
-
- adjacency_list(vertices_size_type
n, -
const GraphProperty& p = GraphProperty()) - //建立包含n个顶点的邻接表
-
- template
<class EdgeIterator> - adjacency_list(EdgeIterator
first, EdgeIterator last, -
vertices_size_type n, -
edges_size_type m = 0, -
const GraphProperty& p = GraphProperty()) - //建立由first~last的边及包含n个顶点的邻接表
-
- template
<class EdgeIterator, class EdgePropertyIterator> - adjacency_list(EdgeIterator
first, EdgeIterator last, -
EdgePropertyIterator ep_iter, -
vertices_size_type n, -
vertices_size_type m = 0, -
const GraphProperty& p = GraphProperty()) - //同上,顺便给边的属性赋值
成员方法:
- void
clear() - //清空边和顶点
-
- void
swap(adjacency_list& x) - //交换
定义于头文件:<boost/graph/adjacency_list.hpp>
邻接矩阵(adjacency_matrix)
邻接矩阵用一个[顶点数*顶点数]的矩阵存放连接各顶点的边,如图所示:
类声明:
adjacency_matrix<Directed, VertexProperty, EdgeProperty, GraphProperty, Allocator>
邻接矩阵对边的查找、添加、删除都非常快,缺点是占用内存空间比较大,是顶点数的平方,而且对顶点数的添加删除就不行了。所以比较适合于顶点少边多而且不须要添加删除顶点的场合。
构造函数:
- adjacency_matrix()
- //建立一个空的邻接矩阵
-
- adjacency_matrix(vertices_size_type
n, -
const GraphProperty& p = GraphProperty()) - //建立包含n个顶点的邻接矩阵
定义于头文件:<boost/graph/adjacency_matrix.hpp>
CSR图(compressed_sparse_row_graph)
这种图以CSR的格式存储邻接矩阵,CSR格式是一种矩阵存储形式,它由三个数组组成(假定取名 values,columns,rowIndex):数组 values存放矩阵中的有效数据,数组columns存放数组values中各数据于矩阵中所在的列,数组rowIndex存放矩阵中各行的第一个有效 数据在数组values上的索引位。比如下面的矩阵:
则CSR的三个数组为:
values | = | (1 | -1 | -3 | -2 | 5 | 4 | 6 | 4 | -4 | 2 | 7 | 8 | -5) |
columns | = | (1 | 2 | 4 | 1 | 2 | 3 | 4 | 5 | 1 | 3 | 4 | 2 | 5) |
rowIndex | = | (1 | 4 | 6 | 9 | 12 | 14) | | | | | | | |
这种存储形式显然更适用于稀疏矩阵,而且增加删除边和顶点都要重建三个数组。所以在Graph库 中,compressed_sparse_row_graph不属于可变图 (Mutable Graph)概念,即前文中可变图概念的函数都不能用。它适于图比较大而且图结构不会改变的场合。
构造函数:
构造函数与邻接表相同,成员方法中没有clear、swap等修改图结构的方法。并且不支持可变图概念的函数,对于图结构(边与顶点的关系)是只读的,不过属性是可以存取的。
头文件定义于<boost/graph/compressed_sparse_row_graph.hpp>示例:生成一个图,并显示出图中所有顶点和边
假设我们要生成下面这个图这是一个有向图
顶点是:A B C D E F
所有的边:
B --> C B --> F C --> C C --> A D --> E E --> D F --> A
由于Boost.Graph的顶点只能使用整型(放心,后文会说怎样把自定义的数据绑定到节点和边上的),所以我们把A~F依次按0~5编号。这样,所有的顶点和边就是:
0,1,2,3,4,5 1 --> 2 1 --> 5 2 --> 2 2 --> 0 3 --> 4 4 --> 3 5 --> 0
代码:
- #include
<iostream> - #include
<utility> - #include
<boost/graph/graph_traits.hpp> - #include
<boost/graph/adjacency_list.hpp> -
- using
namespace std; - using
namespace boost; -
- int
main(int argc, char* argv[]) - {
-
// 定义图类型,使用vector存放顶点和边,有向图 -
typedef adjacency_list<vecS, vecS, directedS> graph_t; -
-
// 产生图对象,有6个顶点 -
graph_t g(6); -
-
// 加入边 -
add_edge(1,2,g); -
add_edge(1,5,g); -
add_edge(2,2,g); -
add_edge(2,0,g); -
add_edge(3,4,g); -
add_edge(4,3,g); -
add_edge(5,0,g); -
-
// 显示所有的顶点 -
// 顶点迭代器类型 -
typedef graph_traits<graph_t>::vertex_iterator vertex_iter; -
// 得到所有顶点,vrange中的一对迭代器分别指向第一个顶点和最后的一个顶点之后。 -
std::pair<vertex_iter, vertex_iter> vrange = vertices(g); -
for(vertex_iter itr=vrange.first; itr!=vrange.second; ++itr) -
cout << *itr << endl; -
-
// 显示所有的边 -
// 边迭代器类型 -
typedef graph_traits<graph_t>::edge_iterator edge_iter; -
// 得到所有边,erange中的一对迭代器分别指向第一条边和最后的一条边之后 -
std::pair<edge_iter, edge_iter> erange = edges(g); -
for(edge_iter itr=erange.first; itr!=erange.second; ++itr) -
cout << source(*itr,g) << "-->" << target(*itr,g) << endl; -
-
return 0; - };
编译运行,显示结果应该和之前说的顶点和边一致。可以修改本例中的图类型试试,如typedef adjacency_matrix<directedS> graph_t;
为了便于理解,上面的代码没有使用任何技巧来简化代码。实际上在官方的Graph实例中,使用了enum来代替代表顶点的整数,使用boost::tie来简化for循环,理解了上面的代码后,再看这个优化版的代码就好看多了:
- #include
<iostream> - #include
<utility> - #include
<boost/graph/graph_traits.hpp> - #include
<boost/graph/adjacency_list.hpp> -
- using
namespace std; - using
namespace boost; -
- int
main(int argc, char* argv[]) - {
-
// 定义图类型 -
typedef adjacency_list<vecS, vecS, directedS> graph_t; -
-
// 顶点 -
enum{A,B,C,D,E,F,N}; //N正好等于顶点数 -
// 边 -
typedef std::pair<int, int> edge_t; -
edge_t edgelist[]={ -
edge_t(B,C), -
edge_t(B,F), -
edge_t(C,C), -
edge_t(C,A), -
edge_t(D,E), -
edge_t(E,D), -
edge_t(F,A) -
}; -
-
// 产生图对象,输入边迭代器和N个顶点 -
graph_t g(edgelist, edgelist+7, N); -
-
// 显示所有的顶点 -
graph_traits<graph_t>::vertex_iterator vitr, vitr_end; -
for(tie(vitr, vitr_end) = vertices(g); -
vitr != vitr_end; -
++vitr) -
cout << *vitr << endl; -
-
// 显示所有的边 -
graph_traits<graph_t>::edge_iterator eitr, eitr_end; -
for(tie(eitr, eitr_end) = edges(g); -
eitr != eitr_end; -
++eitr) -
cout << source(*eitr,g) << "-->" << target(*eitr,g) << endl; -
-
return 0; - }
在本例中,有一个graph_traits类是前文中没有提到的。它是用于提取出与图相关的类型的,它的声明为:
- template
<typename Graph> -
struct graph_traits { -
typedef typename Graph::vertex_descriptor vertex_descriptor; -
typedef typename Graph::edge_descriptor edge_descriptor; -
typedef typename Graph::adjacency_iterator adjacency_iterator; -
typedef typename Graph::out_edge_iterator out_edge_iterator; -
typedef typename Graph::in_edge_iterator in_edge_iterator; -
typedef typename Graph::vertex_iterator vertex_iterator; -
typedef typename Graph::edge_iterator edge_iterator; -
-
typedef typename Graph::directed_category directed_category; -
typedef typename Graph::edge_parallel_category edge_parallel_category; -
typedef typename Graph::traversal_category traversal_category; -
-
typedef typename Graph::vertices_size_type vertices_size_type; -
typedef typename Graph::edges_size_type edges_size_type; -
typedef typename Graph::degree_size_type degree_size_type; -
};
其中每个成员的定义是:
成员 | 说明 |
---|---|
vertex_descriptor | 用于区分图中各顶点的对象类型 |
edge_descriptor | 用于区分图中各边的对象类型 |
adjacency_iterator | 用于遍历邻接点的迭代器 |
out_edge_iterator | 用于遍历出边的迭代器 |
in_edge_iterator | 用于遍历入边的迭代器 |
vertex_iterator | 用于遍历图中的顶点的迭代器 |
edge_iterator | 用于遍历图中边的迭代器 |
directed_category | 指出该图是有向图还是无向图 |
edge_parallel_category | 指出该图是否允许输入平行边(可插入平等边allow_parallel_edge_tag或可自动去除平行边disallow_parallel_edge_tag) |
traversal_category | 指出怎样遍历图中的顶点,可选的有incidence_graph_tag, adjacency_graph_tag, bidirectional_graph_tag, vertex_list_graph_tag, edge_list_graph_tag, vertex_and_edge_list_graph_tag, adjacency_matrix_tag.你也可以通过继承上面这些标记来建立自己的标记 |
vertices_size_type | 用于表示图中顶点数的无符号整型 |
edge_size_type | 用于表示图中边数的无符号整型 |
degree_size_type | 用于表示顶点度数的无符号整型 |
用法:
- //获得边类型
- typedef
boost::graph_traits<G>::edge_descriptor edge_t; - //获得顶点类型
- typedef
boost::graph_traits<G>::vertex_descriptor vertex_t; - //获得出边迭代器
- typedef
boost::graph_traits<G>::out_edge_iterator oedge_t;