Iterating over edges of a graph using range-based for

回眸只為那壹抹淺笑 提交于 2019-12-03 12:26:35
TemplateRex

I strongly recommend using the Boost.Graph library for such computations. The main reason is that graphs are complicated data structures on which you can run even more complicated algorithms. Even if your own hand-made data structure works correctly, it is likely not to run efficiently (in terms of space/time complexity) and may not support the algorithms that your applications needs.

As an indication on how accessible this library is: I had no prior experience with Boost.Graph, but it took about 30 minutes to come up with the following 30 lines of code that completely reproduces your example.

#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;
   }
}

Output on LiveWorksSpace

Granted, because boost::edges returns a std::pair, you can't use range-based for on the edges, but that's only syntactic sugar which you can try to repair by defining your own begin/end functions. What's important is that you can iterate over edges directly.

Note that the boost_adjacency_list data structure provides you with edge and vertex operations of well-defined time and space complexity. The code above merely reproduces your example without knowing what kind of operations you really want. Changing the underlying containers allows you to make tradeoffs appropriately to your application.

An opportunity for a shameless plug! I have a project linq-cpp for bringing .NET LINQ functionality to C++11, and this is a perfect example for where it really shines.

Using it, you could write a function like the following:

TEnumerable<std::pair<int, int>> EnumerateEdges(std::vector<std::unordered_set<int>>& neighbors)
{
    return Enumerable::FromRange(neighbors)
        .SelectManyIndexed([](std::unordered_set<int>& bNodes, int aNode)
        {
            return Enumerable::FromRange(bNodes)
                .Select([=](int bNode){ return std::make_pair(aNode, bNode); });
        });
}

And then use it like this:

EnumerateEdges(neighbors).ForEach([](std::pair<int, int> edge)
{
    /* your code */
});

Or maybe like this:

auto edges = EnumerateEdges(neighbors).ToVector();

I believe that your internal representation of a graph, std::vector<std::unordered_set<Vertex>>, is what makes the code hard to write/read. Maybe another representation (e.g. std::set<std::pair<Vertex, Vertex>>) would make your code simpler. However, it's hard to tell since we don't know exactly what are the requirements of Graph.

Anyway, as pointed out by Zeta there's a bug in EdgeIter::operator !=(). For instance, the code below:

int main() {

    Graph g(5);
    g.addEdge(0, 1);
    g.addEdge(0, 2);

    auto i1 = g.edges().begin();
    auto i2 = i1;
    ++i2;
    std::cout << std::boolalpha;
    std::cout << (i1 != i2) << std::endl;
}

outputs false. Hence, the code considers that i1 and i2 are not different when they clearly are.

Update:

It's probably obvious but here is a simpler version which uses a different representation for the graph. However, I emphasize that this may not be satisfactory depending on your requirements for Graph (which I don know):

#include <set>
#include <stdexcept>
#include <iostream>

typedef unsigned Vertex;

class Graph {

public:

    typedef std::pair<Vertex, Vertex> Edge;
    typedef std::set<Edge> Edges;

    void addEdge(Vertex u, Vertex v) {
      edges_.insert({u, v});
    }

    const Edges& edges() { return edges_; }

private:

    Edges edges_;
};

int main() {

    Graph g;

    g.addEdge(1, 2);
    g.addEdge(2, 3);
    g.addEdge(1, 3);    

    for (auto e: g.edges())
        std::cout << e.first << ' ' << e.second << std::endl;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!