图的遍历
图的遍历和树的遍历类似,希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫图的遍历。
对于图的遍历来说,如何避免因回路陷入死循环,就需要科学地设计遍历方案,通过有两种遍历次序方案:深度优先遍历和广度优先遍历。
1. 深度优先遍历
深度优先遍历,也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。
它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。
若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。
我们用邻接矩阵的方式,则代码如下所示。
#define MAXVEX 100 //最大顶点数
typedef int Boolean; //Boolean 是布尔类型,其值是TRUE 或FALSE
Boolean visited[MAXVEX]; //访问标志数组
#define TRUE 1
#define FALSE 0
//邻接矩阵的深度优先递归算法
void DFS(Graph g, int i)
{
int j;
visited[i] = TRUE;
printf("%c ", g.vexs[i]); //打印顶点,也可以其他操作
for(j = 0; j < g.numVertexes; j++)
{
if(g.arc[i][j] == 1 && !visited[j])
{
DFS(g, j); //对为访问的邻接顶点递归调用
}
}
}
//邻接矩阵的深度遍历操作
void DFSTraverse(Graph g)
{
int i;
for(i = 0; i < g.numVertexes; i++)
{
visited[i] = FALSE; //初始化所有顶点状态都是未访问过状态
}
for(i = 0; i < g.numVertexes; i++)
{
if(!visited[i]) //对未访问的顶点调用DFS,若是连通图,只会执行一次
{
DFS(g,i);
}
}
}
-----------------------------------------------
如果使用的是邻接表存储结构,其DFSTraverse函数的代码几乎是相同的,只是在递归函数中因为将数组换成了链表而有不同,代码如下。
//邻接表的深度递归算法
void DFS(GraphList g, int i)
{
EdgeNode *p;
visited[i] = TRUE;
printf("%c ", g->adjList[i].data); //打印顶点,也可以其他操作
p = g->adjList[i].firstedge;
while(p)
{
if(!visited[p->adjvex])
{
DFS(g, p->adjvex); //对访问的邻接顶点递归调用
}
p = p->next;
}
}
//邻接表的深度遍历操作
void DFSTraverse(GraphList g)
{
int i;
for(i = 0; i < g.numVertexes; i++)
{
visited[i] = FALSE;
}
for(i = 0; i < g.numVertexes; i++)
{
if(!visited[i])
{
DFS(g, i);
}
}
}
对比两个不同的存储结构的深度优先遍历算法,对于n个顶点e条边的图来说,邻接矩阵由于是二维数组,要查找某个顶点的邻接点需要访问矩阵中的所有元素,因为需要O(n2)的时间。而邻接表做存储结构时,找邻接点所需的时间取决于顶点和边的数量,所以是O(n+e)。显然对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提高。
-------------------------------------------------------------------------------------
2. 广度优先遍历
广度优先遍历,又称为广度优先搜索,简称BFS。图的广度优先遍历就类似于树的层序遍历了。
邻接矩阵做存储结构时,广度优先搜索的代码如下。
//邻接矩阵的广度遍历算法
void BFSTraverse(Graph g)
{
int i, j;
Queue q;
for(i = 0; i < g.numVertexes; i++)
{
visited[i] = FALSE;
}
InitQueue(&q);
for(i = 0; i < g.numVertexes; i++)//对每个顶点做循环
{
if(!visited[i]) //若是未访问过
{
visited[i] = TRUE;
printf("%c ", g.vexs[i]); //打印结点,也可以其他操作
EnQueue(&q, i); //将此结点入队列
while(!QueueEmpty(q)) //将队中元素出队列,赋值给
{
int m;
DeQueue(&q, &m);
for(j = 0; j < g.numVertexes; j++)
{
//判断其他顶点若与当前顶点存在边且未访问过
if(g.arc[m][j] == 1 && !visited[j])
{
visited[j] = TRUE;
printf("%c ", g.vexs[j]);
EnQueue(&q, j);
}
}
}
}
}
}
1
------------------------------------------
对于邻接表的广度优先遍历,代码与邻接矩阵差异不大, 代码如下。
//邻接表的广度遍历算法
void BFSTraverse(GraphList g)
{
int i;
EdgeNode *p;
Queue q;
for(i = 0; i < g.numVertexes; i++)
{
visited[i] = FALSE;
}
InitQueue(&q);
for(i = 0; i < g.numVertexes; i++)
{
if(!visited[i])
{
visited[i] = TRUE;
printf("%c ", g.adjList[i].data); //打印顶点,也可以其他操作
EnQueue(&q, i);
while(!QueueEmpty(q))
{
int m;
DeQueue(&q, &m);
p = g.adjList[m].firstedge; 找到当前顶点边表链表头指针
while(p)
{
if(!visited[p->adjvex])
{
visited[p->adjvex] = TRUE;
printf("%c ", g.adjList[p->adjvex].data);
EnQueue(&q, p->adjvex);
}
p = p->next;
}
}
}
}
}
对比图的深度优先遍历与广度优先遍历算法,会发现,它们在时间复杂度上是一样的,不同之处仅仅在于对顶点的访问顺序不同。
可见两者在全图遍历上是没有优劣之分的,只是不同的情况选择不同的算法。