BGL Adjacency_list:如何使用顶点属性(而不是描述符)对OUT_EDGE进行排序

BGL adjacency_list: How to sort out_edges using vertex property, not descriptor

本文关键字:OUT 描述 EDGE 排序 list Adjacency 何使用 顶点 BGL 属性      更新时间:2023-10-16

使用bgl adjacency_list,我希望来自顶点的外边缘以目标顶点的属性对。

在bgl中,范围的列表已经由目标顶点描述符分类,但是我恰好使用 listS作为我的顶点容器,因此我的顶点描述符是void*指针。不幸的是,我发现通过这些地址进行排序使我的图形遍历不确定。我的顶点确实有一个自定义属性类,该类别具有可用于整理外边缘的ID,但我无法使其正常工作。(请参阅下面的代码。)

我已经提到了这个问题,这有助于诊断问题,但是我在其中没有看到一个好的解决方案:Boost Graph库:IN_EDGES的确定性迭代顺序?

未分类的MWE

这是MWE,无需进行分类。请注意,我用id在0、5、2、8顺序创建顶点,以模拟顶点地址的顺序与id不同。在我的真实应用中,我不能保证这些顶点的地址将遵循id订购。

活在coliru

#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/directed_graph.hpp>
class VertexInfo
{
public:
    VertexInfo(int i) : id(i) {}
    int id;
};
int main()
{
    typedef boost::adjacency_list< boost::setS,
                                   boost::listS,    
                                   boost::bidirectionalS,
                                   VertexInfo,
                                   boost::no_property,
                                   boost::no_property,
                                   boost::listS
                                 > Graph;
    //typedef boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;
    Graph g;
    Vertex src  = boost::add_vertex(VertexInfo(0), g);
    Vertex tar1 = boost::add_vertex(VertexInfo(5), g);
    Vertex tar2 = boost::add_vertex(VertexInfo(2), g);
    Vertex tar3 = boost::add_vertex(VertexInfo(8), g);
    boost::add_edge(src, tar1, g);
    boost::add_edge(src, tar2, g);
    boost::add_edge(src, tar3, g);
    // If sorted by address, the order would probably be:
    // 0 --> 5
    // 0 --> 2
    // 0 --> 8
    // If sorted by ID, the order should be:
    // 0 --> 2
    // 0 --> 5
    // 0 --> 8
    typename boost::graph_traits<Graph>::out_edge_iterator ei, ei_end;
    for(boost::tie(ei, ei_end) = boost::out_edges(src, g); ei != ei_end; ++ei)
    {
        std::cout << g[boost::source(*ei, g)].id 
                  << " --> " 
                  << g[boost::target(*ei, g)].id 
                  << std::endl;
    }
    return 0; 
}

目前给我的是:

0 --> 5
0 --> 2
0 --> 8

,但我需要给我这个:

0 --> 2
0 --> 5
0 --> 8

尝试分类,不工作

我看了提升文档,发现这两个部分有帮助。

  1. bgl提供了一个示例,介绍了如何通过创建自定义容器选择器并使用该容器的自定义比较器来订购Out-Edges(orded_out_edges.cpp)。不幸的是,该示例中的比较器使用目标顶点描述符和边缘的默认属性,以进行比较。我需要一个基于目标顶点的自定义属性进行比较的比较器;无访问图对象。

  2. bgl还显示了如何在顶点添加自定义顶点属性标签;我曾希望我可以在没有图形对象的情况下从顶点描述符访问标记的顶点属性。但这似乎不起作用

活在coliru

#include <iostream>
#include <functional>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/directed_graph.hpp>
// http://www.boost.org/doc/libs/1_55_0/libs/graph/example/ordered_out_edges.cpp
// http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/using_adjacency_list.html#sec:custom-vertex-properties
// https://stackoverflow.com/questions/30968690/boost-graph-library-deterministic-order-of-iteration-of-in-edges
// http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/adjacency_list.html
// ??
// https://stackoverflow.com/questions/9169276/bgl-edgeu-v-g-with-custom-associative-container-for-edge-lists
#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
#error BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION not supported
#endif
// See custom-vetex-properties
struct vertex_info_t
{
    typedef boost::vertex_property_tag kind;
};
class VertexInfo
{
public:
    VertexInfo(int i) : id(i) {}
    int id;
};
////////////////////////////////////////
// See libs/graph/example/ordered_out_edges.cpp
template <class StoredEdge>
struct Comparator : public std::binary_function<StoredEdge, StoredEdge, bool>
{
    bool operator()(const StoredEdge& e1, const StoredEdge& e2)
    {
        return boost::get(vertex_info_t(), e1.get_target()).id < boost::get(vertex_info_t(), e2.get_target()).id;
        //return e2.get_target() < e1.get_target(); // reverse order of insertion, an example to prove custom OrderedSetS but does not use vertex properties
    }
};
struct OrderedSetS {};
namespace boost
{
    template <class ValueType>
    struct container_gen<OrderedSetS, ValueType>
    {
        typedef std::set<ValueType, Comparator<ValueType> > type;
    };
    template <>
    struct parallel_edge_traits<OrderedSetS>
    { 
        typedef allow_parallel_edge_tag type;
    };
}
////////////////////////////////////////
int main()
{
    typedef boost::adjacency_list< OrderedSetS, //boost::setS,
                                   boost::listS,    
                                   boost::bidirectionalS,
                                   boost::property<vertex_info_t, VertexInfo>, //VertexInfo,
                                   boost::no_property,
                                   boost::no_property,
                                   boost::listS
                                 > Graph;
    //typedef boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;
    Graph g;
    Vertex src  = boost::add_vertex(VertexInfo(0), g);
    Vertex tar1 = boost::add_vertex(VertexInfo(5), g);
    Vertex tar2 = boost::add_vertex(VertexInfo(2), g);
    Vertex tar3 = boost::add_vertex(VertexInfo(8), g);
    boost::add_edge(src, tar1, g);
    boost::add_edge(src, tar2, g);
    boost::add_edge(src, tar3, g);
    // If sorted by address, the order would probably be:
    // 0 --> 5
    // 0 --> 2
    // 0 --> 8
    // If sorted by ID, the order should be:
    // 0 --> 2
    // 0 --> 5
    // 0 --> 8
    typename boost::graph_traits<Graph>::out_edge_iterator ei, ei_end;
    for(boost::tie(ei, ei_end) = boost::out_edges(src, g); ei != ei_end; ++ei)
    {
        std::cout << boost::get( boost::get(vertex_info_t(), g), boost::source(*ei, g) ).id 
                  << " --> " 
                  << boost::get( boost::get(vertex_info_t(), g), boost::target(*ei, g) ).id 
                  << std::endl;
    }
    return 0; 
}

这导致汇编错误

main.cpp:38:26: error: no matching function for call to 'get(vertex_info_t, void*&)'
         return boost::get(vertex_info_t(), e1.get_target()).id < boost::get(vertex_info_t(), e2.get_target()).id;

它不允许我通过vertex_info_t标签获得VertexInfo属性类,仅使用顶点描述符(void*),而没有图形。

问题

任何人都可以想到一种通过目标顶点的外部或内部标记属性对外边进行排序的方法?

在BGL中,Target顶点描述符

已经对外向列表进行了排序

尚未检查过,但是当蝙蝠时,我会说这是一个无证件的实现细节,而不是要依靠的东西(除非您可以指出文档中的何处)。

update 感谢@evanw,我现在记得我 di di di di em> 先前检查了这一点:boost Graph库:in_edges的确定性迭代顺序?因此,给定一个订购的OutEdgeListS,您 can can 依靠顶点描述符订购的外部(直到BGL的较新版本更改该实现细节,可能)。

但是,问题当然是vertex_descriptor值本身不是确定性的。

bgl还显示了如何在顶点添加自定义顶点属性标签;我曾希望我可以在没有图形对象的情况下从顶点描述符访问标记的顶点属性。但这似乎不起作用

的确,您已经指出了问题:尽管存储的对象在那里有属性捆绑包(这就是使它们捆绑),但其描述符仅引用了顶点(这是不透明的,如您所指出的)。

因此,唯一的解决方法是以某种方式具有可用的图形对象。由于adjacency_list<>没有提供自定义边缘容器构造方式的方法¹,因此大约有两种方法:

  1. 使用一个全局参考,当然,该参考的缺点是不支持图形类型的一个以上的实例。我强烈建议反对这一点,因为它会太容易破裂(例如,当您按值逐个值传递时可能已经破裂了),并以其他方式限制图表的使用(例如,您将无法使用子图)。

    <</p>
  2. 在边缘属性中存储一个冗余图形。这是相当浪费的,但至少对于此简单的测试案例(请参阅下面的警告)

概念证明

方法2.再提出一个技术障碍,这是您可以转发声明Graph的要求

struct ForwardGraph;
struct VertexInfo
{
    VertexInfo(int i) : id(i) {}
    int id;
};
struct EdgeInfo {
    ForwardGraph const* graph;
    EdgeInfo(ForwardGraph const& g) : graph(&g) {}
    //EdgeInfo(EdgeInfo const&) = delete;
    EdgeInfo& operator=(EdgeInfo const&) = delete;
};

现在,在某种程度上间接定义ForwardGraph

typedef boost::adjacency_list<by_idS, boost::listS, boost::bidirectionalS, VertexInfo, EdgeInfo> GraphImpl;
struct ForwardGraph : GraphImpl {
    using GraphImpl::GraphImpl; // inherit constructors
};

当然,关键是如何连接by_idS。请注意,我在Debug模式中非常有意义,所以我们断言graph实际上是为我们的边缘设置的。

struct by_idS { };
namespace boost {
    template <class T>
    struct container_gen<by_idS, T> {
        struct cmp {
            bool operator()(const T& e1, const T& e2) const {
                auto const* g1 = get(&EdgeInfo::graph, e1);
                auto const* g2 = get(&EdgeInfo::graph, e2);
                assert(g1 && g2 && g1 == g2);
                auto& g = *g1;
                return g[e1.get_target()].id < g[e2.get_target()].id;
            }
        };
        typedef std::multiset<T, cmp> type;
    };
    template <> struct parallel_edge_traits<by_idS> { typedef allow_parallel_edge_tag type; };
}

警告:我不会绝对相信,在复制图形时,Boost的算法不会复制边缘属性。可悲的是,禁止EdgeInfo的复制构造函数打破了标准的add_edge实现,因为如果不允许移动语义。同样,这是一个细节名称空间的生活,因此唯一的破解方法是为BGL贡献更改

一个小帮手,以防止忘记添加新边缘的EdgeInfo属性(使用C 14用于简洁):

namespace boost {
    auto add_edge(GraphImpl::vertex_descriptor src, GraphImpl::vertex_descriptor tgt, ForwardGraph& g) {
        return add_edge(src, tgt, EdgeInfo { g }, g);
    }
}

我们准备滚动:

活在coliru

typedef ForwardGraph Graph;
int main() {
    Graph g;
    auto src  = boost::add_vertex({0}, g);
    auto tar1 = boost::add_vertex({5}, g);
    auto tar2 = boost::add_vertex({2}, g);
    auto tar3 = boost::add_vertex({8}, g);
    boost::add_edge(src, tar1, g);
    boost::add_edge(src, tar2, g);
    boost::add_edge(src, tar3, g);
    typename boost::graph_traits<Graph>::out_edge_iterator ei, ei_end;
    for(auto e : boost::make_iterator_range(boost::out_edges(src, g)))
        std::cout << g[boost::source(e, g)].id << " --> " << g[boost::target(e, g)].id << std::endl;
}

打印

0 --> 2
0 --> 5
0 --> 8

¹您甚至无法通过在详细信息名称中专门使用类型(即adj_list_gen<>::struct config)来入侵它,因为所有事物都假定StoredEdge S可以是默认构造或从EdgeProperty对象构造的 CC_21 s。没有办法注入对图的引用。

²再次,您不能作弊,因为即使您存储了void const*,也无法实际投射到边缘集的比较器中的Graph类型,因为...类型不能是在声明Graph :)

之前知道

³很容易忘记它,但是我们添加了一个自定义构造函数,因此我们不能躺在周围默认结构的EdgeInfo s。