主要内容
图的遍历算法是实现图的其他算法的基础,图的遍历方式有两种:深度优先搜索遍历和广度优先搜索遍历。
深度优先搜索遍历类似于树的先序遍历(根节点→左子树→右子树),借助了栈结构以实现递归;广度优先搜索遍历则类似于层次遍历,借助于队列结构实现。
因为图的任意两个顶点都有可能存在联系,所以在访问某个顶点后,可能沿着某条路径又会回到访问过的顶点。为了避免同一顶点被访问多次,在遍历图的过程中,必须用一个数组visited[vexnum]记录每个访问过的顶点。
以邻接矩阵为存储结构的遍历,时间复杂度为O(n²);以邻接表为存储结构的遍历,时间复杂度为O(n+e)。
深度优先搜索
深度优先搜索(Depth First Search,DFS)遍历是树的先序遍历的推广。
遍历过程:
(1)从图中的某个顶点开始访问;
(2)若顶点存在未被访问的邻接顶点,则继续访问它的其中一个邻接顶点;
(3)重复步骤(2)直至某个顶点不存在未被访问的邻接顶点;
(4)从步骤(3)的某个顶点开始回溯访问,若回溯访问的邻接顶点存在未被访问的邻接顶点,则再执行步骤(2)(3)(4);
(5)重复步骤(2)(3)(4)直至所有顶点均被遍历,NOT EXISTS visted[n] 的值为0。
这种一条路走到底,走不通再返回的特征正是“深度”一词的体现。
需要提一下的是,深度优先算法的代码并没有体现“回溯”的过程,“回溯”体现在标记数组visited[vexnum]中,遍历图的所有顶点一个深入、判断、覆盖的过程。
1. 以邻接矩阵为存储结构的深度优先搜索遍历
#define TRUE 1 /*已访问*/
#define False 0 /*未访问*/
void DFS_AMG(AMGraph &G, int v1) /*v1为起始顶点*/
{
int visited[G.vexnum] = {0}; /*定义记录已访问顶点的数组*/
/*定义局部数组时,只将首元素初始化为0,其余元素自动设0*/
cout<<v1; /*以输出语句表示遍历,对顶点进行其他操作则换为函数调用*/
int i = LocateVex(G, v1); /*确定顶点v1对应的编号*/
visited[i] = TRUE; /*记录已访问顶点*/
for(int v2 = 1; v2 <= G.vexnum && v2 != v1; v2++) /*v2图中的另一个顶点*/
{
int j = LocateVex(G, v2); /*同样需要确定v2的编号*/
/*当v1与v2有关联且v2未被访问,递归执行DFS_AMG()*/
if(G.arcs[i][j] != 0 && visited[j] = False)
DFS_AMG(G,v2);
}
}
理解递归过程:
(1)在邻接矩阵中确定起始顶点v1所代表的第i行;
(2)在第i行中找到第一个值不为0的结点,该结点在第j列;
(3)判断visited[j]的值是否为FALSE,若是,将注意力转到第j行;
(4)在第j行中进行与步骤(1)(2)(3)相同的过程;
(5)若第i行中还存在第二个值不为0的结点,该结点在第k列,则在执行完所有从第i行、第j列(arcs[i][j])开始的过程后,再执行从arcs[i][k]开始的所有过程,始终按照“先被访问的顶点的邻接点,先于后被访问的顶点的邻接点”的原则;
(6)在步骤(5)中的顺序执行同样发生在每一个顶点上,即每一行中,所以深度优先遍历是一个深入、判断、覆盖(不断将visited[n]的值填为TRUE)的过程。
提前路过的圈毛君:“虽然已经很努力去解释了,可自己看还是感觉有点拗口,希望大家能看明白叭_(:з」∠)_”
2. 以邻接表为存储结构的深度优先搜索遍历
void DFS_ALG(ALGraph &G, int v1)
{
int visited[G.vexnum] = {0};
cout<<v1;
visited[v1-1] = TRUE; /*第i个顶点vi的编号为i-1*/
/*在邻接矩阵中还需要vi的合理性,所以用LocateVex()函数来求编号*/
ArcNode *p = G.ALs[v1-1].nextarcnode; /*指针p指向头结点v1指向的下一个边结点*/
while(p != NULL) /*若边结点不为空*/
{
int v2 = p->anothervex; /*通过边结点确定v1的一个邻接顶点*/
if(visited[v2-1] = FALSE)
DFS_ALG(G,v2);
p = p->nextarcnode; /*应用链式结构就不需要像邻接矩阵那样在循环语句中一列一列判断,给指针赋值为下一个指针就好*/
}
}
理解代码的思路跟上面类似。
由深度优先的遍历过程可以得到一棵以起始顶点v1为根结点的树,这棵树称作深度优先生成树。
广度优先搜索
广度优先搜索(Breadth First Search,BFS)遍历类似于树的层次遍历。
广度优先的过程与深度优先类似,唯一的区别是广度优先是一层一层地遍历,而不会从某个顶点开始一直往下遍历,直到不符合遍历的条件。
广度优先以横向遍历为先,即访问顶点v1后,顺序访问v1的邻接顶点v2,v3,然后先顺序访问2的邻接顶点,再顺序访问v3的邻接顶点,如此类推。
深度优先利用栈结构实现递归,广度优先则需要队列来实现每一层的顺序执行:
(1)v1入队,访问顶点v1,并在visited[vexnum]将顶点v1对应的元素值归TRUE;
(2)检查v1的邻接顶点,v2和v3顺序入队;
(3)v1出队;
(4)访问顶点v2,检查v2的邻接顶点并使它们顺序入队;
(5)v2出队,访问顶点v3;
(6)访问顶点v3,检查v3的邻接顶点并使它们顺序入队;
(7)之后的入队、出队过程如此类推。
*利用队列“先来先服务”(FCFS)的性质,在编写代码时可以将思路转化为先求队列,再遍历。
1. 以邻接矩阵为存储结构的广度优先搜索遍历
int visited[G.vexnum]; /*全局变量和静态变量初始化时会自动被设置为0*/
LinkQueue Q; /*定义链队Q*/
InitQueue(Q); /*初始化链队Q*/
void AL_EnQueue(LinkQueue &Q, int v1) /*从顶点v1开始,将所有顶点逐个入队*/
{
int i = LocateVex(G, v1);
for(int v2 = 1; v2 <= G.vexnum; v2++) /*先使v1未被访问的邻接顶点顺序入队*/
{
int j = LocateVex(G, v2);
if(G.arcs[i][j] != 0 && visited[j] = FALSE)
{
EnQueue(Q, v2);
visited[j] = TRUE; /*标记*/
}
}
for(int v2 = 1; v2 <= G.vexnum; v2++) /*再在v1的邻接顶点上递归*/
{
int j = LocateVex(G, v2);
if(G.arcs[i][j] != 0 && visited[j] = FALSE)
AL_QnQueue(Q, v2);
}
}
void BFS_AMG(AMGraph &G, int v1)
{
EnQueue(Q, v1); /*v1入队*/
int i = LocateVex(G, v1);
visited[i] = TRUE;
AL_EnQueue(Q, v1); /*所有顶点入队*/
cout<<GetHead(Q); /*访问链队的头结点*/
DeQueue(Q); /*头结点出队*/
}
2. 以邻接表为存储结构的广度优先搜索遍历
int visited[G.vexnum];
LinkQueue Q;
InitQueue(Q);
void AL_EnQueue(LinkQueue &Q, int v1)
{
ArcNode *p = G.ALs[v1-1].nextarcnode;
while(p != NULL)
{
int v2 = p->anothervex;
if(visited[v2-1] = FALSE)
EnQueue(Q, v2);
p = p->nextarcnode;
}
while(p != NULL)
{
int v2 = p->anothervex;
if(visited[v2-1] = FALSE)
AL_EnQueue(Q, v2);
p = p->nextarcnode;
}
}
void BFS_AMG(AMGraph &G, int v1)
{
EnQueue(Q, v1);
visited[v1-1] = TRUE;
AL_EnQueue(Q, v1);
cout<<GetHead(Q);
DeQueue(Q);
}
由深度优先的遍历过程可以得到一棵以起始顶点v1为根结点的树,这棵树称作深度优先生成树。