基于Boost BGL的带规避与提前终止的深度优先遍历问题
问题描述
给定如下有向树结构:
0 | 1 /|\ / | \ 2' 3' 4' | | 5' 6'
需求:从顶点0出发执行深度优先遍历,找到第一个带pebble(以撇号'标记)的顶点后立即终止遍历,同时需规避指定顶点。例如规避顶点2时,预期遍历顺序为0→1,1→2(规避后返回1),1→3(找到pebble,终止),禁止访问5、6、4。
目前基于Boost BGL的depth_first_visit实现,控制台输出看似符合预期:
Discover vertex 0 Discover vertex 1 Discover vertex 2 Avoiding vertex 2 Finished vertex 2 Discover vertex 3 Found a pebble on vertex 3 and stopping dfs.
但实际depth_first_visit并未真正在顶点3处终止:虽然通过终止函数避免了检查边25和36(不会发现5、6),但边14会在顶点1被发现时立即被检查,导致顶点4被访问。
现有实现代码:
#include <boost/graph/adjacency_list.hpp> #include <boost/graph/graph_utility.hpp> #include <iostream> struct pebbles{ int num_pebbles = 1; }; using Graph = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS,pebbles>; struct Visitor : boost::default_dfs_visitor { using Vertex = boost::graph_traits<Graph>::vertex_descriptor; using Edge = boost::graph_traits<Graph>::edge_descriptor; boost::optional<Vertex> avoid; void discover_vertex(Vertex s, Graph const &g){ if (stop_dfs) return; std::cerr << "Discover vertex: " << s << std::endl; if (s==avoid) {std::cerr << "Avoiding vertex: " << s << std::endl; return; } if (g[s].num_pebbles != 0 ){ stop_dfs = true; std::cerr << "Found a pebble on vertex: " << s << " and stopping dfs." << std::endl; return; } } //void examine_edge(Edge e, Graph const& ) const { std::cerr << "Examining edge: " << e << std::endl; } void finish_vertex(Vertex s, Graph const&) const { if (stop_dfs) return; std::cerr << "Finished vertex: " << s << std::endl; } //terminator function bool operator()(Vertex s, Graph const& g) const { return ((s == avoid) || (g[s].num_pebbles != 0)); }; private: bool stop_dfs = false; }; int main(){ Graph g; //test graph g = {{0,1,2,3,4,5,6},{01,12,13,14,25,36}} boost::add_edge(0,1,g); boost::add_edge(1,2,g); boost::add_edge(1,3,g); boost::add_edge(1,4,g); boost::add_edge(2,5,g); boost::add_edge(3,6,g); g[0].num_pebbles = 0; g[1].num_pebbles = 0; Visitor peb_vis_termfunc; std::vector<boost::default_color_type> colormap(num_vertices(g)); peb_vis_termfunc.avoid = 2; //avoid vertex 2 boost::depth_first_visit(g,0,peb_vis_termfunc,colormap.data(),peb_vis_termfunc); return 0; }
取消注释examine_edge函数即可验证边(1,4)会被检查。希望找到pebble后完全退出depth_first_visit,不再检查任何剩余边。
查阅Boost文档得知,强制提前退出的正确方式是抛出异常,但作为C++/Boost新手,不清楚具体实现方法,同时希望得到代码风格、最佳实践或改进方向的建议。
解决方案
1. 用异常实现强制终止
Boost BGL的DFS遍历支持通过抛出异常提前终止,遍历函数会捕获异常并向上传递,可立即停止所有后续的边检查和顶点处理。
修改步骤:
- 定义自定义异常类型(比通用异常更清晰)
- 在发现pebble时抛出该异常
- 在
main函数中捕获异常,避免程序崩溃
修改后的代码:
#include <boost/graph/adjacency_list.hpp> #include <boost/graph/graph_utility.hpp> #include <iostream> #include <stdexcept> // 自定义异常类型,标记DFS正常终止 struct DfsTerminated : public std::runtime_error { using std::runtime_error::runtime_error; }; struct pebbles{ int num_pebbles = 1; }; using Graph = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS,pebbles>; struct Visitor : boost::default_dfs_visitor { using Vertex = boost::graph_traits<Graph>::vertex_descriptor; using Edge = boost::graph_traits<Graph>::edge_descriptor; boost::optional<Vertex> avoid; void discover_vertex(Vertex s, Graph const &g){ std::cerr << "Discover vertex: " << s << std::endl; if (s == avoid) { std::cerr << "Avoiding vertex: " << s << std::endl; return; } if (g[s].num_pebbles != 0 ){ std::cerr << "Found a pebble on vertex: " << s << " and stopping dfs." << std::endl; // 抛出异常终止整个DFS流程 throw DfsTerminated("Pebble found, stopping DFS"); } } void examine_edge(Edge e, Graph const& ) const { std::cerr << "Examining edge: " << e << std::endl; } void finish_vertex(Vertex s, Graph const&) const { std::cerr << "Finished vertex: " << s << std::endl; } // terminator函数:仅用于跳过规避顶点的子树 bool operator()(Vertex s, Graph const& g) const { return (s == avoid); }; }; int main(){ try { Graph g; // 构建测试图 boost::add_edge(0,1,g); boost::add_edge(1,2,g); boost::add_edge(1,3,g); boost::add_edge(1,4,g); boost::add_edge(2,5,g); boost::add_edge(3,6,g); g[0].num_pebbles = 0; g[1].num_pebbles = 0; Visitor dfs_visitor; std::vector<boost::default_color_type> colormap_vec(num_vertices(g)); auto colormap = boost::make_iterator_property_map( colormap_vec.begin(), get(boost::vertex_index, g), boost::white_color ); dfs_visitor.avoid = 2; // 规避顶点2 boost::depth_first_visit(g,0,dfs_visitor,colormap,dfs_visitor); } catch (const DfsTerminated& e) { // 捕获终止异常,程序正常结束 std::cerr << "DFS terminated: " << e.what() << std::endl; } return 0; }
效果验证:
运行后输出为:
Discover vertex: 0 Examining edge: (0,1) Discover vertex: 1 Examining edge: (1,2) Discover vertex: 2 Avoiding vertex: 2 Finished vertex: 2 Examining edge: (1,3) Discover vertex: 3 Found a pebble on vertex: 3 and stopping dfs. DFS terminated: Pebble found, stopping DFS
可见边(1,4)未被检查,完全符合预期。
2. 代码风格与最佳实践建议
- 异常使用:自定义异常类型比通用异常更清晰,便于区分业务逻辑终止和其他错误。
- 颜色映射管理:用
boost::make_iterator_property_map管理颜色映射,更符合BGL的设计习惯,避免直接操作底层容器。 - 冗余状态移除:原代码中的
stop_dfs成员变量可删除,异常已能直接终止遍历,无需额外状态标记。 - 变量命名:将
peb_vis_termfunc改为dfs_visitor,命名更简洁清晰。 - const正确性:确保无需修改的成员函数和参数都添加
const,维持代码的严谨性。 - 注释补充:给关键逻辑(如异常抛出、终止函数作用)添加注释,提升代码可维护性。
- 可选类型替代:若环境支持C++17,可将
boost::optional替换为std::optional,减少Boost依赖。
内容的提问来源于stack exchange,提问作者Randy Marsh
相关产品推荐
相关产品推荐

