[英]Iterating over edges of a graph using range-based for
我有一個圖形表示為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;
}
我強烈建議使用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;
}
}
當然,因為boost::edges
返回一個std::pair
,你不能在邊緣使用基於范圍的 ,但這只是你可以通過定義自己的開始/結束函數來修復的語法糖。 重要的是你可以直接迭代邊緣。
請注意, boost_adjacency_list
數據結構為您提供定義明確的時間和空間復雜度的邊緣和頂點操作。 上面的代碼只是重現你的例子,而不知道你真正想要什么樣的操作。 更改底層容器允許您根據應用程序進行權衡。
我相信你的圖形的內部表示, std::vector<std::unordered_set<Vertex>>
,是使代碼難以編寫/讀取的原因。 也許另一種表示(例如std::set<std::pair<Vertex, Vertex>>
)會使你的代碼更簡單。 但是,由於我們不確切知道Graph
的要求是什么,因此很難說清楚。
無論如何,正如Zeta所指出的那樣, 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
在它們清楚的時候並沒有不同。
更新:
這可能是顯而易見的,但這里是一個更簡單的版本,它使用了不同的圖形表示。 但是,我強調這可能不太令人滿意,這取決於您對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;
}
一個無恥插頭的機會! 我有一個項目linq-cpp,用於將.NET LINQ功能引入C ++ 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();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.