C++数据结构之有向无环图——拓扑排序、AOV、AOE、关键路径

一、介绍

1.有向无环图(DAG)

2.拓扑排序

1.偏序

2.全序

3.拓扑有序

4.拓扑排序

3.AOV(Activity On Vertex 顶点表示活动的网)

概念

举例

应用

4.AOE(Activity On Edge 边表示活动的网)

概念

举例

性质

5.关键路径

概念

举例

二、实现拓扑排序

算法思想

 算法实现

1.DAG的创建

2.拓扑排序

3.全部代码

代码执行结果

三、实现求关键路径

算法思想

算法实现

有向图类:

得到拓扑排序的逆排序getcontrarytopostack():

求关键路径criticalpath():

全部代码:

代码执行结果

四、总结


一、介绍

1.有向无环图(DAG)

有向无环图(DAG:Directed Acycline Graph)指的是是图中无环的有向图

 我们要确定一个有向图是否为DAG,我们可以利用之前所学到的DFS深度优先搜索,查看是否存在一条路径可以从起点出发并再次回到起点

 如此图,我们选择顶点0进行DFS,那么按照深度优先遍历的规则,遍历顺序应该是0-1-2-4-3

在遍历每个顶点的时候,我们加入判断其是否产生了回路即可,因此当遍历到顶点4的时候,算法就会检测到回路的存在,因此该图不是一个DAG

2.拓扑排序

设我们现在有这么一个集合L={1,2,3,4},这个集合存在一个关系R={<1,1>,<2,2>,<3,3>,<4,4>,<1,2>,<2,1>,<1,3>,<3,1>,<1,4>,<4,1>,<2,3>,<3,2>,<2,4>,<4,2>,<3,4>,<4,3>},以及关系R1={<1,1>,<2,2>,<3,3>,<4,4>,<1,2>,<1,3>,<1,4>,<2,3>,<2,4>,<3,4>}

1.偏序

自反性:任取一个集合L中的元素x,<x,x>都存在于R中,那么关系R是自反的

example:不管我们选择哪个元素,从1到4,我们都能找到<1,1>到<4,4>存在于R中,因此R是自反的

对称性:任取集合L中的两个元素x和y,如果<x,y>在R中,那么<y,x>也在R中,那么关系R是对称的

example:我们选择1和3,<1,3>存在于R中,<3,1>也存在于R中,选择2和4,<2,4>存在于R中,<4,2>也存在于R中,那么关系R是对称的

反对称性:任取集合L中的两个元素x和y,如果<x,y>在R中,那么<y,x>就不在R中,那么关系R是反对称的

example:我们选择1和3,<1,3>在R1中,<3,1>不在R1中,选择2和4,<2,4>在R1中,<4,2>不在R1中,那么关系R1是反对称的

传递性:任取集合L中的三个元素x、y、z,如果<x,y>和<y,z>都存在于R中那么<x,z>也存在于R中,那么关系R是传递的

exm:我们选择1、3、4,<1,3>和<3,4>都存在于R中,且<1,4>也存在于R中,因此关系R是传递的

结论:如果R是自反的,反对称的,和传递的,则称R是A上的偏序关系

2.全序

如果关系R是集合X上的偏序,那么如果对于每个L中的x,y,R中必定有xRy或者yRx就可以称R是X上的全序关系(xRy 指的是元素x和元素y在R中的关系且是有序的)

偏序和全序的不同:

偏序指的是集合中只有部分成员之间可比较,全序指的是集合中的全体成员均可比较,因此全序时特殊的偏序

3.拓扑有序

如下图其实就存在偏序关系,因为顶点1和顶点3是没有先后关系的,因此当选取顶点1和顶点3的时候,你会发现无法满足偏序定义中的自反性、反对称性、传递性。

 但如如果我们加入顶点1和顶点3的先后关系后,这张图就变成了全序的了,如下图所示,此时整张图中的任取两个元素都将会满足偏序的关系,又因为全部元素任取两个都可以满足偏序关系,因此是全序的,此时我们就可以称下图为拓扑有序的。

4.拓扑排序

由偏序的定义得到的拓扑有序的操作称作拓扑排序,需要注意的是,拓扑排序针对的是DAG实现的,如果有向图中存在回路的话那么拓扑排序是不可实现的

拓扑排序算法流程:

1)在有向图中选择一个没有前驱的顶点并且将其输出

2)从图中将该顶点以及所有以这个顶点为尾的弧删除

3)重复1,2步直到所有的顶点都被输出

举例说明

 观察下图,存在0-1-2-0,0-3-2-0这两条回路,那么这个有向图无法进行拓扑排序

3.AOV(Activity On Vertex 顶点表示活动的网)

概念

i.AOV一定是DAG,即有向无环图

ii.如果用有向图的顶点表示活动,用弧表示活动之间的优先关系,那么我们就称该有向图为AOV

iii.AOV的应用包括流程图等等

举例

 如上图是一个有向无环图,我们按照拓扑排序的规则

拓扑排序算法流程:

1)在有向图中选择一个没有前驱的顶点并且将其输出

2)从图中将该顶点以及所有以这个顶点为尾的弧删除

3)重复1,2步直到所有的顶点都被输出

 我们先在这个DAG中寻找一个没有前驱的顶点,查询可以得到C1和C2这两个顶点,因此我们需要选择一个顶点,我们先选择C1并输出,然后继续按照规则可以得到下面的动图演示

 最后得到的拓扑排序输出为C1 , C8 , C9 , C2 , C3 , C4, C5 , C6 , C7

当然了可能有人会有疑问,删除了C4之后为什么先删除C5而不去删除C7,这里我们需要考虑到的是,每次递归我们都要涉及一个先后顺序的问题,而一般来说是从小到大进行遍历的,因此当发现序号较小的C5并且符合拓扑排序的规则的时候我们优先选择删除C5并输出

因此拓扑排序是有多种可能的,具体的拓扑排序结果要视代码的执行情况而定

应用

依然如上面的图一样,此时各个顶点代表的是一门课程,弧表示的是先修的关系,那么按照我们的拓扑排序我们可以得到对应的修学课程的顺序

4.AOE(Activity On Edge 边表示活动的网)

概念

i.AOV一定是DAG,即有向无环图

ii.如果用有向图的顶点表示事件,用弧表示活动,那么我们就称该有向图为AOV

iii.没有入弧的顶点称作源点,如下图中的顶点0

iv.没有出弧的顶点称作汇点,如下图中的顶点4

v.AOV的应用包括工程的完成时间等等

举例

 如上图所示顶点表示的就是相应的事件,弧表示的就是活动,弧上的权值表示的就是持续时间

性质

1.只有在进入某个顶点的全部活动都结束了,该顶点所代表的事件才会发生

example:如上图所示,只有当<1,2>和<3,2>都执行完毕了之后顶点2所代表的事件才会发生

2.只有在某个顶点所代表的事件结束之后,从该顶点出发的各活动才会开始

example:如上图所示,只有当顶点2所代表的事件执行完毕了,从顶点2出发的<2,4>活动才会开始

5.关键路径

概念

示例图

1.关键路径与关键活动

在AOE中,当所有活动完成才意味着一个工程的完成,因此完成整个工程所必须花费的时间,应该是从源点到汇点的最大路径长度,能具有最大路径长度的路径称为关键路径,而关键路径上的活动称为关键活动

2.事件的最早发生时间:ve[k](ve代表着事件对应的vertex和earliest)

计算方式:从源点开始

当全部进入事件的活动都结束之后这个事件才可以进行,设有两个活动<i,k>和<j,k>进入事件k,因此事件k的最早发生时间为ve[k]=max(ve[j]+length<j,k> , ve[i]+length<i,k>)

exmaple:我们要求事件4的最早发生时间,ve[4]=max( ve[1]+length<1,4> ,ve[2]+length<2,4> )

因为一个工程是从源点开始的,因此事件0的最早发生时间ve[0]=0,事件1只有一个活动进入也就是<0,1>,因此ve[1]=ve[0]+leng<0,1>=2,同理可求ve[2]=ve[0]+length<0,2>=1,因此ve[4]=max(6,6)=6,因此事件4的最早开始时间就是6,最终我们还可以求得事件5也就是汇点的最早发生时间=9

3.事件的最迟发生时间:vl[k](vl代表着事件对应的vertex和latest)

计算方式:从汇点开始

设有两个活动<k,i>,<k,j>从事件k出发,分别到达事件i和事件j,那么vl[k]=min(vl[i]-length<k,i>,vl[j]-length<k,j>)

example:我们要求事件1的最晚发生时间,vl[1]=min(vl[3]-length<1,3>,vl[4]-length<1,4>)

当求出汇点事件的最早发生时间之后,也就求出了汇点的最迟发生时间,即ve[汇点]=vl[汇点]=9,同时ve[源点]=vl[源点]=0,那么我们根据最迟发生时间的计算方式我们可以得到vl[3]=vl[5]-length<3,5>=5,vl[4]=vl[5]-length<4,5>=7,因此vl[1]=min(vl[3]-length<1,3>,vl[4]-length<1,4>)=min(2,3)=2

4.活动的最早发生时间:ee[i](ee:event earliest)

由于代表事件的顶点的出弧代表活动,当该事件未发生的时候活动自然也没法发生,因此活动的最早发生时间就是代表活动的弧尾顶点所代表事件的最早发生时间,我们将全部弧按照一定的顺序排序并且赋予一个下标(a0-a6),由此我们可以得到:

ee[0]=ve[0]=0        ee[1]=ve[0]=0       ee[2]=ve[1]=2        ee[3]=ve[1]=2         ee[4]:=ve[2]=1       ee[5]=ve[3]=5        ee[6]=ve[4]=6

5.活动的最迟发生时间:el[k](el:event latest)

同理我们可以得到:

el[0]=vl[1]-a0=0        el[1]=vl[2]-a1=1        el[2]=vl[3]-a2=2        el[3]=vl[4]-a3=3        el[4]=vl[4]-a4=2        el[5]=vl[5]=9

举例

 如上图所示是一个AOE,根据上面对相关概念的解释,我们可以知道这个AOE的关键路径是0-1-4-5,关键顶点是0、1、4、5,事件0的最早发生时间为0,事件1的最早发生时间为ve[0]+length<0,2>=2,事件4的最迟发生时间为max(vl[1]+length<1,4> , vl[2]+length<2,4>)=6,活动最早发生时间活动最晚发生时间以此类推就好

二、实现拓扑排序

算法思想

拓扑排序算法流程:

1)在有向图中选择一个没有前驱的顶点并且将其输出

2)从图中将该顶点以及所有以这个顶点为尾的弧删除

3)重复1,2步直到所有的顶点都被输出

 算法实现

1.DAG的创建

我们在这里选择使用邻接矩阵来储存DAG,adjmatrix[i][j]=-1表示顶点i无法找到一条弧直接到达顶点j,adjmatrix[i][i]表示的是自身到自身的弧的距离,我们可以知道,一个DAG中是无法找到一个自身到自身的弧的,因此我们也赋值为-1

有向图类:

class indigraph
{
    public:
    int maxvertexindex=0;//最大顶点序号
    int flag[maxsize];//用来标志一个顶点是否被访问
    int adjmatrix[maxsize][maxsize];//邻接矩阵用来存储有向图
    void createindigraph();//创建有向图
    void initflag();
    void initadjmatrix();
    void topo();//拓扑排序
    void printadjmatrix();//打印邻接矩阵
};

创建有向图:

void indigraph::createindigraph()
{
    int bowtail;//弧尾顶点
    int weight;//权重
    int symbol=0;//标志位
    for(int i=0;symbol==0;i++)
    {
        while (true)
        {
            cout<<"请输入顶点"<<i<<"的出弧弧尾:"<<endl;
            cin>>bowtail;
            if (bowtail>maxvertexindex)
            {
                maxvertexindex=bowtail;
            }
            if (bowtail==-1)
            {
                break;
            }
            if (bowtail==-2)
            {
                symbol=1;
                break;
            }
            cout<<"请输入弧<"<<i<<","<<bowtail<<">的权值:"<<endl;
            cin>>weight;
            adjmatrix[i][bowtail]=weight;
        }
    }
    cout<<"邻接矩阵构建完毕"<<endl;
}

打印有向图邻接矩阵:

void indigraph::printadjmatrix()
{
    cout<<"============adjmatrix================"<<endl;
    for (int i = 0; i <maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            cout<<adjmatrix[i][j]<<"  ";
        }
        cout<<endl;
    }
    cout<<"====================================="<<endl;
}

2.拓扑排序

根据算法思想中的拓扑排序算法流程,我们需要先找到没有前驱的顶点,在邻接矩阵adjmatrix[][]中,adjmatrix[i][j]表示的是从顶点i到达顶点j的弧,i为弧尾,j为弧头,其中的数值为弧的权值,因此如果我们要确定一个顶点k是否有前驱顶点,我们只需要遍历adjmatrix[][k],如果发现存在值不为无穷大,则说明存在一个顶点到达顶点k,那么顶点k就有前驱顶点,反之如果遍历完毕之后发现所有值都为无穷大那么就说明这个顶点没有前驱顶点

void indigraph::topo()
{
    int i,j;
    queue<int> q;//创建一个队列用来存放每次找到的无前驱顶点的顶点
    queue<int> topoqueue;//创建一个队列用来存储拓扑排序的结果
    while(topoqueue.size()!=5)
    {
        for (i = 0; i < maxvertexindex+1; i++)//两层循环用来对整个邻接矩阵进行遍历
        {
            for (j = 0; j < maxvertexindex+1; j++)
            {
                if (adjmatrix[j][i]!=-1)//一旦发现某一列存在非-1的的数值说明该列对应的顶点不是无前驱顶点
                {
                    break;
                }
            }  
            if (j==maxvertexindex+1&&flag[i]!=1)//当遍历完整列并且发现该列对应的顶点未被访问过
            {
                q.push(i);//临时队列添加无前驱顶点
                topoqueue.push(i);//结果队列添加无前驱顶点
                flag[i]=1;//标记这个顶点被访问过
            } 
        }
        while (q.empty()!=true)//当临时队列不为空说明还没访问完毕
        {
            int v=q.front();//存储临时队列的头元素
            q.pop();//弹出元素
            for (int i = 0; i < maxvertexindex+1; i++)//并将对应顶点所参与的弧删除
            {
                adjmatrix[v][i]=-1;//标记为-1就意味着删除
            }
        }

    }
    cout<<"拓扑排序为:"<<endl;//输出拓扑排序结果
    while (topoqueue.size()!=0)
    {
        cout<<topoqueue.front()<<"  ";
        topoqueue.pop();
    }
}

3.全部代码

 #include<stdio.h>
 #include<iostream>
 #include<queue>
 #define maxsize 100
 #define infi 999
 using namespace std;
 
class indigraph
{
    public:
    int maxvertexindex=0;//最大顶点序号
    int flag[maxsize];//用来标志一个顶点是否被访问
    int adjmatrix[maxsize][maxsize];//邻接矩阵用来存储有向图
    void createindigraph();//创建有向图
    void initadjmatrix();
    void initflag();
    void topo();//拓扑排序
    void printadjmatrix();//打印邻接矩阵
};

void indigraph::createindigraph()
{
    int bowtail;//弧尾顶点
    int weight;//权重
    int symbol=0;//标志位
    for(int i=0,k=0;symbol==0;i++)
    {
        while (true)
        {
            cout<<"请输入顶点"<<i<<"的出弧弧尾:"<<endl;
            cin>>bowtail;
            if (bowtail>maxvertexindex)
            {
                maxvertexindex=bowtail;
            }
            if (bowtail==-1)
            {
                break;
            }
            if (bowtail==-2)
            {
                symbol=1;
                break;
            }
            if (i>maxvertexindex)
            {
                maxvertexindex=i;
            }
            cout<<"请输入弧<"<<i<<","<<bowtail<<">的权值:"<<endl;
            cin>>weight;
            adjmatrix[i][bowtail]=weight;
        }
    }
    cout<<"邻接矩阵构建完毕"<<endl;
}

void indigraph::initadjmatrix()
{
    for (int i = 0; i < maxsize; i++)
    {
        for (int j = 0; j < maxsize; j++)
        {
            adjmatrix[i][j]=-1;
        }
    }
}

void indigraph::initflag()
{
    for (int i = 0; i < maxvertexindex; i++)
    {
        flag[i]=0;
    }
    
}

void indigraph::printadjmatrix()
{
    cout<<"======adjmatrix======"<<endl;
    for (int i = 0; i <maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            cout<<adjmatrix[i][j]<<"  ";
        }
        cout<<endl;
    }
    cout<<"====================="<<endl;
}

void indigraph::topo()
{
    int i,j;
    queue<int> q;//创建一个队列用来存放每次找到的无前驱顶点的顶点
    queue<int> topoqueue;//创建一个队列用来存储拓扑排序的结果
    while(topoqueue.size()!=maxvertexindex+1)
    {
        for (i = 0; i < maxvertexindex+1; i++)//两层循环用来对整个邻接矩阵进行遍历
        {
            for (j = 0; j < maxvertexindex+1; j++)
            {
                if (adjmatrix[j][i]!=-1)//一旦发现某一列存在非-1的的数值说明该列对应的顶点不是无前驱顶点
                {
                    break;
                }
            }  
            if (j==maxvertexindex+1&&flag[i]!=1)//当遍历完整列并且发现该列对应的顶点未被访问过
            {
                q.push(i);//临时队列添加无前驱顶点
                topoqueue.push(i);//结果队列添加无前驱顶点
                flag[i]=1;//标记这个顶点被访问过
            } 
        }
        while (q.empty()!=true)//当临时队列不为空说明还没访问完毕
        {
            int v=q.front();//存储临时队列的头元素
            q.pop();//弹出元素
            for (int i = 0; i < maxvertexindex+1; i++)//并将对应顶点所参与的弧删除
            {
                adjmatrix[v][i]=-1;//标记为-1就意味着删除
            }
        }

    }
    cout<<"拓扑排序为:"<<endl;//输出拓扑排序结果
    while (topoqueue.size()!=0)
    {
        cout<<topoqueue.front()<<"  ";
        topoqueue.pop();
    }
}

 int main()
 {
    indigraph ig;
    ig.initadjmatrix();
    ig.initflag();
    ig.createindigraph();
    ig.printadjmatrix();
    ig.topo();
    return 0;
 }

代码执行结果

 如上图是一个DAG,我们将其导入到该算法之中我们可以得到下图的结果:

三、实现求关键路径

 

如上图所示是一个工程的进度计划,用AOE表示,现在我们要求出这个工程的最短工期和关键路径,从而知道应该缩短什么活动的事件从而缩短工期

算法思想

1.求出所有事件的最早发生时间

2.由所有事件的最早发生时间求出所有活动的最早发生时间

3.由汇点事件的最早发生时间得到汇点事件的最迟发生时间

4.由汇点事件的最迟发生时间求出所有事件的最迟发生时间

5.由所有事件的最迟发生时间求出所有活动的最迟发生时间

6.对比活动的最早发生时间和最迟发生时间,相同的即为关键活动,关键活动经过的事件就是关键顶点

7.由关键活动或关键事件就可以得到关键路径

算法实现

有向图类:

tips:为了方便测试,我将AOE图直接存入了有向图类中,如果想要测试其他图的话请按照下面代码中注释的提示来操作

我们可以知道,我们已经实现了拓扑排序并且能将结果储存在一个队列topoqueue中,对于我们求取事件的最晚发生时间时,我们要按照逆拓扑排序来对事件进行操作,那么要怎么得到一个逆拓扑排序的数据结构呢,我们可以使用,当我们把队列的元素依次弹出并放入栈中,此时我们就得到了逆拓扑排序,只要我们不断取出栈顶的数,取出的数字顺序就是逆拓扑排序的顺序,因此我们就创建了下图的三种数据结构

    queue<int> topoqueue;//创建一个队列用来存储拓扑排序的结果
    queue<int> transferqueue;//创建一个临时队列用来间接得到存放拓扑排序的逆排序栈
    stack<int> contrarytopostack;//创建一个栈用来存放拓扑排序的逆排序栈

 并且我们为了比较最早时间和最迟时间,我们需要创建多个数组分别是ve[],vl[],ee[],el[]用来存储这些时间结果,并且我们在进行对比之后我们还需要一个队列criticalevent[]用来存储关键事件,一个数组criticalactivity[]用来储存关键活动,下图就是有向图类的全部成员变量和成员函数

class indigraph
{
    public:
    queue<int> topoqueue;//创建一个队列用来存储拓扑排序的结果
    queue<int> transferqueue;//创建一个临时队列用来间接得到存放拓扑排序的逆排序栈
    stack<int> contrarytopostack;//创建一个栈用来存放拓扑排序的逆排序栈
    int ve[maxsize];//事件的最早发生时间
    int vl[maxsize];//事件的最迟发生时间
    int ee[maxsize];//活动的最早发生时间
    int el[maxsize];//活动的最迟发生时间
    queue<int> criticalevent;//关键事件
    int criticalactivity[maxsize];//关键活动
    //int arcnum;//如果是自己输入图就取消这一行的注释并注释下一行
    int arcnum=14;
    //int maxvertexindex=0;//如果是自己输入图就取消这一行的注释并注释下一行
    int maxvertexindex=9;//最大顶点序号
    int flag[maxsize];//用来标志一个顶点是否被访问
    //int adjmatrix[maxsize][maxsize];//如果是自己输入图就取消这一行的注释并注释下一行
    int adjmatrix[maxsize][maxsize]=//邻接矩阵用来存储有向图
    {   {-1,5,6,-1,-1,-1,-1,-1,-1,-1},//0
        {-1,-1,-1,3,-1,-1,-1,-1,-1,-1},//1
        {-1,-1,-1,12,3,-1,-1,-1,-1,-1},//2
        {-1,-1,-1,-1,3,3,5,-1,-1,-1},//3
        {-1,-1,-1,-1,-1,-1,1,4,-1,-1},//4
        {-1,-1,-1,-1,-1,-1,-1,-1,-1,4},//5
        {-1,-1,-1,-1,-1,-1,-1,-1,5,-1},//6
        {-1,-1,-1,-1,-1,-1,-1,-1,2,-1},//7
        {-1,-1,-1,-1,-1,-1,-1,-1,-1,2},//8
        {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}//9
    };
    void createindigraph();//创建有向图
    void initadjmatrix();//初始化邻接矩阵
    void initflag();//初始化访问数组
    void getcontrarytopostack();//得到拓扑排序的逆排序
    void topo();//拓扑排序
    void criticalpath();//求关键路径
    void printadjmatrix();//打印AOE图的邻接矩阵
};

得到拓扑排序的逆排序getcontrarytopostack():

void indigraph::getcontrarytopostack()
{
    while (transferqueue.empty()!=true)
    {
        contrarytopostack.push(transferqueue.front());
        transferqueue.pop();
    }   
}

求关键路径criticalpath():

void indigraph::criticalpath()
{
    //求出所有事件的最早发生时间
    int source=topoqueue.front();//求源点
    cout<<source<<endl;
    ve[source]=0;//源点的最早发生时间为0
    cout<<"ve["<<source<<"]:"<<0<<"   ";
    topoqueue.pop();//源点弹出
    while (topoqueue.empty()!=true)
    {
        int tempvertex=topoqueue.front();
        //cout<<"当前操作的顶点为"<<tempvertex<<endl;
        int tempmax=0;
        for (int i = 0; i < maxvertexindex+1; i++)
        {
            if (adjmatrix[i][tempvertex]!=-1&&ve[i]+adjmatrix[i][tempvertex]>tempmax)
            {
                //cout<<"查询到弧:<"<<i<<","<<tempvertex<<">"<<endl;
                //cout<<"该弧对应的最早发生时间为:"<<ve[i]+adjmatrix[i][tempvertex]<<endl;
                tempmax=ve[i]+adjmatrix[i][tempvertex];
                //cout<<"tempmax="<<tempmax<<endl;    
            }  
        }
        ve[tempvertex]=tempmax;
        cout<<"ve["<<tempvertex<<"]:"<<tempmax<<"   ";
        topoqueue.pop();
    }

    //求出所有活动的最早发生时间
    int k=0;
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            if (adjmatrix[i][j]!=-1)
            {
                ee[k]=ve[i];
                k+=1;
            }
        }
    }

    cout<<endl;
    //求出所有事件的最迟发生时间
    getcontrarytopostack();//得到存放拓扑排序的栈,并且只能从尾部开始取数,因此取数的顺序就成为了逆拓扑排序
    vl[contrarytopostack.top()]=ve[contrarytopostack.top()];//汇点事件的最迟发生时间=汇点事件的最早发生时间
    cout<<"vl["<<contrarytopostack.top()<<"]:"<<ve[contrarytopostack.top()]<<"   ";
    contrarytopostack.pop();
    while (contrarytopostack.empty()!=true)
    {
        int tempvertex1=contrarytopostack.top();
        //cout<<"当前操作的顶点为"<<tempvertex1<<endl;
        int tempmin=infi;
        for (int i = 0; i < maxvertexindex+1; i++)
        {
            if (adjmatrix[tempvertex1][i]!=-1&&vl[i]-adjmatrix[tempvertex1][i]<tempmin)
            {
                //cout<<"查询到弧:<"<<tempvertex1<<","<<i<<">"<<endl;
                //cout<<"该弧对应的最迟发生时间为:"<<vl[i]-adjmatrix[tempvertex1][i]<<endl;
                tempmin=vl[i]-adjmatrix[tempvertex1][i];
                //cout<<"tempmin="<<tempmin<<endl;    
            }  
        }
        vl[tempvertex1]=tempmin;
        cout<<"vl["<<tempvertex1<<"]:"<<tempmin<<"   ";
        contrarytopostack.pop();
    }


    //求出所有活动的最迟发生时间
    k=0;
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            if (adjmatrix[i][j]!=-1)
            {
                el[k]=vl[i];
                k+=1;
            }
        }
    }
    cout<<endl;
    //求出最早发生时间和最迟发生时间相同的事件即关键事件
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        if (ve[i]==vl[i])
        {
            cout<<"事件"<<i<<"的最早发生时间和最迟发生时间相同"<<endl;
            criticalevent.push(i);
        } 
    }
    cout<<"criticalevent.size()="<<criticalevent.size()<<endl;
    //求出最早发生时间和最迟发生时间相同的活动即关键活动
    int p=0;
    for (int i = 0; i < 14; i++)
    {
        if (ee[i]==el[i])
        {
            cout<<"活动a"<<i+1<<"的最早发生时间和最迟发生时间相同"<<endl;
            criticalactivity[p]=i+1;
            p+=1;
        } 
    }
    
    //由关键事件或关键活动得到关键路径
    //由关键事件得到关键路径
    int temp;
    int length=criticalevent.size()-1;
    for (int i = 0; i <length; i++)
    {
        temp=criticalevent.front();
        criticalevent.pop();
        cout<<temp<<"---"<<adjmatrix[temp][criticalevent.front()]<<"---"<<criticalevent.front()<<endl;
    }
    
}

全部代码:

 #include<stdio.h>
 #include<iostream>
 #include<queue>
 #include<stack>
 #define maxsize 100
 #define infi 999
 using namespace std;
 
class indigraph
{
    public:
    queue<int> topoqueue;//创建一个队列用来存储拓扑排序的结果
    queue<int> transferqueue;//创建一个临时队列用来间接得到存放拓扑排序的逆排序栈
    stack<int> contrarytopostack;//创建一个栈用来存放拓扑排序的逆排序栈
    int ve[maxsize];//事件的最早发生时间
    int vl[maxsize];//事件的最迟发生时间
    int ee[maxsize];//活动的最早发生时间
    int el[maxsize];//活动的最迟发生时间
    queue<int> criticalevent;//关键事件
    int criticalactivity[maxsize];//关键活动
    //int arcnum;//如果是自己输入图就取消这一行的注释并注释下一行
    int arcnum=14;
    //int maxvertexindex=0;//如果是自己输入图就取消这一行的注释并注释下一行
    int maxvertexindex=9;//最大顶点序号
    int flag[maxsize];//用来标志一个顶点是否被访问
    //int adjmatrix[maxsize][maxsize];//如果是自己输入图就取消这一行的注释并注释下一行
    int adjmatrix[maxsize][maxsize]=//邻接矩阵用来存储有向图
    {   {-1,5,6,-1,-1,-1,-1,-1,-1,-1},//0
        {-1,-1,-1,3,-1,-1,-1,-1,-1,-1},//1
        {-1,-1,-1,12,3,-1,-1,-1,-1,-1},//2
        {-1,-1,-1,-1,3,3,5,-1,-1,-1},//3
        {-1,-1,-1,-1,-1,-1,1,4,-1,-1},//4
        {-1,-1,-1,-1,-1,-1,-1,-1,-1,4},//5
        {-1,-1,-1,-1,-1,-1,-1,-1,5,-1},//6
        {-1,-1,-1,-1,-1,-1,-1,-1,2,-1},//7
        {-1,-1,-1,-1,-1,-1,-1,-1,-1,2},//8
        {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}//9
    };
    void createindigraph();//创建有向图
    void initadjmatrix();//初始化邻接矩阵
    void initflag();//初始化访问数组
    void getcontrarytopostack();//得到拓扑排序的逆排序
    void topo();//拓扑排序
    void criticalpath();//求关键路径
    void printadjmatrix();//打印AOE图的邻接矩阵
};

void indigraph::createindigraph()
{
    initadjmatrix();
    initflag();
    int bowtail;//弧尾顶点
    int time;//权重
    int symbol=0;//标志位
    for(int i=0,k=0;symbol==0;i++)
    {
        while (true)
        {
            cout<<"请输入事件"<<i<<"的出弧弧尾:"<<endl;
            cin>>bowtail;
            if (bowtail>maxvertexindex)
            {
                maxvertexindex=bowtail;
            }
            if (bowtail==-1)
            {
                break;
            }
            if (bowtail==-2)
            {
                symbol=1;
                break;
            }
            if (i>maxvertexindex)
            {
                maxvertexindex=i;
            }
            arcnum+=1;
            cout<<"请输入活动<"<<i<<","<<bowtail<<">的时间:"<<endl;
            cin>>time;
            adjmatrix[i][bowtail]=time;
        }
    }
    cout<<"邻接矩阵构建完毕"<<endl;
}

void indigraph::initadjmatrix()
{
    for (int i = 0; i < maxsize; i++)
    {
        for (int j = 0; j < maxsize; j++)
        {
            adjmatrix[i][j]=-1;
        }
    }
}

void indigraph::initflag()
{
    for (int i = 0; i < maxvertexindex; i++)
    {
        flag[i]=0;
    }
    
}

void indigraph::printadjmatrix()
{
    cout<<"=============adjmatrix============="<<endl;
    for (int i = 0; i <maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            cout<<adjmatrix[i][j]<<"  ";
        }
        cout<<endl;
    }
    cout<<"==================================="<<endl;
}

void indigraph::topo()
{
    int i,j;
    queue<int> q;//创建一个队列用来存放每次找到的无前驱顶点的顶点
    int tempadjmatrix[maxsize][maxsize];
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            tempadjmatrix[i][j]=adjmatrix[i][j];
        }
        
    }
    
    while(topoqueue.size()!=maxvertexindex+1)
    {
        for (i = 0; i < maxvertexindex+1; i++)//两层循环用来对整个邻接矩阵进行遍历
        {
            for (j = 0; j < maxvertexindex+1; j++)
            {
                if (tempadjmatrix[j][i]!=-1)//一旦发现某一列存在非-1的的数值说明该列对应的顶点不是无前驱顶点
                {
                    break;
                }
            }  
            if (j==maxvertexindex+1&&flag[i]!=1)//当遍历完整列并且发现该列对应的顶点未被访问过
            {
                q.push(i);//临时队列添加无前驱顶点
                topoqueue.push(i);//结果队列添加无前驱顶点
                flag[i]=1;//标记这个顶点被访问过
            } 
        }
        while (q.empty()!=true)//当临时队列不为空说明还没访问完毕
        {
            int v=q.front();//存储临时队列的头元素
            q.pop();//弹出元素
            for (int i = 0; i < maxvertexindex+1; i++)//并将对应顶点所参与的弧删除
            {
                tempadjmatrix[v][i]=-1;//标记为-1就意味着删除
            }
        }

    }
    cout<<"拓扑排序为:"<<endl;//输出拓扑排序结果
    queue<int> tempqueue;
    while (topoqueue.size()!=0)
    {
        cout<<topoqueue.front()<<"  ";
        tempqueue.push(topoqueue.front());
        transferqueue.push(topoqueue.front());
        topoqueue.pop();
    }
    while (tempqueue.empty()!=true)
    {
        topoqueue.push(tempqueue.front());
        tempqueue.pop();
    }
    
}

void indigraph::getcontrarytopostack()
{
    while (transferqueue.empty()!=true)
    {
        contrarytopostack.push(transferqueue.front());
        transferqueue.pop();
    }   
}

void indigraph::criticalpath()
{
    //求出所有事件的最早发生时间
    int source=topoqueue.front();//求源点
    cout<<source<<endl;
    ve[source]=0;//源点的最早发生时间为0
    cout<<"ve["<<source<<"]:"<<0<<"   ";
    topoqueue.pop();//源点弹出
    while (topoqueue.empty()!=true)
    {
        int tempvertex=topoqueue.front();
        //cout<<"当前操作的顶点为"<<tempvertex<<endl;
        int tempmax=0;
        for (int i = 0; i < maxvertexindex+1; i++)
        {
            if (adjmatrix[i][tempvertex]!=-1&&ve[i]+adjmatrix[i][tempvertex]>tempmax)
            {
                //cout<<"查询到弧:<"<<i<<","<<tempvertex<<">"<<endl;
                //cout<<"该弧对应的最早发生时间为:"<<ve[i]+adjmatrix[i][tempvertex]<<endl;
                tempmax=ve[i]+adjmatrix[i][tempvertex];
                //cout<<"tempmax="<<tempmax<<endl;    
            }  
        }
        ve[tempvertex]=tempmax;
        cout<<"ve["<<tempvertex<<"]:"<<tempmax<<"   ";
        topoqueue.pop();
    }

    //求出所有活动的最早发生时间
    int k=0;
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            if (adjmatrix[i][j]!=-1)
            {
                ee[k]=ve[i];
                k+=1;
            }
        }
    }

    cout<<endl;
    //求出所有事件的最迟发生时间
    getcontrarytopostack();//得到存放拓扑排序的栈,并且只能从尾部开始取数,因此取数的顺序就成为了逆拓扑排序
    vl[contrarytopostack.top()]=ve[contrarytopostack.top()];//汇点事件的最迟发生时间=汇点事件的最早发生时间
    cout<<"vl["<<contrarytopostack.top()<<"]:"<<ve[contrarytopostack.top()]<<"   ";
    contrarytopostack.pop();
    while (contrarytopostack.empty()!=true)
    {
        int tempvertex1=contrarytopostack.top();
        //cout<<"当前操作的顶点为"<<tempvertex1<<endl;
        int tempmin=infi;
        for (int i = 0; i < maxvertexindex+1; i++)
        {
            if (adjmatrix[tempvertex1][i]!=-1&&vl[i]-adjmatrix[tempvertex1][i]<tempmin)
            {
                //cout<<"查询到弧:<"<<tempvertex1<<","<<i<<">"<<endl;
                //cout<<"该弧对应的最迟发生时间为:"<<vl[i]-adjmatrix[tempvertex1][i]<<endl;
                tempmin=vl[i]-adjmatrix[tempvertex1][i];
                //cout<<"tempmin="<<tempmin<<endl;    
            }  
        }
        vl[tempvertex1]=tempmin;
        cout<<"vl["<<tempvertex1<<"]:"<<tempmin<<"   ";
        contrarytopostack.pop();
    }


    //求出所有活动的最迟发生时间
    k=0;
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        for (int j = 0; j < maxvertexindex+1; j++)
        {
            if (adjmatrix[i][j]!=-1)
            {
                el[k]=vl[i];
                k+=1;
            }
        }
    }
    cout<<endl;
    //求出最早发生时间和最迟发生时间相同的事件即关键事件
    for (int i = 0; i < maxvertexindex+1; i++)
    {
        if (ve[i]==vl[i])
        {
            cout<<"事件"<<i<<"的最早发生时间和最迟发生时间相同"<<endl;
            criticalevent.push(i);
        } 
    }
    cout<<"criticalevent.size()="<<criticalevent.size()<<endl;
    //求出最早发生时间和最迟发生时间相同的活动即关键活动
    int p=0;
    for (int i = 0; i < 14; i++)
    {
        if (ee[i]==el[i])
        {
            cout<<"活动a"<<i+1<<"的最早发生时间和最迟发生时间相同"<<endl;
            criticalactivity[p]=i+1;
            p+=1;
        } 
    }
    
    //由关键事件或关键活动得到关键路径
    //由关键事件得到关键路径
    int temp;
    int length=criticalevent.size()-1;
    for (int i = 0; i <length; i++)
    {
        temp=criticalevent.front();
        criticalevent.pop();
        cout<<temp<<"---"<<adjmatrix[temp][criticalevent.front()]<<"---"<<criticalevent.front()<<endl;
    }
    
}

 int main()
 {
    indigraph ig;
    ig.initflag();
    ig.printadjmatrix();
    ig.topo();
    ig.criticalpath();
    return 0;
 }

代码执行结果

 我们可以求得关键路径为0-2-3-6-8-9

四、总结

本文的关键在于拓扑排序的算法实现以及关键路径的求取,特别是关键路径中的各种概念大家一定要理解清楚避免混乱,如果说喜欢本文的话麻烦点个赞👍哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

臭刘皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值