【问题标题】:BGL adjacency_list: How to sort out_edges using vertex property, not descriptorBGL adjacency_list:如何使用顶点属性而不是描述符对 out_edges 进行排序
【发布时间】:2016-12-04 21:21:26
【问题描述】:

使用 BGL adjacency_list,我希望顶点的出边按目标顶点的属性进行排序。

在 BGL 中,出边列表已经按目标顶点描述符排序,但我碰巧使用 listS 作为我的顶点容器,所以我的顶点描述符是 void* 指针。不幸的是,我发现按这些地址排序会使我的图遍历的顺序不确定。我的顶点确实有一个自定义属性类,它的 ID 可用于对出边进行排序,但我无法使其工作。 (见下面的代码。)

我参考了这个问题,这对诊断问题很有帮助,但我没有看到一个好的解决方案:boost graph library: deterministic order of iteration of in_edges?

未分类的 MWE

这是一个没有任何排序尝试的 MWE。请注意,我以 0、5、2、8 的顺序使用id 创建顶点,以模拟与id 的顺序不同的顶点地址。在我的实际应用中,我不能保证这些顶点的地址会遵循id 的顺序。

Live On 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

尝试排序,不工作

我查看了 Boost 文档,发现这两部分很有帮助。

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

  2. BGL 还展示了如何将custom vertex property tag 添加到顶点;我曾希望我可以在没有图形对象的情况下从顶点描述符访问标记的顶点属性;但这似乎也不起作用

Live On 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*) 而没有图形。

问题

谁能想出一种方法来通过目标顶点的外部或内部标记属性对外边进行排序?

【问题讨论】:

    标签: c++ boost boost-graph


    【解决方案1】:

    在 BGL 中,出边列表已经按目标顶点描述符排序

    尚未检查,但我马上要说这是一个未记录的实现细节,而不是值得依赖的东西(除非您可以指出文档中的说明)。

    更新感谢@EvanW,我现在记得我确实检查过这个:boost graph library: deterministic order of iteration of in_edges?。因此,给定一个有序的OutEdgeListS,您可以依赖由顶点描述符排序的出边(直到新版本的 BGL 可能更改该实现细节)。

    当然,问题在于 vertex_descriptor 值本身在这里不是确定性的。

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

    确实,您已经查明了问题所在:虽然存储边对象在那里具有属性包(这就是使它们捆绑的原因),但顶点只是由它们的描述符引用(它们是不透明的,正如你所指出的)。

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

    1. 使用全局引用,这当然有不支持多个图形类型实例的缺点。我强烈建议不要这样做,因为它太容易破坏(例如,当您按值传递图表时可能已经损坏)并限制图表在其他方面的使用(例如,您将无法使用子图)。

    2. 在边缘属性中存储一个冗余的图形引用。这是相当浪费的,但它可以完成工作,至少对于这个简单的测试用例(参见下面的 CAVEAT)

    概念证明

    方法 2. 提出了另一个技术障碍,那就是您可以转发声明 Graph 的要求,这样您就可以在 EdgeInfo² 中实际存储一个类型化的指针:

    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。请注意,我在调试模式下要格外小心,因此我们断言 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; };
    }
    

    CAVEAT:我不会绝对相信 Boost 的任何算法都不会在复制图形时复制边缘属性。可悲的是,禁止 EdgeInfo 的复制构造函数破坏了标准的 add_edge 实现,因为 if 不允许移动语义。同样,它存在于 detail 命名空间中,因此破解它的唯一真正方法是对 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);
        }
    }
    

    我们准备好了:

    Live On 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&lt;&gt;::struct config)来破解它,因为一切都假定StoredEdges 可以是默认构造的,也可以是从EdgeProperty 对象构造的。无法注入对图的引用。

    ² 再次,你不能作弊,因为即使你存储了 void const* 也没有办法在你的边缘集的比较器内实际转换为 Graph 类型,因为......类型在声明 Graph 之前无法知道 :)

    ³ 很容易忘记它,但我们添加了一个自定义构造函数,因此我们不能有默认构造的EdgeInfos。

    【讨论】:

    • "尚未检查,但我马上要说这是一个未记录的实现细节,不是可以依赖的(除非您可以指出文档中的位置) 。” -- 实际上,我是从那里学到的。您在另一个相关问题上的comment 真的很有帮助。感谢您也回答我的问题。
    • @EvanW 你知道什么。原来我确实在其他时候检查过。不过,重点是:这是一个未记录的实现细节。 确定性记录在案的并不完全一样,更不用说值得依赖了。更糟糕的是,如果顶点容器是随机访问的 (vecS),它只是确定性的。使用 listS可能是确定性的,只要您的分配是确定性的。如果你问我,那就太牵强了。
    • 在话题上,是什么让你忽略了答案(除了希望不喜欢“hacky”方面和缺点)?我错过了问题的一部分吗?
    • 接受答案,因为它确实有效;虽然最后我们继续在边缘信息中添加另一个 ID 并基于这些进行排序。抱歉回复晚了——感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多