【问题标题】:Boost filtered graph with blacklisted edges使用列入黑名单的边提升过滤图
【发布时间】:2026-01-29 21:10:01
【问题描述】:

我想在边缘被列入黑名单的图上运行 Dijkstra,即,我想计算不使用这些链接的最短路径。 目前,我首先定义过滤器:

typedef std::pair<int, int> Edge;
typedef boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, boost::property<boost::edge_weight_t, int> > graph_t;

结构 BlackListEdgeConstraint { 私人的: 标准::设置黑名单; graph_t* g;

public:

    BlackListEdgeConstraint():blackList(std::set<Edge>() ),g(NULL){};

    BlackListEdgeConstraint(std::set<Edge>& list, graph_t* g_) : blackList(list), g(g_)
    {
    }

    /**
     * This is the "predicate function" used by boost::filtered_graph (
     *  see http://www.boost.org/doc/libs/1_64_0/libs/graph/doc/filtered_graph.html )
     *  It it returns true, the edge is included in the filtered graph, otherwise it is excluded.
     */
    bool operator()(const boost::graph_traits<graph_t>::edge_descriptor& e) const
    {
        Edge edge(source(e,*g), target(e,*g) );
        //Include the edge if it's not in the blacklist.
        return blackList.find( edge ) == blackList.end();
    }
};

然后我在 5 月执行此操作 main(...) 函数

... I fill the graph g ...
std::set<Edge> blacklist; blacklist.insert( Edge(0,1)  );
BlackListEdgeConstraint filter(blacklist, &g);
boost::filtered_graph<graph_t, BlackListEdgeConstraint> filtered(g, filter);
... I run Dikjstra on the filtered graph ...

现在,我所做的工作有效,但很奇怪。实际上,我首先在顶点 0 和 1 之间创建了一条边。然后,在 operator() (...) 内部,我有一个 edge_descriptor 而不是 Edge(如果我将 Edge 作为参数,编译器会按照 here 的解释进行抱怨,所以我猜boost 正在某处进行一些转换,原因我不知道)。然后,我再次检索operator() (...) 中的顶点 0 和 1,并重建 Edge。您知道,如果直接接受 operator()(..) Edge,我会花很长时间做一些简单的事情。

你认为我可以以更优雅、更高效的方式完成相同的操作吗?

【问题讨论】:

    标签: c++ performance boost-graph


    【解决方案1】:

    你基本上问什么与提升图关系不大。您希望能够高效地查找一对顶点描述符。

    决定因素将是黑名单数据结构的选择。

    附带说明,您Edge 对象插入到图表中。您选择的图模型是一个邻接列表,因此它存储每个顶点的相邻顶点列表。

    pair&lt;int,int&gt; 只是一种方便的类型,用于轻松初始化图形。你完全可以不用它。

    在考虑了可能的选择之后,我认为没有更快的方法。

    • 在某些较大的规模上,您可能会获得更高的有效性能

      • 使用本身表示为邻接列表的黑名单(例如std::map&lt;source_vertex, std::list&lt;target_vertex&gt; &gt;)或
      • 使用unordered_set&lt;&gt;

      除非大规模进行,否则这两种优化都不太可能产生显着差异。

    • 通过使用boost::container::flat_set,您可能会受益于参考位置

    成本真实吗?

    如果您认为“构造 Edge”是一种资源浪费,请忘记这一点:它是一种 POD 类型 (std::pair&lt;int, int&gt;),因此只有微不足道的构造函数/析构函数。看到set&lt;Edge&gt;是一个模板类,对它的大部分操作都可以内联。编译器将内联方法调用、注册参数、删除多余的加载/存储周期并有效地生成最佳代码:

    #include <set>
    #include <utility>
    
    using Edge = std::pair<int, int>;
    using Blacklist = std::set<Edge>;
    
    struct Pred {
        Blacklist blacklist { { 
            { 92, 29 },
            { 74, 92 },
            { 86, 6 },
            { 67, 35 },
            { 59, 4 },
            { 66, 13 },
            { 82, 37 },
            { 51, 94 },
            { 32, 6 }
        } };
    
        bool operator()(int source, int target) const {
            return blacklist.end() != blacklist.find(Edge {source, target});
        }
    };
    

    View Disassembly Live On Godbolt GCC Explorer

    专业提示:点击 Clang 反汇编上的 # 按钮查看优化器 cmets

    总结 Edge 类型的成本不存在。任何实际成本都取决于在edge_descriptor 上使用boost::source()boost::target()

    看到您的边缘容器选择器是listS,您的边缘容器是基于节点的,这意味着边缘描述符是稳定的,并且基本上是对边缘对象的类型擦除引用。调用boost::source(e, g) 只不过是转换描述符和取消引用而已。根据它的使用方式,编译器可能甚至能够看穿这些间接。

    如果您不喜欢该成本,请调整 Graph 类型 :)(您的用例可能需要使用 EdgeList 概念,或者受益于使用基于节点的边缘容器等)。

    【讨论】: