赞
踩
在前面我们知道,树是一种非线性结构,为了方便它在计算机中的存储,对树进行遍历使它线性化。
而图同样也是一种非线性结构,但是图又是一种不同于树的多对多结构,所以在前面我们将其转换为了多个一对多的结构来描述它的存储结构。
图的遍历同树类似,也是从某一个顶点出发,按照某种方法对图中所有顶点访问且仅访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
因为图的任一顶点都可能和其余的顶点相邻接,为了避免同一顶点被访问多次,在遍历图的过程中,必须记下每个已访问过的顶点。
通常有两条遍历的图的路径:深度优先搜索和广度优先搜索。
深度优先搜索(Depth First Search,DFS)遍历类似于树的先序遍历。我们也可以理解一下深度的概念,也就是树中深度的概念,但这需要图要有跟树相似的形状。何谓深度优先?就是从某个顶点一直往下,先不管与它处于同一层次的其它顶点。
对于一个连通图,深度优先搜索遍历的过程如下:
下面举一个例子,来说明该搜索方法,如下图:
如果我们要遍历该图,按照上述的步骤进行,那么即:
因为我们遍历是求解图的连通性问题,若访问结束没有未被访问的结点,则是连通图;若有,则是非连通图。
对于上述深度优先搜索的结果,我们可以构造一棵以 v 1 v_{1} v1为根的树,称为深度优先生成树,如下图:
由于遍历的过程需要判断搜索的顶点是否已被访问,所以我们应该想办法在搜索的时候判断每个是否已被访问。已知,每个顶点只有两种状态:已被访问和未被访问。所以如果该顶点未被访问,只需将其状态改为已被访问就行。而该判断方法比较简单,所以我们可以考虑用一维数组visited来存储每个顶点的状态。若未被访问,则其值为false;若已访问,则其值为true。在这里可以使用布尔类型来实现。 → \rightarrow →布尔数据类型
算法分析:
代码实现:
bool visited[MVNum];
void DFS(Graph G,int v)
{
printf("%d",&v); //打印出每个顶点,方便最后检查是否全部顶点已被访问
visited[v]=ture;
for(int w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w))
if(!visited[w]) //如果该邻接点未被访问
DFS(G,w);
}
其中FirstAdjVex和NextAdjVex函数要根据具体的图的存储结构来设计。
算法分析:
代码实现:
void DFSTraverse(Graph G)
{
for(int v=0;v<G.vexnum;v++)
visited[v]=false;
for(int v=0;v<G.vexnum;v++)
if(!visited[v])
DFS(G,v);
下面分别采用两种不同的图结构来实现深度优先搜索。有了上述过程的理解,我们直接给出代码实现。
void DFS_AM(AMGraph G,int v)
{
for(int i=0;i<vexnum;i++)
visited[i]=false;
printf("%d ",&v);
visited[v]=true;
for(w=0;w<G.vexnum;w++)
if((G.arcs[v][w]!=0)&&!visited[w])
DFS_AM(G,w);
}
void DFS_AL(ALGraph G,int v) { ArcNode *p; for(int i=0;i<G.vexnum;i++) visited[i]=false; printf("%d ",&v); visited[v]=true; p=G.vertices[v].firstarc; while(p!=NUll) { w=p->adjvex; if(!visited[w]) DFS_AL(G,w); p=p->nextarc; } }
在遍历图时,对图中每个顶点至多调用依次DFS函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费时间取决于所采用的存储结构。
广度优先搜索和深度优先搜索正好相反,如果说深度优先搜索是“竖”着来,那么广度优先搜索就是“横”着来。
该过程类似于树的按层次遍历
下图为对无向图G4广度优先搜索遍历的一个示例过程:
由此我们得到的访问序列为:
v
1
→
v
2
→
v
3
→
v
4
→
v
5
→
v
6
→
v
7
→
v
8
v_{1}\rightarrow v_{2}\rightarrow v_{3}\rightarrow v_{4}\rightarrow v_{5}\rightarrow v_{6}\rightarrow v_{7}\rightarrow v_{8}
v1→v2→v3→v4→v5→v6→v7→v8
而上述图也可作为广度优先搜索的一棵广度优先生成树。
算法分析:
代码实现:
typedef struct { QElemType *base; int front; int rear; }SqQueue; void InitQueue(SqQueue &q) { base=(QElemType*)malloc(MVNum*sizeof(QElemType)); q.front=q.rear=0; } void EnQueue(SqQueue &q,QElemType e) { if(q.rear+1)%MVNum=q.front; return; q.base[q.rear]=e; q.rear=(q.rear+1)%MVNum; } void DeQueue(SqQueue &q,QElemType &e) { if(q.front==q.rear) return; e=q.base[q.front]; q.front=(q.front+1)%MVNum; } bool visited[MVNum]; void BFS(Graph G,int v) { for(int i=0;i<G.vexnum;i++) visited[i]=false; printf("%d ",&v); visited[v]=true; Initiate(Q); Enqueue(Q,v); while(!QueueEmpty(Q)) //队列非空 { Dequeue(Q,u); //队头元素置为u for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex) if(!visited[w]) { printf("%d ",&w); visited[w]=true; Enqueue(Q,w); } } }
根据上述算法,每个顶点至多进一次队列。遍历图的过程实质上是通过边找邻接点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同。
图的遍历其实并不难,关键是我们知道为什么要去遍历图:是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
遍历图可以从两个方向去遍历,即深度和广度。顾名思义,深度就是从上到下,广度就是从左到右。但深度又不是单纯的从上到下,它是从某个顶点出发,延该顶点的第一个邻接点及该邻接点的第一个邻接点等等继续往下延伸,形成了一条延伸链,直到该延伸链最下面的那个顶点没有未被访问的邻接点,最后再由该延伸链向上回溯。而广度,是要把图看成树状的,一层一层从左到右地去遍历。
对于不同的图的结构,遍历的算法又不同,因此因根据实际情况来操作。
这两个遍历方法并没有好坏之分,只是对于顶点访问的顺序不同。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。