【问题标题】:Iterating over boost graph vertexes based on maximal depth基于最大深度迭代提升图顶点
【发布时间】:2020-04-11 08:17:45
【问题描述】:

我想根据从任何根顶点到顶点的最大深度来迭代图的顶点。

例如,顶点I 的最大深度为 3(并且在下图中用此值进行了注释)。因此,任何排序都是可以接受的:{A , B , C , D} , {E , F , G} , {H}, {I},其中花括号 {} 中的任何顶点都可以任意排序。

我遇到过similar question,但它旨在获得单个顶点的最大深度,并且似乎假设一个根节点。

我对提升图很陌生,但这是我对解决方案可能类似的微弱尝试

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>

using namespace boost;

class depth_visitor : public default_dfs_visitor
{
public:
    template <typename Vertex , typename Graph >
    void discover_vertex(Vertex v , const Graph & g) const
    {
        // update 'depth' of vertex 
    }
};


int main()
{
  enum { A , B, C, D, E, F, G , H , I , COUNT };
  const char* name[] = { "A" , "B" , "C" , "D" , "E" , "F" , "G" , "H" , "I" };

  typedef std::pair <int, int >Edge;
  Edge edge_array[] = { Edge(D, G), Edge(C, G), Edge(C, F), Edge(B, F), Edge(B, E), Edge(A, E),
                        Edge(G, H), Edge(F, I), Edge(E, H), Edge(H, I) };

    typedef adjacency_list < vecS, vecS, directedS > graph_t;
    graph_t g( edge_array , edge_array + sizeof(edge_array) / sizeof(E), COUNT );

    depth_visitor vis;
    depth_first_search( g , visitor( vis ) );
}

【问题讨论】:

  • 图形术语中的“最大深度”可能是最短路径的长度。在树中这很容易,但提升图不受这种方式的限制。您需要强制该图是一棵树。无论如何,您应该可以关注boost::out_edges,直到没有人来了解您的深度。

标签: c++ boost boost-graph


【解决方案1】:

Andy 说的:您可以手动遍历出边。

或者,您可以将BFS 与自定义访问者一起使用。你已经有类似的想法了。

这是一个实现:

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <iostream>
#include <map>

现在,让我们定义我们的类型:

using Name   = char;
using Graph  = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Name>;
using Edge   = Graph::edge_descriptor;
using Vertex = Graph::vertex_descriptor;

让我们创建自己的最大距离记录器。这大致基于 Boost 的distance_recorder&lt;&gt; 类,即EventVisitor

在我们的例子中,我们将让它仅过滤 on_tree_edge 事件(因为我们不关心这里的非树图)。

using Depths = std::map<Vertex, size_t>;

struct Recorder : public boost::base_visitor<Recorder> {
    using event_filter = boost::on_tree_edge;

    Depths& _ref;
    Recorder(Depths& r):_ref(r){}

    void operator()(Edge e, const Graph& g) const {
        auto s = source(e, g), t = target(e, g);
        std::cout << "TREE EDGE " << g[s] << " -> " << g[t] << "\n";

        auto srcit = _ref.find(s);
        auto depth = srcit!=_ref.end()? srcit->second+1: 1;

        if (auto [it, isnew] = _ref.emplace(t, depth); !isnew) {
            it->second = std::max(it->second, depth);
        }
    }
};

你可以看到它基本上只是更新了一个std::map,但有以下特殊处理:

  • 更新现有值时,它会根据当前距离和之前记录的距离的最大值进行更新
  • 为树边未经过的顶点插入深度(基本上,它不使用operator[source_vertex],它会插入深度为0的条目)

除此之外,我们只需要调用算法即可:

std::vector roots { A, B, C, D };

Depths depths;
Recorder r{depths};

for (auto root : roots)
    boost::breadth_first_search(
        g, root,
        queue, boost::make_bfs_visitor(r), color_map);

for (auto [v,d] : depths)
    std::cout << g[v] << " at " << d << "\n";

重要提示:

  • 使用breadth_first_search 而不是breadth_first_visit,因为否则color_map 将不会在每个根节点上重新初始化。这将使已经发现的节点总是被跳过,这意味着我们不会正确地获得最大距离
  • 这也是我们不能像这样使用多源重载的原因:

    // WARNING BROKEN!
    boost::breadth_first_search(
            g, begin(roots), end(roots),
            queue, boost::make_bfs_visitor(r), color_map);
    

    效果是一样的,因为没有为每个根节点重新初始化颜色图。

现场演示

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <iostream>
#include <map>

using Name   = char;
using Graph  = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Name>;
using Edge   = Graph::edge_descriptor;
using Vertex = Graph::vertex_descriptor;

using Depths = std::map<Vertex, size_t>;

struct Recorder : public boost::base_visitor<Recorder> {
    using event_filter = boost::on_tree_edge;

    Depths& _ref;
    Recorder(Depths& r):_ref(r){}

    void operator()(Edge e, const Graph& g) const {
        auto s = source(e, g), t = target(e, g);
        std::cout << "TREE EDGE " << g[s] << " -> " << g[t] << "\n";

        auto srcit = _ref.find(s);
        auto depth = srcit!=_ref.end()? srcit->second+1: 1;

        if (auto [it, isnew] = _ref.emplace(t, depth); !isnew) {
            it->second = std::max(it->second, depth);
        }
    }
};

int main() {
    enum : Vertex { A, B, C, D, E, F, G, H, I, COUNT };
    Graph g(COUNT);

    // give names
    for (auto v : boost::make_iterator_range(vertices(g)))
        g[v] = 'A' + v;

    // add edges
    for (auto [s,t] : {
            std::pair(D,G), {D, G}, {C, G}, {C, F}, {B, F}, {B, E}, {A, E},
            {G, H}, {F, I}, {E, H},
            {H, I} })
    {
        add_edge(s, t, g);
    }

    std::vector roots { A, B, C, D };
    boost::queue<Vertex> queue;
    std::vector<boost::default_color_type> colors(num_vertices(g));
    auto color_map = boost::make_iterator_property_map(begin(colors), get(boost::vertex_index, g));

    Depths depths;
    Recorder r{depths};

    for (auto root : roots)
        boost::breadth_first_search(
            g, root,
            queue, boost::make_bfs_visitor(r), color_map);

    for (auto [v,d] : depths)
        std::cout << g[v] << " at " << d << "\n";
}

打印出来的

TREE EDGE A -> E
TREE EDGE E -> H
TREE EDGE H -> I
TREE EDGE B -> F
TREE EDGE B -> E
TREE EDGE F -> I
TREE EDGE E -> H
TREE EDGE C -> G
TREE EDGE C -> F
TREE EDGE G -> H
TREE EDGE F -> I
TREE EDGE D -> G
TREE EDGE G -> H
TREE EDGE H -> I
E at 1
F at 1
G at 1
H at 2
I at 3

更新

稍作改动以“通过蛮力”检测根:

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <iostream>
#include <map>

using boost::make_iterator_range;
using Name   = char;
using Graph  = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Name>;
using Edge   = Graph::edge_descriptor;
using Vertex = Graph::vertex_descriptor;

using Depths = std::map<Vertex, size_t>;

struct Recorder : public boost::base_visitor<Recorder> {
    using event_filter = boost::on_tree_edge;

    Depths& _ref;
    Recorder(Depths& r):_ref(r){}

    void operator()(Edge e, const Graph& g) const {
        auto depth = 1 + _ref[source(e, g)];

        if (auto [it, isnew] = _ref.emplace(target(e, g), depth); !isnew) {
            it->second = std::max(it->second, depth);
        }
    }
};

int main() {
    enum : Vertex { A, B, C, D, E, F, G, H, I, COUNT };
    Graph g(COUNT);

    // give names
    for (auto v : make_iterator_range(vertices(g)))
        g[v] = 'A' + v;

    // add edges
    for (auto [s,t] : {
            std::pair(D,G), {D, G}, {C, G}, {C, F}, {B, F}, {B, E}, {A, E},
            {G, H}, {F, I}, {E, H},
            {H, I} })
    {
        add_edge(s, t, g);
    }

    boost::queue<Vertex> queue;
    std::vector<boost::default_color_type> colors(num_vertices(g));
    auto color_map = boost::make_iterator_property_map(begin(colors), get(boost::vertex_index, g));

    Depths depths;
    Recorder r{depths};

    for (auto v : make_iterator_range(vertices(g))) {
        boost::breadth_first_search(g, v, queue, boost::make_bfs_visitor(r), color_map);
    }

    std::map<size_t, std::set<Vertex> > by_depth;
    for (auto [v,d] : depths)
        by_depth[d].insert(v);

    for (auto& [d,vs] : by_depth) {
        std::cout << "depth:" << d;
        for (auto v : vs) std::cout << " " << g[v];
        std::cout << "\n";
    }
}

打印

depth:0 A B C D
depth:1 E F G
depth:2 H
depth:3 I

【讨论】:

  • 太棒了!谢谢。你知道是否有一个函数可以返回根顶点还是我必须从boost::vertices中过滤掉它?
  • 您应该能够获得拓扑排序或使用in_degree - 您必须更改图形模型以支持in_degree boost.org/doc/libs/1_72_0/libs/graph/doc/…。但实际上,您基本上可以使用所有根,看看哪个深度为 0。
  • 这是实现的最后一个想法:coliru.stacked-crooked.com/a/b36d8530a9fc100a(就效率而言,这可能不是您能做的最好的)。
  • 也许值得对所有节点重复一遍,而不用担心定位根 - 所有深度为 0 的节点都是隐式根:coliru.stacked-crooked.com/a/2aa15cbe42af20c1更新的答案我想我喜欢它,因为它也简化了记录器。通过增加深度和有序顶点来添加输出。
  • 改写:为什么循环外的boost::breadth_first_search 是必要的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多