图的遍历
定义
从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。
遍历实质:找每个顶点的邻接点的过程。
图的特点:图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。
根据搜索路径的方向,图常用的遍历有深度优先搜索和广度优先搜索
深度优先搜索(DFS)
遍历类似于树的先序遍历,是树先序遍历的推广。注:其结果不唯一
步骤:
1>访问起始点v;
2>若v的第1个邻接点没访问过,深度遍历此邻接点;
3>若当前邻接点已访问过,再找v的第2个邻接点重新遍历。
详细过程:
在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;
再从 w1 出发,访问与 w1邻接但还未被访问过的顶点 w2;
然后再从 w2 出发,进行类似的访问,… 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止。
接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。
如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;
如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。
邻接表表示图的深度优先搜索遍历
void DFS(ALGraph G, int v){ //图G为邻接表类型
cout<<v; visited[v] = true; //访问第v个顶点
p= G.vertices[v].firstarc; //p指向v的边链表的第一个边结点
while(p!=NULL){ //边结点非空
w=p->adjvex; //表示w是v的邻接点
if(!visited[w]) DFS(G, w); //如果w未访问,则递归调用DFS
p=p->nextarc; //p指向下一个边结点
}
}
DFS算法效率的分析
用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,时间复杂度为O(n2)。 用邻接表来表示图,虽然有 2e 个边结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)。
对于DFS算法来说,稠密图适于在邻接矩阵上进行深度遍历; 稀疏图适于在邻接表上进行深度遍历。
广度优先搜索(BFS)
广度优先搜索遍历类似于树的按层次遍历的过程。
步骤:
1>从图的某一结点出发,首先 依次访问该结点的所有邻接顶点,再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点。
2>重复此过程,直至所有顶点均被访问为止。
注:广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。 因此,广度优先搜索不是一个递归的过程,其算法也不是递归的。
详细过程:
(1)从图中某个顶点v出发,访问v,并置visited[v]的值为true,然后将v进队。
(2)只要队列不空,则重复下述处理。
① 队头顶点u出队。
② 依次检查u的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true, 然后将w进队。
void BFS (Graph G, int v){
//按广度优先非递归遍历连通图G
cout<<v; visited[v] = true; //访问第v个顶点
InitQueue(Q); //辅助队列Q初始化,置空
EnQueue(Q, v); //v进队
while(!QueueEmpty(Q)){ //队列非空
DeQueue(Q, u); //队头元素出队并置为u
for(w = FirstAdjVex(G, u); w>=0; w = NextAdjVex(G, u, w))
if(!visited[w]){ //w为u的尚未访问的邻接顶点
cout<<w; visited[w] = true;
EnQueue(Q, w); //w进队
}//if
}//while
}//BFS
BFS算法效率的分析
如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O(n2)。 用邻接表来表示图,虽然有 2e 个边结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)。
图的应用
最小生成树
最小生成树:给定一个无向网络,在该网的所有生成树中,使得个边权值之和最小的那颗生成树称为该网的最小生成树,也叫做最小代价生成树。
构造最小生成树
必须只使用该网中的边来构造最小生成树;
必须使用且仅使用n-1条边来联结网络中的n个顶点;
不能使用产生回路的边。
求最小生成树的算法:普里姆算法和鲁斯卡尔算法
普里姆算法的算法思想
1.设 N = { V, E }是连通网,TE是N上最小生成树中边的集合。
2.初始令U={u0 },(u0∈V),TE={ }。
3.在所有u∈U,v∈V-U的边(u,v)∈E中,找一条代价最小的边(u0,v0) 将(u0,v0)并入集合TE,同时V0并入U。
4.重复上述操作直至U=V为止, 则T=(V,TE)为N的最小生 成树。
鲁斯卡尔算法的算法思想
1.构造一个只有 n 个顶点,没有边的非连通图 T = { V, }, 每个顶点自成一个连通分量
2.在 E 中选最小权值的边,若该边的两个顶点落在不同的连通分量上(即不形成回路), 则加入T 中;否则舍去,重新选择;
3.重复下去,直到所有顶 点在同一连通分量上为止。
稠密图适合普里姆算法,稀疏图适合鲁斯卡尔算法。
最短路径
有向网中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径,即最短路径。
常见的两种最短路径问题:
1.单源最短路径用迪杰斯特拉算法。
2.所有顶点间的最短路径用弗洛伊德算法。
迪杰斯特拉算法
1.初始化:先找出从源点v0到各终点vk的直达路径(v0,vk),即通过一条弧到达的路径。
2.选择:从这些路径中找出一条长度最短的路径(v0,u)。
3.更新:然后对其余各条路径进行适当调整: 若在图中存在弧(u,vk),且(v0,u)+(u,vk)<(v0,vk), 则以路径(v0,u,vk)代替(v0,vk)。 在调整后的各条路径中,再找长度最短的路径,依此类推。
弗洛伊德算法
1.初始化一个n阶方阵,令其对角线的元素为0,若存在弧<vi,vj>,则对应元素为权值。
2.逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之,否则维持原值。所有顶点试探完毕,则算法结束。