Подтвердить что ты не робот

Итерация по краям графика с использованием диапазона для

У меня есть представление графика как 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.

Однако все, что я пробовал, кажется чрезвычайно сложным, лучшая версия, которую я могу придумать, имеет 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;
}
4b9b3361

Ответ 1

Я настоятельно рекомендую использовать библиотеку 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, вы не можете использовать диапазон по умолчанию для на краях, но это только синтаксический сахар, который вы можете попробуйте восстановить, определив свои собственные функции begin/end. Важно то, что вы можете напрямую перебирать края.

Обратите внимание, что структура данных boost_adjacency_list предоставляет вам операции с краем и вершиной четко определенная сложность времени и пространства. В приведенном выше коде просто воспроизводится ваш пример, не зная, какие операции вы действительно хотите. Изменение базовых контейнеров позволяет вам соответствующим образом компрометировать ваше приложение.

Ответ 2

Я считаю, что ваше внутреннее представление графика std::vector<std::unordered_set<Vertex>> - это то, что делает код трудным для записи/чтения. Возможно, другое представление (например, std::set<std::pair<Vertex, Vertex>>) сделает ваш код более простым. Однако это трудно сказать, так как мы не знаем точно, каковы требования Graph.

В любом случае, как указано Zeta​​strong > , есть ошибка в EdgeIter::operator !=(). Например, код ниже:

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

выводит false. Следовательно, код считает, что i1 и i2 не отличаются друг от друга, когда они явно.

Update:

Это, вероятно, очевидно, но вот более простая версия, которая использует другое представление для графика. Однако я подчеркиваю, что это может быть неудовлетворительным в зависимости от ваших требований к Graph (что я не знаю):

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

Ответ 3

Возможность для бесстыдного плагина! У меня есть проект linq-cpp для обеспечения функциональности .NET LINQ для С++ 11, и это идеальный пример, где он действительно светит.

Используя его, вы можете написать такую ​​функцию, как:

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

И затем используйте его следующим образом:

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

Или может быть так:

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