数据结构(有向无环图(DAG)、AOV网、拓扑排序;关键路径、AOE网)
8.12、有向无环图(DAG)、AOV网、拓扑排序
有向无环图(DAG)
若一个有向图中不存在环,则称为有向无环图,简称DAG图(Directed Acyclic Graph).
AOV网
AOV网(Activity Vertex NetWork,用顶点表示活动的网),用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边表示活动必须先于活动进行
拓扑排序:
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序:
- 每个顶点出现且只出现一次
- 若顶点A在序列中排在顶点B的前面,则图中不存在从顶点B到顶点A的路径
或定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则B出现在A的后面,每个AOV网都有一个或者多个拓扑排序。
拓扑排序的实现
- 从AOV网中选择一个没有前驱(入度为0)的顶点并输出
- 从网中删除该顶点和所有以它为起点的有向边
- 重复1和2直到当前的AOV网为空或当前网中不存在无前驱的顶点为止
拓扑排序算法思想
拓扑排序的代码实现
#include <stdio.h> #include <stdlib.h> #include<math.h> #define MaxSize 100 #define MaxInteger 32767 #define boolean int #define true 1 #define false 0 //边\弧 typedef struct ArcNode{ int adjvex;//边或者弧指向的那个结点 struct ArcNode *next;//指向下一个弧的指针 int info;//权值 }ArcNode; //顶点信息 typedef struct ElemType{ char v;//顶点信息 int flag;//顶点是否被使用,1表示使用,0表示未使用 }ElemType; //顶点 typedef struct VNode{ ElemType data;//顶点信息 ArcNode *first;//第一个边或弧 }VNode,AdjList[MaxSize]; //有向图的邻接表存储 typedef struct MGraph{ AdjList vertices;//图的信息 int vexnum,arcnum;//顶点数和边数 }MGraph; //初始化图 boolean MG_Init(MGraph **G){ (*G) = (MGraph *)malloc(sizeof(MGraph)); if(*G == NULL){ printf("内存申请失败!\n"); return false; } (*G)->arcnum = 0;//边数 (*G)->vexnum = 0;//顶点数 for(int i = 0;i < MaxSize;i++){//把所有带使用的顶点信息数据使用标志全置为0 (*G)->vertices[i].data.flag = 0; (*G)->vertices[i].first = NULL; } return true; } //判断顶点V是否存在,并获取边的索引Vi boolean IsVEmpty(MGraph *G,ElemType V,int *Vi){ for(int i = 0;i < G->vexnum;i++){ if(G->vertices[i].data.v == V.v && G->vertices[i].data.flag == 1) { *Vi = i; return true; } } return false; } //判断边是否存在: boolean Adjancent(MGraph *G,ElemType V,ElemType W,int *Vi,int *Wi){ if(!IsVEmpty(G,V,Vi) || !IsVEmpty(G,W,Wi)){ printf("其中一个顶点不存在!!/\n"); return true; } ArcNode *node = G->vertices[*Vi].first; while(node != NULL){ if(node->adjvex == *Wi){ // printf("边已经存在\n"); return true; } node = node->next; } return false; } //插入顶点 boolean InsertV(MGraph *G,ElemType V){ int *Vi; if(IsVEmpty(G,V,Vi)){ printf("顶点已经存在!!\n"); return false; } V.flag = 1; G->vertices[G->vexnum++].data = V; return true; } //插入边 boolean AddArcNode(MGraph *G,ElemType V,ElemType W,int info){ int *Vi = (int *)malloc(sizeof(int)); int *Wi = (int *)malloc(sizeof(int)); if(Adjancent(G,V,W,Vi,Wi)){//判断边是否存在 return false; } ArcNode * p = (ArcNode*)malloc(sizeof(ArcNode)); p->adjvex = *Wi; p->info = info; p->next = NULL; if(G->vertices[*Vi].first == NULL){//没有边,新增加边 G->vertices[*Vi].first = p; G->arcnum++; return true; } ArcNode *node = G->vertices[*Vi].first; while(node->next != NULL){ node = node->next; } node->next = p; G->arcnum++; return true; } //获取某个结点v的所有邻接边;<v,wi>;以v为尾巴 boolean NeighBors(MGraph *G,ElemType V,ElemType ***res,int **infos,int *length){ int *Vi = (int *)malloc(sizeof(int)); if(!IsVEmpty(G,V,Vi)){ return false; } ArcNode *node = G->vertices[*Vi].first; while(node != NULL){ (*length)++; node = node->next; } *res = (ElemType **)malloc(sizeof(ElemType)*(*length)); *infos = (int *)malloc(sizeof(ElemType)*(*length)); node = G->vertices[*Vi].first; for(int i=0;i < (*length) ;i++){ (*res)[i] = (ElemType *)malloc(sizeof(ElemType)*3); (*res)[i][0] = V; (*res)[i][1] = G->vertices[node->adjvex].data; (*infos)[i] = node->info; node = node->next; } return true; } //移除该边<V,W> boolean RemoveArcNode(MGraph *G,ElemType V,ElemType W){ int *Vi = (int *)malloc(sizeof(int)); int *Wi = (int *)malloc(sizeof(int)); if(!Adjancent(G,V,W,Vi,Wi)){//判断该边是否存在 return false; } ArcNode *delete = G->vertices[*Vi].first;//删除的结点 ArcNode *pre = NULL;//删除边的前一个结点 while(delete != NULL){//寻找删除的边 if(delete->adjvex == *Wi){ break; } pre = delete; delete = delete->next; } if(pre == NULL){ G->vertices[*Vi].first = G->vertices[*Vi].first->next; }else{ pre->next = delete->next;//修改指针 } G->arcnum--;//修改边数 free(delete);//释放内存 return true; } //删除顶点 boolean DeleteV(MGraph *G,ElemType V){ int *Vi = (int *)malloc(sizeof(int)); if(!IsVEmpty(G,V,Vi)){ return false; } ArcNode *W = G->vertices[*Vi].first; while(W != NULL){ RemoveArcNode(G,V,G->vertices[G->vertices[*Vi].first->adjvex].data); W = G->vertices[*Vi].first; } int index = 0; int sum = 0; while(sum < G->vexnum){ if(G->vertices[index].data.flag==1){ W = G->vertices[index].first; while(W != NULL){ if(W->adjvex == *Vi){ RemoveArcNode(G,G->vertices[index].data,V); break; } W = W->next; } sum++; } index++; } G->vertices[*Vi].data.flag = 0; G->vexnum--; return true; } //队列的数据信息 typedef struct LinkNode{ int data; struct LinkNode *next; }LinkNode; //链队列定义 typedef struct{ LinkNode *front,*rear;//*front 队头指针, *rear 队尾指针 }LinkQueue; //初始化带头结点的队列 int InitLinkQueue(LinkQueue *Q){ (*Q).front = (*Q).rear = (LinkNode *)malloc(sizeof(LinkNode)); if((*Q).front == NULL || (*Q).rear == NULL) return false; (*Q).front->next = NULL; return true; }; //带头结点判断是否为空 int IsEmpty(LinkQueue Q){ if(Q.front == Q.rear) return true; else return false; } //带头结点入队操作 int EnQueue(LinkQueue *Q,int e){ LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode)); if(p==NULL) return false;//分配内存失败 p->data = e; p->next = NULL; Q->rear->next = p; Q->rear = p; } //带头结点出队操作 int DeQueue(LinkQueue *Q,int *e){ if(Q->front == Q->rear) return false;//队列为空,出队失败 LinkNode *p = Q->front->next; *e = p->data; Q->front->next = p->next; if(Q->rear == p){ //如果出队的元素是对尾元素,需要特殊处理 Q->rear = Q->front; } free(p);//释放片 return true; } //拓扑排序 图G,print:表示结果集 boolean TopologicalSort(MGraph *G,char **print){ int *visted = (int *)malloc(sizeof(int)*G->vexnum);//入度记录的数组 *print = (char *)malloc(sizeof(char)*G->vexnum);//返回的拓扑排序序列 //初始化入度记录数组和拓扑排序结果集 for(int i = 0;i < G->vexnum;i++){ visted[i] = 0; (*print)[i] = '~'; } //根据图G更新入度记录的数组 for(int i = 0;i < G->vexnum;i++){ ArcNode *node = G->vertices[i].first; while(node != NULL){ visted[node->adjvex]++; node = node->next; } } LinkQueue Q;//定义队列 InitLinkQueue(&Q);//初始化队列 int count = 0;//入队列的顶点数目 //入度为0的入队列 for(int i = 0;i < G->vexnum;i++){ if(visted[i] == 0){ EnQueue(&Q,i); } } //循环队列是否为空,寻找其他顶点 while(!IsEmpty(Q)){ int e;//队列的队头元素 DeQueue(&Q,&e);//获取队列队头元素 (*print)[count++] = G->vertices[e].data.v;//获取队头元素的值记入结果集 ArcNode *node = G->vertices[e].first; while(node != NULL){//寻找队头元素该顶点的边 if(--visted[node->adjvex] == 0){//入度--,如果为0了就加入队列 EnQueue(&Q,node->adjvex); } node = node->next; } } if(count == G->vexnum){//全部进入队列返回拓扑排序成功,否则返回失败 return true; }else{ return false; } } //打印拓扑排序的结果集合 void printfTopo(MGraph *G,char *print){ printf("拓扑排序的结果:"); for(int i = 0;i < G->vexnum;i++){ printf("%2c",print[i]); } } //拓扑排序使用 void TopoSort(MGraph *G){ char *print; boolean flag = TopologicalSort(G,&print); if(flag){ printfTopo(G,print); }else{ printf("不是AOV网,拓扑排序失败"); } } int main(){ MGraph *G; MG_Init(&G); printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum); ElemType V1; V1.v = 'A'; InsertV(G,V1); ElemType V2; V2.v = 'B'; InsertV(G,V2); ElemType V3; V3.v = 'C'; InsertV(G,V3); ElemType V4; V4.v = 'D'; InsertV(G,V4); ElemType V5; V5.v = 'E'; InsertV(G,V5); ElemType V6; V6.v = 'F'; InsertV(G,V6); ElemType V7; V7.v = 'G'; InsertV(G,V7); printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum); AddArcNode(G,V1,V2,1); AddArcNode(G,V1,V6,1); AddArcNode(G,V2,V3,1); AddArcNode(G,V3,V7,1); AddArcNode(G,V4,V2,1); AddArcNode(G,V4,V5,1); AddArcNode(G,V5,V6,1); AddArcNode(G,V6,V3,1); // AddArcNode(G,V3,V5,1); printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum); TopoSort(G); return 0; } //结果: 顶点数:0;边数:0 顶点数:7;边数:0 顶点数:7;边数:8 拓扑排序的结果: A D B E F C G
8.13、关键路径、AOE网
在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成该活动的时间),称之为用边表示活动的网络,简称AOE网(Activity On Edge NetWork)。
AOE网具有的两个性质:
- 只有在某个顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始
- 只有进入的某个顶点的各有向边所代表的活动都已经结束时,该顶点所代表的事件才能发生。另外,有些活动是可以并行进行的。
在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),表示整个工程的开始;
也仅有一个出度为0的顶点,称为结束顶点(汇点),表示整个工程的结束。
从源点到汇点的有向路径中可能有多条,所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动
关键路径的寻找
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了