Boost Graph

图论是一种数学抽象,它对于解决多种计算机科学问题是非常有用的,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)
允许添加、删除顶点和边
std::pair<edge_descriptor,bool>
    add_edge(u, v, g);
加入一条边,返回加入后的边以及是否成功。
(若边已存在且不允许并行边,则返回已存在的边)
void remove_edge(u, v, g); 删除连接u和v的边
void remove_edge(e, g); 同上
void remove_edge(iter, g); 同上
void remove_edge_if(p, g); 删除所有谓词p为true的边
void remove_out_edge_if(u, p, g); 删除所有谓词p为ture的出边
void remove_in_edge_if(u, p, g); 删除所有谓词p为trre的入边
vertex_descriptor add_vertex(g); 添加一个顶点,返回加入的顶点
void clear_vectex(u, g); 删除u上的所有边
void remove_vectex(u, g); 删除顶点u,注意删除顶点之前要确保该顶点没有边与之连接,否则会出现未定义的行为。典型的用法是在 remove_vectex前调用clear_vectex。
关联图(Incidence Graph)
vertex_descriptor source(e, g); 返回e边上的起源顶点
vertex_descriptor target(e, g); 返回e边上的目标顶点
std::pair<out_edge_iterator, out_edge_iterator>
    out_edges(u, g);
以边迭代器对的形式返回顶点u上的所有出边
degree_size_type out_degree(u, g); 返回顶点u的出度
双向图(Bidirectional Graph)
std::pair<in_edge_iterator, in_edge_iterator>
    in_edges(v, g)
以边迭代器对的形式返回顶点v上的所有入边
degree_size_type in_degree(v, g) 返回顶点v的入度
degree(v, g) 返回入度+出度
邻接图(Adjacency Graph)
std::pair<adjacency_iterator, adjacency_iterator>
    adjacent_vertices(v, g)
以顶点迭代器范围的形式返回顶点v上的所有邻接点。
点表图(Vertex List Graph)
std::pair<vertex_iterator, vertex_iterator>
    vertices(g)
以顶点迭代器范围的形式返回所有顶点
vertices_size_type num_vertices(g) 返回顶点数
边表图(Edge List Graph)
std::pair<edge_iterator, edge_iterator>
    edges(g);
以边迭代器的形式返回所有边
edges_size_type num_edges(g); 返回边数
vertex_descriptor source(e, g); 返回e边上的起源顶点
vertex_descriptor target(e, g); 返回e边上的目标顶点
属性图(Property Graph)
可以为每个顶点和边加入附加属性
get(p, g) 得到图g的属性p的值
get(p, g, x) 得到顶点或边x的属性p的值
put(p, g, x, v) 设置属性值
可变属性图(Mutable Property Graph)
std::pair<edge_descriptor, bool>
    add_edge(u, v, ep, g)
加入边,顺便给属性ep赋值
vertex_descriptor add_vertex(vp, g) 加入顶点,顺便给属性vp赋值

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 表示一个无向图
构造函数:
  1. adjacency_list()
  2. //建立一个空的邻接表
  3.  
  4. adjacency_list(vertices_size_type n,
  5.                const GraphProperty& GraphProperty())
  6. //建立包含n个顶点的邻接表
  7.  
  8. template <class EdgeIterator>
  9. adjacency_list(EdgeIterator first, EdgeIterator last,
  10.                vertices_size_type n,
  11.                edges_size_type 0,
  12.                const GraphProperty& GraphProperty())
  13. //建立由first~last的边及包含n个顶点的邻接表
  14.  
  15. template <class EdgeIterator, class EdgePropertyIterator>
  16. adjacency_list(EdgeIterator first, EdgeIterator last,
  17.                EdgePropertyIterator ep_iter,
  18.                vertices_size_type n,
  19.                vertices_size_type 0,
  20.                const GraphProperty& GraphProperty())
  21. //同上,顺便给边的属性赋值
成员方法:
  1. void clear()
  2. //清空边和顶点
  3.  
  4. void swap(adjacency_list& x)
  5. //交换

定义于头文件:<boost/graph/adjacency_list.hpp>

邻接矩阵(adjacency_matrix)

邻接矩阵用一个[顶点数*顶点数]的矩阵存放连接各顶点的边,如图所示:


类声明:
adjacency_matrix<Directed, VertexProperty, EdgeProperty, GraphProperty, Allocator>

邻接矩阵对边的查找、添加、删除都非常快,缺点是占用内存空间比较大,是顶点数的平方,而且对顶点数的添加删除就不行了。所以比较适合于顶点少边多而且不须要添加删除顶点的场合。

构造函数:
  1. adjacency_matrix()
  2. //建立一个空的邻接矩阵
  3.  
  4. adjacency_matrix(vertices_size_type n,
  5.                const GraphProperty& GraphProperty())
  6. //建立包含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

代码:

  1. #include <iostream>
  2. #include <utility> 
  3. #include <boost/graph/graph_traits.hpp>
  4. #include <boost/graph/adjacency_list.hpp>
  5.  
  6. using namespace std;
  7. using namespace boost;
  8.  
  9. int main(int argc, charargv[])
  10. {
  11.     // 定义图类型,使用vector存放顶点和边,有向图
  12.     typedef adjacency_list<vecS, vecS, directedS> graph_t;
  13.  
  14.     // 产生图对象,有6个顶点
  15.     graph_t g(6);
  16.  
  17.     // 加入边
  18.     add_edge(1,2,g);
  19.     add_edge(1,5,g);
  20.     add_edge(2,2,g);
  21.     add_edge(2,0,g);
  22.     add_edge(3,4,g);
  23.     add_edge(4,3,g);
  24.     add_edge(5,0,g);
  25.  
  26.     // 显示所有的顶点
  27.     // 顶点迭代器类型
  28.     typedef graph_traits<graph_t>::vertex_iterator vertex_iter;
  29.     // 得到所有顶点,vrange中的一对迭代器分别指向第一个顶点和最后的一个顶点之后。
  30.     std::pair<vertex_iter, vertex_iter> vrange vertices(g);
  31.     for(vertex_iter itr=vrange.first; itr!=vrange.second; ++itr)
  32.         cout << *itr << endl;
  33.    
  34.     // 显示所有的边
  35.     // 边迭代器类型
  36.     typedef graph_traits<graph_t>::edge_iterator edge_iter;
  37.     // 得到所有边,erange中的一对迭代器分别指向第一条边和最后的一条边之后
  38.     std::pair<edge_iter, edge_iter> erange edges(g);
  39.     for(edge_iter itr=erange.first; itr!=erange.second; ++itr)
  40.         cout << source(*itr,g) << "-->" << target(*itr,g) << endl;
  41.  
  42.     return 0;
  43. };

编译运行,显示结果应该和之前说的顶点和边一致。可以修改本例中的图类型试试,如typedef adjacency_matrix<directedS> graph_t;

为了便于理解,上面的代码没有使用任何技巧来简化代码。实际上在官方的Graph实例中,使用了enum来代替代表顶点的整数,使用boost::tie来简化for循环,理解了上面的代码后,再看这个优化版的代码就好看多了:

  1. #include <iostream>
  2. #include <utility>
  3. #include <boost/graph/graph_traits.hpp>
  4. #include <boost/graph/adjacency_list.hpp>
  5.  
  6. using namespace std;
  7. using namespace boost;
  8.  
  9. int main(int argc, charargv[])
  10. {
  11.     // 定义图类型
  12.     typedef adjacency_list<vecS, vecS, directedS> graph_t;
  13.  
  14.     // 顶点
  15.     enum{A,B,C,D,E,F,N}; //N正好等于顶点数
  16.     // 
  17.     typedef std::pair<intintedge_t;
  18.     edge_t edgelist[]={
  19.         edge_t(B,C),
  20.         edge_t(B,F),
  21.         edge_t(C,C),
  22.         edge_t(C,A),
  23.         edge_t(D,E),
  24.         edge_t(E,D),
  25.         edge_t(F,A)
  26.     };
  27.  
  28.     // 产生图对象,输入边迭代器和N个顶点
  29.     graph_t g(edgelist, edgelist+7, N);
  30.  
  31.     // 显示所有的顶点
  32.     graph_traits<graph_t>::vertex_iterator vitr, vitr_end;
  33.     for(tie(vitr, vitr_end) vertices(g);
  34.         vitr != vitr_end;
  35.         ++vitr)
  36.         cout << *vitr << endl;
  37.    
  38.     // 显示所有的边
  39.     graph_traits<graph_t>::edge_iterator eitr, eitr_end;
  40.     for(tie(eitr, eitr_end) edges(g);
  41.         eitr != eitr_end;
  42.         ++eitr)
  43.         cout << source(*eitr,g) << "-->" << target(*eitr,g) << endl;
  44.  
  45.     return 0;
  46. }

在本例中,有一个graph_traits类是前文中没有提到的。它是用于提取出与图相关的类型的,它的声明为:

  1. template <typename Graph>
  2.   struct graph_traits {
  3.     typedef typename Graph::vertex_descriptor      vertex_descriptor;
  4.     typedef typename Graph::edge_descriptor        edge_descriptor;
  5.     typedef typename Graph::adjacency_iterator     adjacency_iterator;
  6.     typedef typename Graph::out_edge_iterator      out_edge_iterator;
  7.     typedef typename Graph::in_edge_iterator       in_edge_iterator;
  8.     typedef typename Graph::vertex_iterator        vertex_iterator;
  9.     typedef typename Graph::edge_iterator          edge_iterator;
  10.  
  11.     typedef typename Graph::directed_category      directed_category;
  12.     typedef typename Graph::edge_parallel_category edge_parallel_category;
  13.     typedef typename Graph::traversal_category     traversal_category;
  14.  
  15.     typedef typename Graph::vertices_size_type     vertices_size_type;
  16.     typedef typename Graph::edges_size_type        edges_size_type;
  17.     typedef typename Graph::degree_size_type       degree_size_type;
  18.   };
其中每个成员的定义是:
成员说明
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 用于表示顶点度数的无符号整型
用法:
  1. //获得边类型
  2. typedef boost::graph_traits<G>::edge_descriptor edge_t;
  3. //获得顶点类型
  4. typedef boost::graph_traits<G>::vertex_descriptor vertex_t;
  5. //获得出边迭代器
  6. typedef boost::graph_traits<G>::out_edge_iterator oedge_t;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值