c++ - 使用基于范围的 for 迭代图的边缘

标签 c++ graph c++11 foreach boost-graph

我将图形表示为 std::vector<std::unordered_set<unsigned>> neighbors ,也就是说,顶点是整数,对于每个顶点,我们保留一组它的邻居。因此,要遍历所有边缘,我会做类似的事情

for (unsigned u = 0; u < neighbors.size(); ++u)
    for (unsigned v : neighbors[u])
        if (u <= v)
            std::cout << u << ' ' << v << std::endl;

现在,我希望能够从中获得同样的效果

for (auto e: g.edges())
    std::cout << e.first << ' ' << e.second << std::endl;

哪里g来自封装 neighbors 的类 vector 。

然而,我尝试的一切似乎都极其复杂,我能想出的最好的版本有 50 行,而且很难看出它是正确的。有没有简单的方法可以做到这一点?

这是我丑陋的版本:

#include <iostream>
#include <unordered_set>
#include <vector>
typedef unsigned Vertex;
class Graph {
public:
    typedef std::unordered_set<Vertex> Neighbors;
    std::size_t numVertices() const { return neighbors_.size(); }
    Graph(std::size_t n = 0) : neighbors_(n) { }
    void addEdge(Vertex u, Vertex v) {
        neighbors_[u].insert(v);
        neighbors_[v].insert(u);
    }
    class EdgeIter {
        friend Graph;
    public:
        bool operator!=(const EdgeIter& other) { return u_ != other.u_; }
        void operator++() {
            do {
                ++it_;
                while (it_ == it_end_) {
                    u_++;
                    if (u_ >= neighbors_.size())
                        break;
                    it_     = neighbors_[u_].cbegin();
                    it_end_ = neighbors_[u_].cend();
                }
            } while (u_ < neighbors_.size() && *it_ < u_);
        }
        std::pair<Vertex, Vertex> operator*() { return {u_, *it_}; }
    private:
        EdgeIter(const std::vector<std::unordered_set<Vertex> >& neighbors, size_t u)
            : u_(u), neighbors_(neighbors) {
            if (u < neighbors_.size()) {
                it_     = neighbors_[u_].cbegin();
                it_end_ = neighbors_[u_].cend();
                while (it_ == it_end_) {
                    u_++;
                    if (u_ >= neighbors_.size())
                        break;
                    it_     = neighbors_[u_].cbegin();
                    it_end_ = neighbors_[u_].cend();
                }
            }
        }
        Vertex u_;
        const std::vector<std::unordered_set<Vertex> >& neighbors_;
        std::unordered_set<Vertex>::const_iterator it_, it_end_;
    };
    EdgeIter edgesBegin() const { return EdgeIter(neighbors_, 0); }
    EdgeIter edgesEnd()   const { return EdgeIter(neighbors_, neighbors_.size()); }
    class Edges {
    public:
        Edges(const Graph& g) : g_(g) { }
        EdgeIter begin() const { return g_.edgesBegin(); }
        EdgeIter end  () const { return g_.edgesEnd();   }
    private:
        const Graph& g_;
    };
    Edges edges() { return Edges(*this); }
    std::vector<Neighbors> neighbors_;
};
int main() {
    Graph g(5);
    g.addEdge(1, 2);
    g.addEdge(2, 3);
    g.addEdge(1, 3);    
    for (unsigned u = 0; u < g.numVertices(); ++u)
        for (unsigned v : g.neighbors_[u])
            if (u <= v)
                std::cout << u << ' ' << v << std::endl;
    for (auto e: g.edges())
        std::cout << e.first << ' ' << e.second << std::endl;
}

最佳答案

我强烈建议使用 Boost.Graph用于此类计算的库。主要原因是图形是复杂的数据结构,您可以在其上运行更复杂的算法。即使您自己手工制作的数据结构可以正常工作,它也可能无法高效运行(在空间/时间复杂度方面)并且可能不支持您的应用程序所需的算法。

作为这个库的可访问性的一个指示:我之前没有使用 Boost.Graph 的经验,但是我花了大约 30 分钟才想出以下 30 行代码来完全重现您的示例。

#include <iostream>
#include <iterator>
#include <boost/graph/adjacency_list.hpp>

typedef unsigned V;
typedef std::pair<V, V> E;

// store neighbors in a std::set, vertices in a std::vector
typedef boost::adjacency_list<boost::setS, boost::vecS> Graph;

int main()
{
   // construct the graph
   E e[] = { E(1,2), E(2,3), E(1,3) };
   Graph g(std::begin(e), std::end(e), 5);

   std::cout << "iterate over vertices, then over its neighbors\n";
   auto vs = boost::vertices(g);
   for (auto vit = vs.first; vit != vs.second; ++vit) {
       auto neighbors = boost::adjacent_vertices(*vit, g);
       for (auto nit = neighbors.first; nit != neighbors.second; ++nit)
           std::cout << *vit << ' ' << *nit << std::endl;
   }

   std::cout << "iterate directly over edges\n";
   auto es = boost::edges(g);
   for (auto eit = es.first; eit != es.second; ++eit) {
       std::cout << boost::source(*eit, g) << ' ' << boost::target(*eit, g) << std::endl;
   }
}

LiveWorksSpace 上输出

当然,因为 boost::edges 返回一个 std::pair,你 can't use range-based for在边缘,但这只是语法糖,您可以尝试通过定义自己的开始/结束函数来修复它。重要的是您可以直接迭代边。

请注意,boost_adjacency_list 数据结构为您提供了 well-defined time and space complexity 的边和顶点操作。 .上面的代码只是在不知道您真正想要什么样的操作的情况下重现您的示例。更改底层容器允许您对您的应用程序进行适当的权衡。

关于c++ - 使用基于范围的 for 迭代图的边缘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15812663/

相关文章:

c++ - 有没有办法在 C++ 中捕获所有异常?

c++ - 如何在 Qt 中制作工作音量 slider ?

c++ - std::thread通过引用传递调用复制构造函数

C++11 我可以确保 condition_variable.wait() 不会错过通知吗?

c++ - 'TypeInfo<char>(char * )' isn' t defined but worked pre-C++11; what changed, and how can I fix the error?

c++ - 使用 std::unordered_set 观察多个对象

c++ - 此 getline 测试在 MSVC 2012 中有效,但在 MSVC 2015 中出错,如何修复?

java - 绘图功能

algorithm - 查找具有最小边交叉分区的图分区

r - 将条形面板放置在地 block 的相对两侧