图的遍历
题记:
小伙伴都开学了,我也想开学。
放平心态,稳住不慌,平心静气,过好接下来的每一天,90天倒计时,该休息就休息,该学习就学习,努力向前走,会有你想要一切!加油!
例题代码
图的邻接矩阵定义
#define Max_Vertex_Num 100 //顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct{
VertexType Vex[Max_Vertex_Num]; //顶点表
EdgeType Edge[Max_Vertex_Num][Max_Vertex_Num]; //邻接矩阵,边表
int vexnum,arcnum; //图的顶点数和弧数
}MGraph;
图的邻接表定义
#define Max_Vertex_Num 100 //顶点数目的最大值
typedef struct ArcNode{ //边表结点
int adjvex; //该弧指向的顶点位置
struct ArcNode *next; //指向下一条弧的指针
InfoType info; //网的边权值
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
}VNode,AdjList[Max_Vertex_Num];
typedef struct{ //以邻接表存储的图的类型
AdjList vertices; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
}ALGraph;
从图的邻接表转换成邻接矩阵表示的算法
分析:设图的顶点存储在数组 v[n] 中,初始化邻接矩阵,依次遍历顶点 v[i] 的边链表,修改邻接矩阵第 i 行的元素,若链表边结点的值为 j,修改arcs[ i ] [ j ]=1。
邻接表遍历完,转换结束。适用有向图、无向图。
void Convert(ALGraph &G,int arcs[M][N]){
for(int i=0;i<n;i++){ //依次遍历各顶点表结点为头的边链表
p=(G->v[i]).firstarc; //取出顶点i的第一条出边
j=p->data;
while(p!=NULL){ //遍历边链表
arcs[i][j]=1;
p=p->nextarc; //指向下一条边
}
}
}
广度优先搜索 BFS(类似于树的层次遍历算法)
(1)算法思想:
访问起始顶点v 由v出发依次访问v各个未访问的邻接顶点w1,w2,w3…… 接着访问w1,w2,w3…… 所有未被访问过的邻接点;
再从这些访问过的顶点出发,访问它们未被访问过的邻接点,直到图中的所有顶点都被访问过为止。
若此时图中尚有顶点未被访问,另选图中一个未曾被访问的顶点作为始点,重复上述过程,直至图中所有顶点都被访问完。
【以v为起始点,由近到远依次访问和v有路径相通且路径长度为1,2,……的顶点,分层查找,每走一步访问一批顶点】
适用场合:Dijkstra单源最短路径 prim最小生成树
算法借助一个辅助队列,记忆正在访问的顶点的下一层顶点
bool visited[Max_Vertex_Num]; //标志顶点是否被访问过,避免重复访问
//对图G进行广度优先搜索遍历
void BFSTraverse(Graph G){
for(int i=0;i<G.vexnum;i++) //初始化标记数组
visited[i]=False;
InitQueue(Q); //初始化辅助队列Q
for(i=0;i<G.vexnum;i++){ //判断当前顶点vi是否访问过,若没有就从vi开始BFS
if(!visited[i])
BFS(G,i);
}
}
//对某顶点v进行BFS
void BFS(Graph G,int v){
//访问顶点v,标记已访问,并且入队列
visit(v);
visited[v]=True;
EnQueue(Q,v);
while(!IsEmpty(Q)){
DeQueue(Q,v); //顶点v出队列 访问其邻接点
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){ //检测v的所有邻接点 未被访问与顶点v操作一样
if(!visited[w]){
visit(w);
visited[w]=True;
EnQueue(Q,w);
}
}
}
}
(2)BFS 算法求无权图单源最短路径
分析:G=(V,E)为无权图,定义从顶点u到顶点v最短路径为d(u,v)为从u到v的任何路径中最少的边数,若无通路,则为d(u,v)=无穷大(-12345)
void BFS_Min_Distance(Graph G,int u){
for(int i=0;i<G.vexnum;i++) //初始化d[i]表示u到i结点的最短路径
d[i]=-12345;
//顶点u进入队列
visited[u]=True; //将BFS中的访问结点,改为修改最短路径
d[u]=0;
EnQueue(Q,u);
while(!isEmpty(Q)){ //类似BFS主过程
DeQueue(Q,u);
for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w)){
if(!visited[w]){
visited[w]=True;
d[w]=d[u]+1; //与BFS不同之处在于,不用访问结点数值,而是修改与顶点u之间的路径
EnQueue(Q,w);
}
}
}
}
深度优先搜索DFS(类似于树的先序遍历算法)
(1)算法思想:
尽可能深的搜索一个图,不断访问邻接点的邻接点……;
访问起始顶点v,由v出发访问与v邻接且未被访问的顶点w1,再访问与w1邻接且未被访问的顶点w2……重复上述过程;
直到不能再向下访问时,依次退回到最近被访问的顶点,若它还有邻接顶点未被访问,则从该顶点开始继续访问,直到图中所以顶点都被访问过。
【算法借助递归工作栈】
bool visited[Max_Vertex_Num]; //标志顶点是否被访问过
//对图G进行深度优先搜索遍历
void DFSTraverse(Graph G){
for(int i=0;i<G.vexnum;i++) //初始化标记数组
visited[i]=False;
for(i=0;i<G.vexnum;i++){ //判断当前顶点vi是否访问过,若没有就从vi开始BFS
if(!visited[i])
DFS(G,i);
}
}
void DFS(Graph G,int v){
visit(v); //访问顶点v 标记为已访问
visited[v]=True;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w])
DFS(G,w); //w是v尚未被访问过的邻接点
}
}
/* 非递归 栈实现
栈S记忆下一步可能访问的顶点,使用一个访问标记数组visited[i]记忆第i个顶点是否在栈内或者曾经在栈内;
如果是,以后不能再进栈;图采用邻接表表示
*/
//类似广度优先搜索,只不过一个借助队列 一个借助栈
void DFS_Non_RC(ALGraph &G,int v){
//从顶点v开始进行深度优先搜索,一次遍历一个连通分量的所有顶点
InitStack(S);
for(i=0;i<G.vexnum;i++)
visited[i]=False;
//标记v为已访问结点
Push(S,v);
visited[v]=Ture;
while(!isEmpty(S)){
Pop(S,k); //栈中弹出一个元素,保存在k中
visit(k);
for(w=FirstNeighbor(G,k);w>=0;w=NextNeighbor(G,k,w)){ //访问k的所有邻接点 未被访问就入栈 标记已访问
if(!visited[w]){
Push(S,w);
visited[w]=True;
}
}
}
}
课后习题
1、判断一个无向图G是否为一棵树,若是一棵树,返回true;否则返回false
分析:无向图G是一棵树的条件,G必须是无回路的连通图或有n-1条边的连通图(本题可用边数作为判断)
连通判定:能否遍历所有顶点,采用DFS 遍历图中统计可能访问到的顶点个数和边数;
若一次遍历就能访问到n个顶点和n-1条边,此图是一棵树
//主函数,判断无向图G是否为一棵树
bool IsTree(ALGraph &G){
for(i=1;i<=G.vexnum;i++) //初始化标记数组
visited[i]=False;
int Vnum=0,Enum=0; //初始化顶点数和边数
DFS(G,1,Vnum,Enum,visited); //统计可能访问到的顶点个数和边的条数
if(Vnum==G.vexnum&&Enum==2*(G.vexnum-1)) //无向图中每两个顶点的一条边都会遍历2次
return true;
else
return false;
}
//统计可能访问到的顶点个数和边的条数,通过Vnum/Enum返回
void DFS(ALGraph &G,int v,int &Vnum,int &Enum,int visited[]){
visited[v]=True; //标记v已访问,结点数+1
Vnum++;
for(w=FirstNeighbor(G,v);W>=0;W=NextNeighbor(G,v,w)){
Enum++; //v与w之间存在边
if(!visited[w]) //如果w未被访问就继续向下“深”
DFS(G,w,Vnum,Enum,visited);
}
}
2、分别采用 BFS 和 DFS 判别以邻接表存储的有向图中是否存在由顶点 vi 到 vj 的路径( i 不等于 j )
分析:采用从顶点 vi 出发,依次遍历图中的每个顶点,直到搜索到顶点 vj ;若能搜索到顶点 vj ,说明存在顶点 vi 到 vj 的路径
递归结束的条件:存在顶点vi到vj的路径,返回1,否则返回0
//DFS
bool visited[MaxSize];
for(k=1;k<=G.vexnum;k++) //初始化标记数组
visited[k]=False;
int Exist_Path_DFS(ALGraph &G,int i,int j){
if(i==j) //存在顶点vi到vj的路径,返回1
return 1;
else{
visited[i]=True; //置vi访问标记为1
for(w=FirstNeighbor(G,i);w>=0;w=NextNeighbor(G,i,w)){ //检测邻接点是否有路径
if(!visited[w]&&Exist_Path_DFS(G,w,j)) //如果邻接点w未被访问且存在w到j的路径 返回1
return 1;
}
}
return 0; //不存在顶点vi到vj的路径,返回0
}
//BFS
bool visited[MaxSize];
for(k=1;k<=G.vexnum;k++) //初始化标记数组
visited[k]=False;
int Exist_Path_BFS(ALGraph &G,int i,int j){
InitQueue(Q);
EnQueue(Q,i);
while(!isEmpty(Q)){
DeQueue(Q,i); //队头结点出队
visited[i]=True;
for(w=FirstNeighbor(G,i);w>=0;w=NextNeighbor(G,i,w)){
k=w.adjvex; //如果存在i-w-k-j路径,记录w的邻接点
if(k==j) //与j比较,如果相等就表示存在,则返回1;否则就将k入队列,下次继续弹出
return 1;
if(!visited[k])
EnQueue(Q,k);
}
}
return 0;
}
3、图用邻接表存储,输出所有 vi 到 vj 的简单路径
分析:递归DFS 从结点u出发,递归深度优先遍历图中的结点,若访问到结点v,输出该搜索路径上的结点。
输出路径——设置一个path数组来存放路径上的结点(初始为空);d表示路径长度(初始为-1)
查找过程如下:
FindPath(G,u,v,path,d)
d++;path[d]=u; 从u开始有路径长度,存储并将长度+1
继续找u的邻接点u1 如果u1未被访问,继续查找u1的邻接点;如果已被访问,指向u的下一个邻接点
……
直到找到一条路径是u==v的情况,结束递归(递归结束的条件)
【 函数中的p/w/u都是改变量,p是始点的第一个邻接点,必定有路径;w是p的邻接点(也就是u邻接点的邻接点) 总是寻找与终点v之间有无路径,也就是终点 不变,改变的是始点与邻接点;最后,要将始点的已访问标记置为0,下次从这个点继续重新找路径 】
void FindPath(ALGraph &G,int u,int v,int path[],int d){ //u是始点,v是终点
path[d++]=u; //始点已访问,并加入路径数组中
visited[u]=1;
if(u==v) //如果找到路径,直接打印
printf(path[]);
p=G->adjlist[u].firstarc; //p指向u的第一个邻接点
while(p!=NULL){
w=p->adjvex; //w是p的邻接点【如果p指向它的下一条边,w就是与之相邻的邻接点】
if(visited[w]==0) //如果w未被访问过,递归访问,找与终点v之间有无路径
FindPath(G,w,v,path,d);
p=p->nextarc; //p指向u的下一条弧
}
visited[u]=0; //所有简单路径不止一条,输出一种后还可有其他路径,需要重新从u开始
}
图的应用
掌握Dijkstra单源最短路径 prim最小生成树 Floyd算法 拓扑排序 关键路径——课后习题多刷!!
课后习题
如何利用 DFS 实现有向无环图拓扑排序
分析:有向无环图G中任意结点u,v;存在三种关系:
结点u是结点v的祖先,对DFS访问u的过程中,必然会先对v进行DFS调用,v的DFS结束时间小于u的DFS结束时间;祖先的结束时间大于子孙的结束时间
结点v是结点u的祖先,结点u的结束时间小于结点v的结束时间
结点u和v没有关系,拓扑序列中的位置任意
【 拓扑序列就是结点的结束时间,按照从大到小的顺序进行排列,祖先开始……
主要算法与DFS大概一样,需要增加time变量对于结点结束时间(出栈时间)进行记录,保存在数组中,便于排序 】
bool visited[MaxSize]; //访问标记数组
void DFSTraverse(Graph G){
for(i=0;i<G.vexnum;i++)
visited[i]=False;
time=0; //初始情况time为0
for(v=0;v<G.vexnum;v++)
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G,int v){ //DFS主算法
visited[v]=True;
visit(v);
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w])
DFS(G,w);
}
time=time+1; //退出递归时才会调用,也就是从最后一个结点开始,初步将time+1,越后遍历的结点time越小
FinishTime[v]=time;
}