You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 01:45:05