王道_Graph代码合集

图的遍历

题记:
小伙伴都开学了,我也想开学。
放平心态,稳住不慌,平心静气,过好接下来的每一天,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;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值