数据结构(六)——图之关键路径

代码中所用到的结构体

typedef struct arcnode
{
    int weight;//权重
    int adjvex;//指向的下一个顶点
    struct arcnode *next;//指向这个点的另一条边
}Arcnode,*pArcnode;

typedef struct vnode
{
    pArcnode firstarc;//点所指向的第一条边
}Vnode,AdjList[30];

typedef struct graph
{
    int Vnum,Arcnum;//点的数目,边的数目
    AdjList vertice;
}Graph,*pGraph;

构造AOE网

void CreateGraph(pGraph G)//构造AOE网
{
    int i;
    int node1,node2,weight;//暂时存储数据
    pArcnode p1;
    printf("请输入有向图的总点数:\n");
    scanf("%d",&G->Vnum);
    getchar();
    for(i=0;i<G->Vnum;i++)//给每个结点的第一个后继边初始化
    {
        G->vertice[i].firstarc=NULL;
    }
    printf("请输入无向图的总边数:\n");
    scanf("%d",&G->Arcnum);
    getchar();
    printf("请输入点和点之间的连接:(例:1 5 10)\n");
    for(i=0;i<G->Arcnum;i++)//循环输入边的信息
    {
        scanf("%d %d %d",&node1,&node2,&weight);
        getchar();
        p1=(pArcnode)malloc(sizeof(Arcnode));
        p1->adjvex=node2;//构造连接
        p1->weight=weight;
        p1->next=NULL;
        if(G->vertice[node1].firstarc==NULL)//还未有邻接点时
        {
            G->vertice[node1].firstarc=p1;
        }
        else//已有邻接点时
        {
            p1->next=G->vertice[node1].firstarc;
            G->vertice[node1].firstarc=p1;
        }
    }
}

计算拓扑排序和ve【】

  • 重要参数:ve【】,用于存储事件(即为图中的点)开始的最早时间;Q【】,队列用于存储拓扑排序;indegree【】,用于存储每个点的入度
  • 为了得到拓扑排序,首先先计算每个点的入度,这里,用到了CalculateIndegree()函数,代码如下。
void CalculateIndegree(pGraph G,int indegree[])
{
    int i;
    pArcnode s;
    for(i=0;i<G->Vnum;i++)//入度数组初始化
    {
        indegree[i]=0;
    }
    for(i=0;i<G->Vnum;i++)//计算每个点的入度
    {
        s=G->vertice[i].firstarc;
        while(s!=NULL)
        {
            indegree[s->adjvex]++;
            s=s->next;
        }
    }
}
  • 计算完后,寻找入度为零的点,将这些点及其后继指针从图中删除,将其后继节点的入度减一。
  • 并在这个过程中计算ve【】的值
  • 重复以上的步骤,知道图中的点都计算完
  • 结果,执行完这个函数后得到了拓扑排序和ve【】。
void TuoPu(pGraph G,int ve[],int Q[])//求拓扑排序,ve【】存储事件开始的最早时间,Q【】队列存储拓扑排序
{
    int indegree[G->Vnum];
    int head=0,tail=0,temp;
    int i;
    pArcnode s;
    CalculateIndegree(G,indegree);//计算每个点的入度
    for(i=0;i<G->Vnum;i++)//初始化事件开始的最早时间
    {
        ve[i]=0;
    }
    for(i=0;i<G->Vnum;i++)//寻找一开始入度就为0的点
    {
        if(indegree[i]==0)//当入度为0时
        {
            Q[tail]=i;
            tail++;
        }
    }
    while(head!=tail)//计算整个图的拓扑排序
    {
        temp=Q[head];//出队
        head++;
        s=G->vertice[temp].firstarc;
        while(s!=NULL)//消除入度为零的点的后继边
        {
            indegree[s->adjvex]--;//为该点的后继边指向的点入度减1
            if(ve[s->adjvex]<ve[temp]+s->weight)//计算活动最早开始的时间
            {
                ve[s->adjvex]=ve[temp]+s->weight;
            }
            if(indegree[s->adjvex]==0)//判断入度减一后是否为入度为0的点
            {
                Q[tail]=s->adjvex;
                tail++;
            }
            s=s->next;
        }
    }
    if(G->Vnum!=tail)//判断拓扑序列中的点数是否和整个图的点数一样
    {
        printf("该图不是有向无环图");
    }
}

计算vl【】,e【】,l【】

  • 重要参数:ve【】,事件开始的最早时间;Q【】,存储拓扑排序的序列;vl【】,存储事件最晚的开始时间;e【】存储活动最早的开始时间;l【】存储活动最晚的开始时间;point【】【】用于存储每个活动的起始事件和终止事件。
  • 首先根据逆拓扑排序和汇点计算vl【】
  • 计算e【】和l【】
  • 其中e【i】==l【i】的边为关键活动。
void GetPath(pGraph G,int ve[],int Q[])//求关键路径,ve【】为事件最早开始时间,Q【】为存储拓扑排序的队列
{
    int i,j;
    int head=0,tail=0,temp;
    int vl[G->Vnum],e[G->Arcnum],l[G->Arcnum];//vl【】为事件开始的最晚时间,e【】为活动呢开始的最早时间,l【】为活动开始的最晚时间
    int point[G->Arcnum][2];//point【】【】用于存储每个活动的起始事件和终止事件
    pArcnode s;
    tail=G->Vnum;
    for(i=0;i<G->Vnum;i++)//初始化事件最晚开始时间
    {
        vl[i]=0;
    }
    vl[Q[tail-1]]=ve[Q[tail-1]];//提出汇点进行计算
    tail--;
    while(head!=tail)//当头指针和尾指针指向同一个地方时结束,这个循环是为了通过逆拓扑排序计算事件最晚开始的时间
    {
        temp=Q[tail];//出队操作
        tail--;
        s=G->vertice[temp].firstarc;
        while(s!=NULL)//计算vl
        {
            if(vl[temp]<ve[s->adjvex]-s->weight)
            {
                vl[temp]=vl[s->adjvex]-s->weight;
            }
            s=s->next;
        }
    }
    for(i=0,j=0;i<G->Vnum;i++)//for循环是为了遍历图中每个点
    {
        s=G->vertice[i].firstarc;
        while(s!=NULL)//计算图中每个点(事件)的后继指针(活动)的最早开始时间
        {
            e[j]=ve[i];
            point[j][0]=i;
            point[j][1]=s->adjvex;
            j++;
            s=s->next;
        }
    }
    for(i=0,j=0;i<G->Vnum;i++)//for循环是为了遍历图中每个点
    {
        s=G->vertice[i].firstarc;
        while(s!=NULL)//计算图中每个点(事件)的后继指针(活动)的最晚开始时间
        {
            l[j]=vl[s->adjvex]-s->weight;
            j++;
            s=s->next;
        }
    }
    printf("关键路径为:\n");
    for(i=0;i<G->Arcnum;i++)//打印关键路径
    {
        if(e[i]==l[i])
        {
            printf("%d->%d:%d\n",point[i][0],point[i][1],e[i]);
        }
    }
}

源代码

#include 
#include 
typedef struct arcnode
{
    int weight;//权重
    int adjvex;//指向的下一个顶点
    struct arcnode *next;//指向这个点的另一条边
}Arcnode,*pArcnode;

typedef struct vnode
{
    pArcnode firstarc;//点所指向的第一条边
}Vnode,AdjList[30];

typedef struct graph
{
    int Vnum,Arcnum;//点的数目,边的数目
    AdjList vertice;
}Graph,*pGraph;

void CreateGraph(pGraph G)//构造AOE网
{
    int i;
    int node1,node2,weight;//暂时存储数据
    pArcnode p1;
    printf("请输入有向图的总点数:\n");
    scanf("%d",&G->Vnum);
    getchar();
    for(i=0;i<G->Vnum;i++)//给每个结点的第一个后继边初始化
    {
        G->vertice[i].firstarc=NULL;
    }
    printf("请输入无向图的总边数:\n");
    scanf("%d",&G->Arcnum);
    getchar();
    printf("请输入点和点之间的连接:(例:1 5 10)\n");
    for(i=0;i<G->Arcnum;i++)//循环输入边的信息
    {
        scanf("%d %d %d",&node1,&node2,&weight);
        getchar();
        p1=(pArcnode)malloc(sizeof(Arcnode));
        p1->adjvex=node2;//构造连接
        p1->weight=weight;
        p1->next=NULL;
        if(G->vertice[node1].firstarc==NULL)//还未有邻接点时
        {
            G->vertice[node1].firstarc=p1;
        }
        else//已有邻接点时
        {
            p1->next=G->vertice[node1].firstarc;
            G->vertice[node1].firstarc=p1;
        }
    }
}

void CalculateIndegree(pGraph G,int indegree[])
{
    int i;
    pArcnode s;
    for(i=0;i<G->Vnum;i++)//入度数组初始化
    {
        indegree[i]=0;
    }
    for(i=0;i<G->Vnum;i++)//计算每个点的入度
    {
        s=G->vertice[i].firstarc;
        while(s!=NULL)
        {
            indegree[s->adjvex]++;
            s=s->next;
        }
    }
}
void TuoPu(pGraph G,int ve[],int Q[])//求拓扑排序,ve【】存储事件开始的最早时间,Q【】队列存储拓扑排序
{
    int indegree[G->Vnum];
    int head=0,tail=0,temp;
    int i;
    pArcnode s;
    CalculateIndegree(G,indegree);//计算每个点的入度
    for(i=0;i<G->Vnum;i++)//初始化事件开始的最早时间
    {
        ve[i]=0;
    }
    for(i=0;i<G->Vnum;i++)//寻找一开始入度就为0的点
    {
        if(indegree[i]==0)//当入度为0时
        {
            Q[tail]=i;
            tail++;
        }
    }
    while(head!=tail)//计算整个图的拓扑排序
    {
        temp=Q[head];//出队
        head++;
        s=G->vertice[temp].firstarc;
        while(s!=NULL)//消除入度为零的点的后继边
        {
            indegree[s->adjvex]--;//为该点的后继边指向的点入度减1
            if(ve[s->adjvex]<ve[temp]+s->weight)//计算活动最早开始的时间
            {
                ve[s->adjvex]=ve[temp]+s->weight;
            }
            if(indegree[s->adjvex]==0)//判断入度减一后是否为入度为0的点
            {
                Q[tail]=s->adjvex;
                tail++;
            }
            s=s->next;
        }
    }
    if(G->Vnum!=tail)//判断拓扑序列中的点数是否和整个图的点数一样
    {
        printf("该图不是有向无环图");
    }
}

void GetPath(pGraph G,int ve[],int Q[])//求关键路径,ve【】为事件最早开始时间,Q【】为存储拓扑排序的队列
{
    int i,j;
    int head=0,tail=0,temp;
    int vl[G->Vnum],e[G->Arcnum],l[G->Arcnum];//vl【】为事件开始的最晚时间,e【】为活动呢开始的最早时间,l【】为活动开始的最晚时间
    int point[G->Arcnum][2];//point【】【】用于存储每个活动的起始事件和终止事件
    pArcnode s;
    tail=G->Vnum;
    for(i=0;i<G->Vnum;i++)//初始化事件最晚开始时间
    {
        vl[i]=0;
    }
    vl[Q[tail-1]]=ve[Q[tail-1]];//提出汇点进行计算
    tail--;
    while(head!=tail)//当头指针和尾指针指向同一个地方时结束,这个循环是为了通过逆拓扑排序计算事件最晚开始的时间
    {
        temp=Q[tail];//出队操作
        tail--;
        s=G->vertice[temp].firstarc;
        while(s!=NULL)//计算vl
        {
            if(vl[temp]<ve[s->adjvex]-s->weight)
            {
                vl[temp]=vl[s->adjvex]-s->weight;
            }
            s=s->next;
        }
    }
    for(i=0,j=0;i<G->Vnum;i++)//for循环是为了遍历图中每个点
    {
        s=G->vertice[i].firstarc;
        while(s!=NULL)//计算图中每个点(事件)的后继指针(活动)的最早开始时间
        {
            e[j]=ve[i];
            point[j][0]=i;
            point[j][1]=s->adjvex;
            j++;
            s=s->next;
        }
    }
    for(i=0,j=0;i<G->Vnum;i++)//for循环是为了遍历图中每个点
    {
        s=G->vertice[i].firstarc;
        while(s!=NULL)//计算图中每个点(事件)的后继指针(活动)的最晚开始时间
        {
            l[j]=vl[s->adjvex]-s->weight;
            j++;
            s=s->next;
        }
    }
    printf("关键路径为:\n");
    for(i=0;i<G->Arcnum;i++)//打印关键路径
    {
        if(e[i]==l[i])
        {
            printf("%d->%d:%d\n",point[i][0],point[i][1],e[i]);
        }
    }
}
int main()
{
    pGraph G;
    int ve[30];
    int Q[50];
    G=(pGraph)malloc(sizeof(Graph));
    CreateGraph(G);
    TuoPu(G,ve,Q);
    GetPath(G,ve,Q);
    return 0;
}

测试样例
9
11
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
4 6 8
4 7 7
5 7 4
6 8 2
7 8 4
结果截图
数据结构(六)——图之关键路径_第1张图片

你可能感兴趣的:(数据结构,应用)