图论是一种数学抽象,它对于解决多种计算机科学问题是非常有用的,Boost.Graph提供了一个基于图论的通用编程接口.
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;