Boost Graph Library

关于计算的公理表达通常颇具争论。然而,现代计算最重要的理论支柱之一的图论并不是这些公理表达之一。无数工程领域(从设计路由器和网络到设计构成移动设备核心的芯片)都是图论的应用。

作为 C++ 应用程序软件开发人员,我们通常需要直接将实际工程问题转化成一个等价的图论问题。如果有一个可靠的基于 C++ 的通用图库,就可以帮助我们实现这个转换,这样的图库显然非常受欢迎:Boost Graph Library (BGL) 将填补这项空白。

在本文中,您首先将创建一个无向图,然后按照常规的遍历例程创建一个有向图。随后,您可以应用一些经典算法,所有算法都不需要添加大量代码。这就是 BGL 的神奇之处。

下载和安装

BGL 可从 Boost 网站免费下载(请参阅 参考资料,获取有关的链接)。BGL 是一个仅有头文件的库,因此,以后在应用程序代码中使用该库时,需要在源代码中包含相关的头文件。但是 BGL 需要这个序列化库来进行链接 。以下是一个典型的命令行格式:

 root# g++ test_bgl.cpp I/usr/boost/boost_1_44/ -L/usr/boost/boost_1_44/lib 

如果要试验本文中的代码,您需要安装 Boost 1.44 版本。

回页首

邻接表(Adjacency lists)

任何图实现的头文件中都有一个邻接表 (adjacency list) 或邻接矩阵。清单 1 显示了在 Boost 头文件 adjacency_list.hpp 中如何声明邻接表。

清单 1. 在 Boost 中声明一个邻接表

                template <class OutEdgeListS = vecS,
// a Sequence or an AssociativeContainer class VertexListS = vecS,
// a Sequence or a RandomAccessContainer class DirectedS = directedS,
class VertexProperty = no_property,
class EdgeProperty = no_property,
class GraphProperty = no_property,
class EdgeListS = listS>
class adjacency_list {  };

为了简便起见,我们将重点放在前三个模板参数。

OutEdgeList 模板参数决定了将用于存储边列表( edge-list)信息的容器类型。回顾一下图论基础知识就可以知道,对于有向图,只具有入边的那些顶点都有一个对应的空邻接表。默认值被设置为 vecS,该值对应于 std::vectorVertexList 模板参数决定了用于表示该图顶点列表的容器类型,默认值同样被设置为 std::vectorDirectedS 模板参数根据提供的值是 directedS 还是 undirectedS来确定该图是有向图还是无向图。

在 BGL 中创建一个图

在声明邻接表的同时,清单 2 中的代码在 BGL 中创建了一个简单的无向图,边信息将存储在 std::list 中,顶点信息存储在std::vector 中。

清单 2. 创建一个无向图

                #include <boost/graph/adjacency_list.hpp>
using namespace boost;
typedef boost::adjacency_list<listS, vecS, undirectedS> mygraph;
int main()
{ mygraph g;add_edge (0, 1, g);add_edge (0, 3, g);add_edge (1, 2, g);add_edge (2, 3, g);} 

在清单 2 中,在没有在构造函数中提供任何顶点或边信息的情况下创建了图 g。在运行的时候,会使用诸如 add_edge 和 add_vertex之类的帮助函数创建边和顶点。add_edge 函数,顾名思义:在一个图的两个顶点之间添加一条边。清单 2 中的代码执行结束后,图 g应该有 4 个顶点,分别标记为 0、1、2 和 3,顶点 1 与顶点 0 和顶点 2 连接,等等。

遍历图

遍历图涉及到使用 vertex_iterator 和 adjacency_iterator 类。前者遍历图的所有顶点,后者遍历相应邻接表。清单 3 展示了该代码。

清单 3. 使用 BGL 遍历图

                #include <boost/graph/adjacency_list.hpp>
using namespace boost;
typedef boost::adjacency_list<listS, vecS, undirectedS> mygraph;
int main()
{ mygraph g; add_edge (0, 1, g); add_edge (0, 3, g);add_edge (1, 2, g);add_edge (2, 3, g);mygraph::vertex_iterator vertexIt, vertexEnd;mygraph::adjacency_iterator neighbourIt, neighbourEnd;tie(vertexIt, vertexEnd) = vertices(g);for (; vertexIt != vertexEnd; ++vertexIt) { cout << *vertexIt << " is connected with "; tie(neighbourIt, neighbourEnd) = adjacent_vertices(*vertexIt, g); for (; neighbourIt != neighbourEnd; ++neighbourIt) cout << *neighbourIt << " "; cout << "\n"; }
} 

创建一个有向图

要创建一个有向图,只需要将 清单 3 中的图类型修改为directedS

 #include <boost/graph/adjacency_list.hpp> using namespace boost; typedef boost::adjacency_list<listS, vecS, directedS> mygraph;int main(){ mygraph g; add_edge (0, 1, g);add_edge (0, 3, g); add_edge (1, 2, g); add_edge (2, 3, g); //  Same as Listing 3 } 

帮助函数 vertices 返回一个 std::pair<vertex_iterator 和vertex_iterator>,前者指向图的第一个顶点。结果存储在多元组 tie (vertexIt, vertexEnd) 中,随后会使用 vertexIt 遍历该图。同样,帮助函数 adjacent_vertices 将会返回 std::pair<adjacency_iterator, adjacency_iterator>,第一个 adjacency_iterator 指向邻接表中的第一个元素。

配置一个邻接表

BGL 的优势之一是它是高度可配置的。BGL 允许您使用下列任何选择器类型来配置顶点集和边集合,这些选择器类型都是在头文件中定义的;您需要做的就是在声明图时使用它们:

  • vecS 选择 std::vector
  • lists 适用于 std::list
  • slistS 选择 std::slist
  • setS 选择 std::set
  • multiSetS 选择 std::multiset
  • hash_setS 选择 std::hash_set

如果代码中可能有很多顶点插入操作,但删除操作却不是太多,那么 VertexList 可能是 vecS 或 listS。除了向量需要再分配之外,push_back 通常是一个常量。如果您要执行很多插入和删除操作,那么与 vecS 相比,listS 是一个不错的选择,因为从向量中删除一个元素通常是代价昂贵的,而且很耗时。

回页首

创建无向图的另一种方法

如果不使用基于邻接表的方法创建无向图,那么您可以使用 BGL 提供的 undirected_graph 类(在 undirected_graph.hpp 中定义)创建该图。但是,该类在内部使用了一个邻接表,而且使用基于邻接表的图通常会提供更大的灵活性。清单 4 展示了有关代码。

清单 4. 使用 undirected_graph.hpp 创建一个无向图

                #include <undirected_graph.hpp> #include <iostream> using namespace boost;
using namespace std;
int main( )
{ undirected_graph<>g;undirected_graph<>:vertex_descriptor u = g.add_vertex();undirected_graph<>:vertex_descriptor v = g.add_vertex();undirected_graph<>:vertex_descriptor w = g.add_vertex();undirected_graph<>:vertex_descriptor x = g.add_vertex();add_edge(u, v, g); add_edge(u, w, g); add_edge(u, x, g);cout << "Degree of u: " << degree(u, g);return 0;
} 

在清单 4 中,我使用 add_vertex 向图中添加独立的顶点,使用 add_edge 向图中添加边。最后,通过调用 degree 方法得出单个顶点的度数。清单 5 提供了 undirected_graph 的声明和定义(来自 Boost 源代码)。

清单 5. 解密 BGL undirected_graph

                template < typename VertexProp = no_property,
typename EdgeProp = no_property,
typename GraphProp = no_property> class undirected_graph
{ //  public: typedef adjacency_list<listS,listS, undirectedS,vertex_property,edge_property,GraphProp,listS> graph_type; private: graph_type m_graph; //
}; 

回页首

跟踪一个顶点的入边(in-edges)和出边(out-edges)

可以使用 out_edges 帮助函数访问一个顶点的出边,使用 in_edges 访问入边。BGL 的优势之一就是可以使用 cout 直接输出一条边,显示这条边连接的顶点。清单 6 展示了相关代码。

清单 6. 遍历有向图的顶点

                #include <boost/graph/adjacency_list.hpp> using namespace boost;
typedef boost::adjacency_list<listS, vecS, directedS> mygraph;
int main()
{ mygraph g; add_edge (0, 1, g);add_edge (0, 3, g);add_edge (1, 2, g);add_edge (2, 3, g);mygraph::vertex_iterator vertexIt, vertexEnd;mygraph::in_edge_iterator inedgeIt, inedgeEnd;mygraph::in_edge_iterator outedgeIt, outedgeEnd; tie(vertexIt, vertexEnd) = vertices(g);

tie(vertexIt, vertexEnd) = vertices(g);
 for (; vertexIt != vertexEnd; ++vertexIt)
{
cout << "incoming edges for " << *vertexIt << ": ";
boost::tie(inedgeIt, inedgeEnd) = in_edges(*vertexIt, g);
for(; inedgeIt != inedgeEnd; ++inedgeIt)
{
cout << *inedgeIt << " ";
}
cout << "\n";
}

tie(vertexIt, vertexEnd) = vertices(g);
for (; vertexIt != vertexEnd; ++vertexIt)
{
cout << "out-edges for " << *vertexIt << ": " ;
boost::tie(outedgeIt, outedgeEnd) = out_edges(*vertexIt, g); // Similar to incoming edges
for (; outedgeIt != outedgeEnd; ++outedgeIt)
{
cout << *outedgeIt << " ";
}
cout << endl;
}

} 

编译清单 6 中的顶点将会出现错误。要修复该错误,只需在 mygraph 声明中使用 bidirectionalS 代替 directedS。在 BGL 中使用directedS 标签时,只允许您使用 out_edges 帮助函数以及相关遍历。使用 in_edges 需要将图的类型更改为 bidirectionalS,尽管该图或多或少仍然是一个有向图。

注意:使用 in_edges 会产生额外的空间开销(与使用 directedS 相比,每条边成本增加了一倍),所以要确保该成本是您可以承受的。

回页首

一些有用的 BGL 函数

现在,我们来看一些 BGL 提供的之前我们并没有讨论过的重要实用函数。

您可以使用下列函数进行图访问:

  • std::pair<edge_iterator, edge_iterator> edges(const adjacency_list& g)返回图 g 中边的相对应迭代程序对
  • vertices_size_type num_vertices(const adjacency_list& g) 返回图 g 中顶点的数量
  • edges_size_type num_edges(const adjacency_list& g)返回图 g 中边的数量
  • vertex_descriptor source(edge_descriptor e, const adjacency_list& g)返回一条边的源顶点
  • vertex_descriptor target(edge_descriptor e, const adjacency_list& g)返回一条边的目标顶点
  • degree_size_type in_degree(vertex_descriptor u, const adjacency_list& g)返回一个顶点的入度 (in-degree)
  • degree_size_type out_degree(vertex_descriptor u, const adjacency_list& g)返回一个顶点的出度 (out-degree)

清单 7 显示了执行大量图访问的代码。

清单 7. 使用 BGL 进行图访问

                // usual typedefs here, refer to previous listings
int main()
{ mygraph g; add_edge (0, 1, 8, g);add_edge (0, 3, 18, g);add_edge (1, 2, 20, g);add_edge (2, 3, 2, g);add_edge (3, 1, 1, g);add_edge (1, 3, 7, g);cout << "Number of edges: " << num_edges(g) << "\n";cout << "Number of vertices: " << num_vertices(g) << "\n";mygraph::vertex_iterator vertexIt, vertexEnd; tie(vertexIt, vertexEnd) = vertices(g);for (; vertexIt != vertexEnd; ++vertexIt) { std::cout << "in-degree for " << *vertexIt << ": " << in_degree(*vertexIt, g) << "\n";std::cout << "out-degree for " << *vertexIt << ": " << out_degree(*vertexIt, g) << "\n"; } mygraph::edge_iterator edgeIt, edgeEnd; tie(edgeIt, edgeEnd) = edges(g);for (; edgeIt!= edgeEnd; ++edgeIt) { std::cout << "edge " << source(*edgeIt, g) << "-->" << target(*edgeIt, g) << "\n"; }
} 

您可以使用下列代码来修改图:

  • void remove_edge(vertex_descriptor u, vertex_descriptor v, adjacency_list& g)从图 g 中删除一条边
  • void remove_edge(edge_descriptor e, adjacency_list& g)从图 g 中删除一条边
  • void clear_vertex(vertex_descriptor u, adjacency_list& g) 删除顶点 u 的所有边
  • void clear_out_edges(vertex_descriptor u, adjacency_list& g)删除有向图 g 中顶点 u 的所有出边(不适用于无向图)
  • void clear_in_edges(vertex_descriptor u, adjacency_list& g) 删除有向图 g 中顶点 u 的所有入边(不适用于无向图)
  • void remove_vertex(vertex_descriptor u, adjacency_list& g) 从图 g 中删除一个顶点(如果已使用clear_vertex 或其他适当函数删除与该定顶点相关的所有边。)

回页首

使用 BGL 创建一个有向加权图

现在,您应该已经对有向图有所了解,下一个逻辑任务是使用 BGL 创建一个加权有向图。回顾一下 清单 1 中的邻接表声明,您会发现一个名为 EdgeProperty 的模板参数。您可以用这个模板参数来构造有向加权图。

property 是一个可分配给顶点和边的参数。您可以使用一个标签名和一个与 property 相关的类型来定义该属性。BGL 有几个可用的标签名,其中包括 edge_weight_t 和 vertex_name_t。例如,要在图的顶点中存储标签名,可以将一个 property 定义为 typedef property<vertex_name_t, std::string> VertexNameProperty,然后将该属性传递给图的模板声明中的 VertexProperty 参数。

这是一个边权重的 property 声明:

 typedef property<edge_weight_t, int> EdgeWeightProperty; 

既然已经创建了 EdgeWeightProperty,那么现在稍微调整一下 mygraph 定义:

 typedef boost::adjacency_list<listS,vecS, directedS,no_property,EdgeWeightProperty> mygraph; 

最后,如果向图中添加边,则需要使用新加载的 add_edge,并接受权重作为第 3 个参数。清单 8 提供了完整的代码。

清单 8. 创建一个加权有向图

                #include <boost/graph/adjacency_list.hpp> using namespace boost;typedef property<edge_weight_t, int> EdgeWeightProperty;typedef boost::adjacency_list<listS, vecS, directedS, no_property,EdgeWeightProperty > mygraph;int main()
{ mygraph g;add_edge (0, 1, 8, g);add_edge (0, 3, 18, g);add_edge (1, 2, 20, g);add_edge (2, 3, 2, g);add_edge (3, 1, 1, g);add_edge (1, 3, 7, g);} 

回页首

BGL 中的最小生成树(spanning tree)

BGL 最精彩地方的就是有大量可用于图的预定义算法:Kruskal、Prim、Dijkstra 等,凡是您说得出的,BGL 都有。修改 清单 8 中的代码,从而拥有一个具有加权边的无向图,然后使用 Kruskal 算法得到最小生成树,此时您就会明白我的意思了。BGL 将每个算法放在不同的头文件中,因此,要使用 Kruskal 算法,必须包含 boost/graph/kruskal_min_spanning_tree.hpp 头文件。清单 9 展示了相关代码。

清单 9. 使用 Kruskal 算法得到最小生成树

                #include <boost/graph/adjacency_list.hpp> //  typedef boost::adjacency_list<listS,vecS, directedS, no_property, EdgeWeightProperty > mygraph;typedef mygraph::edge_descriptor Edge;int main()
{ mygraph g;add_edge (0, 1, 8, g);add_edge (0, 3, 18, g);add_edge (1, 2, 20, g);add_edge (2, 3, 2, g);add_edge (3, 1, 1, g);add_edge (1, 3, 7, g);std::list < Edge > spanning_tree;kruskal_minimum_spanning_tree (g, std::back_inserter(spanning_tree));for (std::list < Edge >::iterator ei = spanning_tree.begin(); ei != spanning_tree.end();++ei){ cout << *ei << " "; } cout << "\n";
} 

函数 kruskal_minimum_spanning_tree 在后台展示了它的神奇之处。它将图和迭代程序纳入存储边的容器中。请注意,spanning_tree 声明:我在这里使用了一个列表,但它可以是任何对象,比如一个顶点。BGL 所关心的是第二个参数必须是 Standard Template Library (STL) 输出的迭代程序类型。

回页首

使用 BGL 进行深度优先搜索

宽度优先搜索和深度优先搜索是图遍历的关键,BGL 为这些操作提供了大量支持。需要包含的相关头文件是 boost/graph/depth_first_search.hpp;相关例程是 depth_first_search。BGL 提供多个 depth_first_search 接口;所有接口都需要将所谓的访客对象传递给该方法。

BGL 中的访客(visitor) 在 STL 中充当仿函数角色,除此之外,它还可以做很多事情。访客没有 operator() 之类的单个方法,但可以灵活地定义几种方法,比如 initialize_indexstart_indexdiscover_index 和 examine_edge。毫不夸张地说,BGL 通过提供这些 hook 函数可以让您定制 DFS。首先我们看一个使用 DFS 的样例代码(参见 清单 10)。

清单 10. 使用 BGL 的 DFS 

                #include <boost/graph/adjacency_list.hpp>#include <boost/graph/depth_first_search.hpp>#include <iostream>using namespace std;using namespace boost;typedef property<edge_weight_t, int>EdgeWeightProperty; typedef boost::adjacency_list< listS, vecS, undirectedS, no_property, EdgeWeightProperty>mygraph;class custom_dfs_visitor : public boost::default_dfs_visitor { public: template < typename Vertex, typename Graph >void discover_vertex(Vertex u, const Graph & g)const { std::cout << "At " << u << std::endl; }template < typename Edge, typename Graph >void examine_edge(Edge e, const Graph& g) const { std::cout << "Examining edges " << e << std::endl;}
};
int main()
{mygraph g; add_edge (0, 1, 8, g);add_edge (0, 3, 18, g);add_edge (1, 2, 20, g);add_edge (2, 3, 2, g);add_edge (3, 1, 1, g);add_edge (1, 3, 7, g);custom_dfs_visitor vis;depth_first_search(g, visitor(vis));
} 

清单 10 声明了一个名为 custom_dfs_visitor 的类,这个类定义了两个 hook 函数:discover_vertex 和 examine_edges。前者在第一次遇到顶点时调用,后者在找到该顶点之后在该顶点的每个出边上调用。

因此,如果在访客 (vis) 中将 vis 的类型设置为 boost_default_visitor,会发生什么呢?是的,您猜对了:什么都不会显示。表 1显示了 BGL 提供的 hook 函数的一个简单概述。

表 1. 使用 DFS 进行遍历的 BGL hook 函数

Hook 函数 用途
start_vertex(u, g) 在开始遍历之前调用源顶点
discover_vertex(u, g) 第一次调用顶点时调用
finish_vertex(u, g) 如果 u 是一个树的根节点,则在调用该树上其他所有元素之后调用 finish_vertex。如果 u 是一个叶子节点,则在完成 u 所有出边的检查之后调用该方法。
examine_edge(u, g) 找到顶点 u 后,调用其每个出边
tree_edge(u, g) 当一条边成为搜索树的边时调用
back_edge(u, g) 调用一个图的回边(back edges);用于无向图,因为 (u, v) 和 (v, u) 是同一条边,所以tree_edge 和 back_edge 均被调用

请注意,BGL 还支持其他访客,比如 dijkstra_visitorbellman_visitor 和 astar_visitor

回页首

结束语

以上就是本文内容,我们学习了如何在 BGL 中创建无向图、有向图和加权图;还了解了存取函数和访问函数,并在您创建的图上尝试实现了一些经典图算法。BGL 提供的远远不止这些,本文只触及一点皮毛。请务必查阅 BGL 文档,获取有关的详细信息。

http://www.ibm.com/developerworks/cn/aix/library/au-aix-boost-graph/#list2

Boost Graph Library相关推荐

  1. Boost Graph Library 快速入门

    Boost Graph Library 快速入门 图领域的数据结构和算法在某些方面比容器更为复杂,图算法在图中移动有着众多的路线,而STL使用的抽象迭代器接口不能有效的支持这些.作为替换,我们为图提供 ...

  2. 【转】使用Boost Graph library(二)

    原文转自:http://shanzhizi.blog.51cto.com/5066308/942972 让我们从一个新的图的开始,定义一些属性,然后加入一些带属性的顶点和边.我们将给出所有的代码,这样 ...

  3. C++图常用库boost graph library

    接下来一段日子,会写一些跟bgl相关的内容.bgl是一个性能很不错的库,但是源码跟鬼畜一样-文档也写得很乱.因为最近做的论文需要比较好的性能,实在不得不硬着头皮重新用起bgl.所以尽可能的做一些总结, ...

  4. boost graph_探索Boost Graph库

    发表关于计算的公理陈述通常是一个热门辩论的问题. 但是,图论是现代计算最重要的理论Struts之一,并不是其中一种说法. 从设计路由器和网络到设计构成移动设备核心的芯片的无数工程领域不过是图论的应用. ...

  5. Boost Graph Library-BGL学习笔记1

    首先需要说明的是BGL是一个增强STL map的通用类库,这个库是一个仅有header文件的C++库,不需要分开编译.我发现很多文章上来就讲怎么用,其实安装和导入可能会让一些人卡一会儿,做法也比较简单 ...

  6. 了解 Boost Filesystem Library文件系统

    C++ 语言(实际上是 C++ 标准)的最常见问题之一是,缺乏定义良好的库来帮助处理文件系统查询和操作.由于这个原因,程序员不得不使用本机操作系统提供的应用程序编程接口(Application Pro ...

  7. boost::graph::distributed::mpi_process_groupboost::graph::用法的测试程序

    boost::graph::distributed::mpi_process_groupboost::graph::用法的测试程序 实现功能 C++实现代码 实现功能 boost::graph::di ...

  8. boost::graph::distributed::hohberg_biconnected_components用法的测试程序

    boost::graph::distributed::hohberg_biconnected_components用法的测试程序 实现功能 C++实现代码 实现功能 boost::graph::dis ...

  9. boost::graph::distributed::distributed_queue用法的测试程序

    boost::graph::distributed::distributed_queue用法的测试程序 实现功能 C++实现代码 实现功能 boost::graph::distributed::dis ...

  10. boost::graph::page_rank用法的测试程序

    boost::graph::page_rank用法的测试程序 实现功能 C++实现代码 实现功能 boost::graph::page_rank用法的测试程序 C++实现代码 #include < ...

最新文章

  1. Keras中神经网络可视化模块keras.utils.vis_util 的安装
  2. 软件测试论坛_浅谈软件测试的未来,我们该如何做好准备
  3. php:Mcrypt响应慢的原因解决备注
  4. 瑞幸咖啡百万大咖活动 记人生第一次豪赌,净赔了200元钱。
  5. 多个sheet拆分成多个文件_Pandas拆分DataFrame到多个文件中
  6. 201209阶段二FFmpeg转码
  7. SAP Spartacus支持的语言和货币单位的数据源
  8. Rumor CodeForces - 893C(并查集)
  9. Spring Cloud教程– Spring Cloud Config Server简介
  10. 引入 javascript_在您JavaScript项目中引入类型安全性? 再想一想
  11. cad在布局怎么调比例_大神们都在用的9个CAD制图技巧,你会用几个?
  12. gRPC-go源码(1):连接管理
  13. 开源 协作工具_6所高等学校教授开源,协作峰会的言论以及更多新闻
  14. 21世纪IT人才需要具有的5个鲜明特点
  15. sun的java认证考试_Sun Java认证考试科目
  16. Permute3 mac最新多种媒体视频格式转换工具
  17. 贝塞尔曲线及实践案例
  18. c# rar解压大小_C#解压、压缩RAR文件
  19. python爬取实习僧招聘信息字体反爬
  20. FFmpeg命令行,从小白入门到收藏吃灰——功能大全,总有你需要的!

热门文章

  1. C盘清理工具Dism++教程
  2. DEA用法(1)--三阶段DEA模型与DEAP使用方法教程
  3. 用R进行文本挖掘与分析--软件分词统计词频
  4. secureCRT连接Linux虚拟机
  5. 四川大学软件学院操作系统笔记
  6. Mysql教程|基础使用方法
  7. linux服务器操作系统
  8. eggjs 项目实践
  9. 分享20个Android游戏源码,希望大家喜欢哈!
  10. UNIX系统V(System V)