前面我们探讨了如何把一个抽象的图具体化的用代码去实现,讨论了图的邻接矩阵表
示法和邻接表表示法,那么这一小节我们就来探讨一下图算法中最常见的操作,如何
去遍历图中的各个节点相对应的临边呢?
那么,通过一个点怎么去遍历与这个点相连的临边呢?例如,下图是图中一个节点与
其相邻的各个节点,我们需要遍历0节点的邻节点。
首先,对于邻接矩阵表示的图来说,我们只需要从表中遍历一遍0元素与其他元素
之间的映射关系,即它们之间是否存在着边的关系,如果值为1则代表两节点之间存
在着边关系,如果值为0,则表示两边之间不存在。例如下图对应的邻接矩阵所示:
其中,与0节点相连的节点(即对应值为1的节点有3,5,8),我们只需要遍历一下
就可以找出存在边关系的节点了。因此,此时的遍历的复杂度为O(V)即节点数决定。
而对于邻接表实现的图来说,它对应的结构如下图所示:
此时我们只需要直接把链表中的节点全部遍历即可。
下面我们来具体的设计一下如何去遍历一个图中每个节点与之相关的节点。我们之所以
设计成一个迭代器,是应用了设计模式的思想,并且防止外部直接访问图的内部数据从
而可能发生数据被更改的情况,安全性不高。
首先我们来看一个简单的迭代器的使用方法:
for(int w=adj.begin();!adj.end();w=adj.next()) cout<<w<<" "; cout<<endl;
假设adj是一个数组,因此这是一个数组对应的迭代器,我们通过迭代器可以把数组中
的元素给遍历一遍,同理,图一样可以设计出相对应的迭代器。
首先让我们来看稀疏图:
对于稀疏图来说,我们在稀疏图中建立一个子类来表示迭代器,这样迭代器就既有安全
性,又因为子类的性质可以直接访问到图类中的各个数据成员,因此,我们只需要完
成迭代器之类就好了。
我们看到了上图中的迭代器,需要实现begin(),end(),next()这三个方法,因
此下面是我们的具体实现:
class adjIterator {//构建稀疏图迭代器 private: SparseGraph &G;//存储图的引用 int v; int index;//index起指示作用,指向目前迭代器正在访问的节点 public: adjIterator(SparseGraph &graph, int v) : G(graph) {//传入图的引用以及
需要遍历的节点 this->v = v; this->index = 0; } ~adjIterator() { } int begin() { index = 0; if (G.g[v].size())//如果需要遍历的图中的v节点存在着邻边,则返回
邻边表中的第一个值 return G.g[v][index]; return -1; } int next() {//从当前迭代的元素向下一个元素移动 index++; if (index < G.g[v].size())//确保访问时不会发生越界 return G.g[v][index];//返回需要的元素 return -1; } bool end() {//判断迭代是否要结束了 return index >= G.g[v].size();//目前访问的节点的索引没有 // 超过邻边表中的最大索引值,则没结束 } }; };
下面我们来看一下具体的迭代器的调用方法,我们在main函数中随机生成一个有20个
节点,100条边的稀疏图,然后使用循环调用每一个节点与之对应的迭代器。
int N=20; int M=100;//生成一个有20个节点,100条边的图 srand(time(NULL)); //sparseGraph SparseGraph g1(N, false); for(int i=0;i<M;i++){//对图中存在的节点进行随机的“连边”操作 int a=rand()%N; int b=rand()%N; g1.addEdge(a,b); } for(int v=0;v<N;v++){//对图中的每一个节点都使用迭代器进行迭代 cout<<v<<": "; SparseGraph::adjIterator adj(g1,v);//生成节点对应的迭代器 for(int w=adj.begin();!adj.end();w=adj.next())//调用生成的节点迭代器的三种方法 cout<<w<<" "; cout<<endl; }
下面是实现的迭代效果:
我们上一节讲过一个问题,也是邻接表表示方法的缺点,即表中的相邻节点可能会发生
重复现象,这在上图中给我们可以很容易的证实这个结论。
同理,对于邻接矩阵的迭代器设计也是相类似的,但是因为邻接矩阵的结构不同,
迭代器虽然大体框架是一样的,但是内部的实现需要做一些小小的改动。
下面是邻接矩阵的迭代器实现代码:
class adjIterator{//构建稠密图的相邻节点迭代器 private: DenseGraph &G;//需要进行操作的图的引用 int v;//需要遍历的节点 int index;//index指向迭代器正在访问的元素 public: adjIterator(DenseGraph&graph,int v):G(graph){//生成一个迭代器的构造函数 assert(v>=0&&v<G.n); this->index=-1; this->v=v; } ~adjIterator(){}; //因为邻接矩阵结构的特殊性,即邻接矩阵中的一个节点其关联链表中的元素并不是都有效 //必须值为true才有效,因此并不能保证直接从第一个元素开始访问,所以index初始化为-1 int begin(){ index=-1; return next(); } int next(){ for(index+=1;index<G.V();index++){ if(G.g[v][index]==true){//只把真正有效的元素返回出去 return index; } } return -1; } bool end(){ return index>=G.V();//判断是否遍历完成 } };
我们相同的在main函数中加入对邻接矩阵迭代器的测试代码:
//DenseGraph DenseGraph g2(N, false); for(int i=0;i<M;i++){ int a=rand()%N; int b=rand()%N; g2.addEdge(a,b); } for(int v=0;v<N;v++){ cout<<v<<": "; DenseGraph::adjIterator adj2(g2,v); for(int w=adj2.begin();!adj2.end();w=adj2.next()){ cout<<w<<" "; } cout<<endl; }
相应的看一下测试的结果:
我们可以看到,邻接矩阵虽然比邻接表更占空间,但是邻接矩阵中的每一个节点的邻边节点不会重复。
如需访问此时的全部源代码,请点击此处。