使用基于范围的for迭代图的边缘
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的经验。但是我花了大约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 neighborsn";
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 edgesn";
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
,您不能在边缘上使用基于范围的for,但这只是语法糖,您可以通过定义自己的开始/结束函数来尝试修复。重要的是你可以直接遍历边
boost_adjacency_list
数据结构为您提供了定义良好的时间和空间复杂度的边和顶点操作。上面的代码只是复制了您的示例,而不知道您真正需要哪种操作。更改底层容器允许您对应用程序进行适当的权衡。 无耻的广告机会!我有一个项目 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();
我相信你的图形的内部表示,std::vector<std::unordered_set<Vertex>>
,是什么使代码难以编写/读取。也许另一种表示(例如std::set<std::pair<Vertex, Vertex>>
)会使您的代码更简单。然而,很难说,因为我们不知道Graph
的确切要求是什么。
无论如何,正如Zeta指出的那样,EdgeIter::operator !=()
有一个bug。例如,下面的代码:
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;
}
- 基于范围的 for 循环:迭代使用一个元素扩展的向量
- 将多个 for 循环组合成单个迭代器
- 为什么我的 scanf() 没有在我的数组上迭代我的 for 循环?
- 使用一个 for 循环如何迭代和测试迭代器值?
- 二进制表达式的操作数无效 - 使用 for 和迭代器
- 转到基于范围的 for 循环中的下一个迭代器
- C++:返回一个基于范围 for 循环迭代器,其中包含继承对象
- C++ Code 在 for 循环期间不会累积变量中的总和,仅提供最终迭代值
- 在C++中使用 for 循环的数组迭代
- 迭代的"range-based for"召唤
- 从基于迭代器的for循环转换后,如何在map::find()中调用方法
- 如何使用基于范围的for循环迭代Rapidjson文档(它本身就是一个JSON数组)
- 编译时二叉搜索树错误的反向迭代器表示"no matching function call for operator=()"
- 在for循环的某些迭代中,字符串的长度为0,而在其他迭代中则不为0
- 如何在多次迭代后停止for循环
- 在指针迭代中使用for循环时出现分段错误
- 为什么每次“for”迭代都会输出 4 次
- 使用for迭代QMap
- 跳过当前' for '迭代的其余部分,开始一个新的迭代
- 使用基于范围的for迭代图的边缘