• 第6章-图


    目录

    图的存储及基本操作

    邻接矩阵法

    邻接表法

    图的基本操作

    图的遍历

    广度优先搜索

    BFS算法求解单源最短路径问题

    深度优先搜索

    最小生成树

    prim算法

    Kruskal算法

    最短路径

    Dijkstra算法求单源最短路径问题

    Floyed算法求各顶点之间最短路径问题

    拓扑排序

    关键路径

    基本概念

    求关键路径算法步骤


    图的存储及基本操作

    邻接矩阵法

    1. #define MaxVertexNum 100//顶点数目的最大值
    2. typedef char VertexType;//顶点的数据类型
    3. typedef int EdgeType;//带权图中边上权值的数据类型
    4. typedef struct{
    5. VertexType Vex[MaxVertexNum];//顶点表
    6. EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
    7. int vexnum,arcnum;//图的当前顶点数和弧数
    8. }MGraph;

    邻接表法

    1. #define MaxVertexNum 100//图中顶点数目的最大值
    2. typedef struct ArcNode{//边表结点
    3. int adjvex;//该弧所指向的顶点的位置
    4. struct ArcNode *next;//指向下一条弧的指针
    5. //InfoType info;//网的边权值
    6. }ArcNode;
    7. typedef struct VNode{//顶点表结点
    8. VertexType data;//顶点信息
    9. ArcNode *first;//指向第一条依附该顶点的弧的指针
    10. }VNode,AdjList[MaxVertexNum];
    11. typedef struct{
    12. AdjList vertices;//邻接表
    13. int vexnum,arcnum;//图的顶点数个弧数
    14. }ALGraph;//ALGraph是以邻接表存储的图类型

    图的基本操作

    Adjacent(G,x,y);//判断图G是否存在边或(x,y) 
    Neighbors(G,x);//列出图G中与结点x邻接的边 
    InsertVertex(G,x);//在图G中插入顶点x 
    DeleteVertex(G,x);//从图G中删除顶点x 
    AddEdge(G,x,y);//若无向边(x,y)或有向边不存在,则向图G中添加该边 
    RemoveEdge(G,x,y);//若无向边(x,y)或有向边存在,则向图G中删除该边
    FirstNeighbor(G,x);//求图G中顶点x的第一个邻接点,若有则返回顶点号。
    //若x有邻接点或图中不存在x,则返回-1 
    NextNeighbor(G,x,y);//假设图G中顶点y是顶点x的一个邻接点,返回y外顶点x的
    //下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1 
    Get_edge_value(G,x,y);//获取图G中边(x,y)或对应的权值 
    Set_edge_value(G,x,y,v);//设置图G中边(x,y)或对应的权值为v 

    图的遍历

    广度优先搜索

    1. bool visited[MAX_VERTEX_NUM];//访问标记数组
    2. void BFSTraverse(Graph G){//对图G进行广度优先遍历
    3. for(i=0;i
    4. visited[i]=false;//访问标记数组初始化
    5. InitQueue(Q);//初始化辅助队列Q
    6. for(i=0;i//从0号顶点开始遍历
    7. if(!visited[i])//对每个连通分量调用一次BFS
    8. BFS(G,i);//vi未访问过,从vi开始BFS
    9. }
    10. void BFS(Graph G,int v){//从顶点v出发,广度优先遍历图G
    11. visit(v);//访问初始顶点v
    12. visited[v]=true;//对v做已访问标记
    13. EnQueue(Q,v);//顶点v入队列Q
    14. while(!isEmpty(Q)){
    15. DeQueue(Q,v);//顶点v出队列
    16. for(w=FistNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))//检测v所有邻接点
    17. if(!visited[w]){//w为v的尚未访问的邻接顶点
    18. visit(w);//访问顶点w
    19. visited[w]=true;//对w做已访问标记
    20. EnQueue(G,w);//顶点w入队列
    21. }
    22. }
    23. }

    BFS算法求解单源最短路径问题

    1. #define inf 0x4f4f4f
    2. void BFS_MIN_Distance(Graph G,int u){
    3. //d[i]表示从u到i结点的最短路径
    4. for(i=0;i
    5. d[i]=inf; //初始化路径长度
    6. visited[u]=true;
    7. d[u]=0;
    8. EnQueue(G,u);
    9. while(!isEmpty(Q)){//BFS算法主过程
    10. DeQueue(Q,u);//队头元素u出队
    11. for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
    12. if(!visited[w]){//w为u的尚未访问的邻接顶点
    13. visited[w]=true;//对w做已访问标记
    14. d[w]=d[u]+1;//路径长度加1
    15. EnQueue(G,w);//顶点w入队列
    16. }
    17. }
    18. }

    深度优先搜索

    1. bool visited[MAX_VERTEX_NUM];//访问标记数组
    2. void DFSTraverse(Graph G){//对图G进行深度优先遍历
    3. for(v=0;v
    4. visited[v]=false;//访问标记数组初始化
    5. InitQueue(Q);//初始化辅助队列Q
    6. for(v=0;v//从0号顶点开始遍历
    7. if(!visited[v])
    8. DFS(G,v);//vi未访问过,从vi开始DFS
    9. }
    10. void DFS(Graph G,int v){//从顶点v出发,深度优先遍历图G
    11. visit(v);//访问初始顶点v
    12. visited[v]=true;//对v做已访问标记
    13. EnQueue(Q,v);//顶点v入队列Q
    14. for(w=FistNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))//检测v所有邻接点
    15. if(!visited[w]){//w为v的尚未访问的邻接顶点
    16. DFS(G,w);
    17. }
    18. }
    19. }

    最小生成树

    通用的最小生成树算法:

    1. GENERIC_MST(G){
    2. T=NULL;
    3. while T未形成一棵生成树
    4. do 找到一条最小代价边(u,v)并且加入T后不会产生回路
    5. T=T∪(u,v);
    6. }

    prim算法

    思想:初始时从图中任取一顶点加入树T,此时树中只含有一个顶点,之后选择一个与当前T中顶点集合距离最近的顶点,并将该顶点和相应的边加入T, 每次操作后T中的顶点数和边数都增加1,以此类推,直至图中所有的顶点都并入T,得到的T就是最小生成树。此时T中必然有n-1条边。

    1. void Prim(G,T){
    2. T=空集;//初始化空树
    3. U={w};//添加任一顶点w
    4. while((Y-U)!=空集){//若树中不含全部顶点
    5. 设(u,v)是使 u属于U 与 v属于(V-U) ,且权值最小的边
    6. T=T∪{(u,v)};//边归入树
    7. U=U∪{v};//顶点归入树
    8. }
    9. }

    时间复杂度O(V^{2}),不依赖 E,适用于求解边稠密的图的最小生成树。

    Kruskal算法

    思想:是一种按权值的递增次序选择合适的边来构造最小生成树的方法。

    初始时为只有n个顶点而无边的非连通图T={ V,{ } },每个顶点自成一个连通分量,然后按照边的权值由小到大的顺序,不断选取当前未被选取过且权值最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入T,否则舍弃此边而选择下一条权值最小的边,以此类推,直至T中所有顶点都在一个连通分量上。

    1. void Kruskal(V,T){
    2. T=V;//初始化树T,仅含顶点
    3. numS=n;//连通分量数
    4. while(numS>1){ //若连通分量数大于1
    5. 从E中取出权值最小的边(v,u);
    6. if(v和u属于T中不同的连通分量){
    7. T=T∪{(v,u)};//将此边加入生成树中
    8. numS--;//连通分量数减1
    9. }
    10. }
    11. }

    用堆来存放边的集合,每次选择最小权值边需O(log|E|),采用并查集添加新边,构造T的时间复杂度是O(|E|log|E|)。因此适用于边稀疏而顶点较多的图。

    最短路径

    Dijkstra算法求单源最短路径问题

    单源最短路径:图中某一顶点到其他各个顶点的最短路径。O(V^{2})

    1 初始化:集合S初始为{0},dist[ ]的初始值dist[ i ]=arcs[0][i],i=1,2,...,n-1.

    2 从顶点集合V-S中选出v_{j},满足dist[j]=Min{dist[ i ]| v_{j}\epsilon V-S} ,v_{j}就是当前求得的一条从v_{0}出发的最短路径的终点。令S=S∪{j}。

    3 修改从v_{0}出发到集合V-S上任一顶点v_{k}可达的最短路径长度:若dist[ j ]+arcs[j][k]

    4 重复2&3操作n-1次,直到所有的顶点都包含在S中。

    不适用边上带有负权值。 

    Floyed算法求各顶点之间最短路径问题

    递推产生一个n阶方阵序列A^{-1},A^{0},...,A^{k},...,A^{n-1},其中A^{-1}[i][j]表示从顶点v_{i}v_{j}的路径长度,k表示绕行第k个顶点的运算步骤。

    定义一个n阶方阵序列A^{-1},A^{0},...,A^{k},...,A^{n-1},其中,

    A^{-1}[i][j]=arcs[i][j]

    A^{k}[i][j]=Min \left \{ A^{k-1}[i][j],A^{k-1}[i][k]+A^{k-1}[k][j] \right \},k=0,1,...,n-1

     A^{0}[i][j]是从顶点v_{i}v_{j}、中间顶点是v_{0}的最短路径的长度,A^{k}[i][j]是从顶点v_{i}v_{j}、中间顶点的序号不大于k的最短路径长度。

    O(V^{3}),允许图中带有负权值的边,不允许有包含负权值的边组成的回路。

    同样适用于带权无向图。

    拓扑排序

    有向无环图:DAG

    AOV网:DAG图&顶点表示活动的网络

    邻接表存储时,拓扑排序O(|V|+|E|),邻接矩阵O(|V^{2}|)

    1. bool TopLogicalSort(Graph G){
    2. InitStack(S);//初始化栈,存储入度为0的顶点
    3. for(int i=0;i
    4. Push(S,i);//将所有入度为0的顶点入栈
    5. }
    6. int count=0;//计数,记录当前已经输出的顶点数
    7. while(!IsEmpty(S)){//栈不空,则存在入度为0的顶点
    8. Pop(S,i);//栈顶元素出栈
    9. print[count++]=i;//输出顶点i
    10. for(p=G.vertices[i].firstarc;p;p=p->nextarc){
    11. //将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
    12. v=p->adjvex;
    13. if(!(--indegree[v]))
    14. Push(S,v);//入度为0,入栈
    15. }
    16. }
    17. if(count
    18. return false;//排序失败,有向图中有回路
    19. else return true;
    20. }

    关键路径

    基本概念

    关键路径:从源点到汇点的所有路径中,最大路径长度的路径。

    关键活动:关键路径上的活动    l(i)-e(i)=0,即 l(i)=e(i)的活动a_{i}

    事件v_{k}的最早发生时间ve(k):源点v_{i}到顶点v_{k}的最长路径长度

    ve(源点)=0

    ve(k)=Max{ve(j)+Weight(v_{j},v_{k}),v_{k}}

    Weight(v_{j},v_{k})表示<v_{j},v_{k}>上的权值,v_{k}为 v_{j}的任意后继

    事件v_{k}的最迟发生时间vl(k):不推迟整个工程完成时间的前提下,后继事件v_{j}在其最迟发生时间vl(j)能够发生时,该事件最迟必须发生的时间。

    vl(汇点)=ve(汇点)

    vl(k)=Min{vl(i)-Weight(v_{k},v_{j}) }

     v_{k}为 v_{j}的任意前驱 

    活动a_{i}的最早开始时间e(i):该活动弧的起点所表示的事件的最早发生时间,若<v_{k} ,v_{j} >表示活动a_{i},则

    e(i)=ve(k)

     

    活动a_{i}的最迟开始时间l(i):该活动弧的终点所表示的事件的最迟发生时间与该活动所需时间之差, 若<v_{k} ,v_{j} >表示活动a_{i},则

    l(i)=vl(i)-Weight( v_{k},v_{j})

    一个活动a_{i}的最迟开始时间l(i)和其最早开始时间e(i)的差额d(i)=l(i)-e(i)

    指该活动完成的时间余量

    求关键路径算法步骤

    1  从汇点出发,令 ve(源点)=0,按拓扑有序求其余顶点的最早发生时间ve( )

    2  从汇点出发,令vl(汇点)=ve(汇点),按逆拓扑有序求其余顶点的最迟发生时间vl( )

    3  根据各顶点的ve( )值求所有弧的最早开始时间e( )

    4  根据各顶点的vl( )值求所有弧的最迟开始时间l( )

    5  求AOE网中所有活动的差额d( ),找出所有d( )=0的活动构成关键路径。

  • 相关阅读:
    TCP零基础详解
    C语言关于自定义字符函数和字符串函数的相关笔试题(找工作必看)
    C语言字符转数字函数
    【FPGA教程案例34】通信案例4——基于FPGA的QPSK调制信号产生,通过matlab测试其星座图
    基于因果化评论的可解释推荐方法
    golang ES 聚合查询
    vue3探索——5分钟快速上手大菠萝pinia
    gorm的mysql保存数据保存主键冲突Duplicate entry for key PRIMARY
    【Linux】3.切换操作系统
    【Go之道】探索Go语言之旅:基础与进阶指南
  • 原文地址:https://blog.csdn.net/qq_45181299/article/details/126105726