数据结构(有向无环图(DAG)、AOV网、拓扑排序;关键路径、AOE网)

8.12、有向无环图(DAG)、AOV网、拓扑排序

有向无环图(DAG)

若一个有向图中不存在环,则称为有向无环图,简称DAG图(Directed Acyclic Graph).

AOV网

AOV网(Activity Vertex NetWork,用顶点表示活动的网),用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边\(<V_i,V_j>\)表示活动\(V_i\)必须先于活动\(V_j\)进行

拓扑排序:

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序:

  • 每个顶点出现且只出现一次
  • 若顶点A在序列中排在顶点B的前面,则图中不存在从顶点B到顶点A的路径

或定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则B出现在A的后面,每个AOV网都有一个或者多个拓扑排序。

拓扑排序的实现

  1. 从AOV网中选择一个没有前驱(入度为0)的顶点并输出
  2. 从网中删除该顶点和所有以它为起点的有向边
  3. 重复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的顶点,称为结束顶点(汇点),表示整个工程的结束。

从源点到汇点的有向路径中可能有多条,所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动

关键路径的寻找

posted @ 2022-11-09 14:20  水三丫  阅读(1091)  评论(0编辑  收藏  举报