DAG - AOV - AOE - CPM - Topological-Sort 详解

1.DAG(Directed acycline graph)

DAG图,又称有向无环图,简称为DAG,DAG是相对更像是有向树一样的数据结构,用处十分的广泛
1.表达式树:
DAG可以模拟表达式树,按照数据结构老师的话来说,在操作系统方面的用处更加的广泛
2.检查图的环路:
我们都知道检查一个无向图是否存在回路是非常的简单的,我们只需要DFS遍历一遍就能判断出来
但是我们检查一个有向图是否有环确实非常的困难,这是后我们就需要用到基于DAG的一些基本而算法来解决这类或者更加复杂的问题
3.工程项目管理中:
在现实生活中,我们的工程项目的管理完全可以用DAG图进行模拟,一般来说,我们的模拟的形式有两种,一种是AOV,一种是AOE,这两种我们后续会进行讲解
现在我们基本上了解了什么是DAG,我们现在来看一下在DAG上的一些基本的操作和算法

2.Topological-Sort

拓扑排序:
什么是拓扑排序,我用我自己的语言来组织一下,拓扑排序的意思就是,在一个偏序关系上
我们通过排序操作生成一个序列,这个序列中的任意两个元素之间如果存在偏序关系的话,那么前面的元素必定在后面元素的前面
我们重新理解一下就是,我们将DAG图中的(有向图)中的所有的偏序关系我们通过一个序列的形式抽象成一个逻辑关系,这个逻辑关系保证了所有元素并第十按照次序发生(推导)的

摘引一些好理解的话:(援引自百度百科)

例如,假定一个计算机专业的学生必须完成图3-4所列出的全部课程。在这里,课程代表活动,学习一门课程就表示进行一项活动,学习每门课程的先决条件是学完它的全部先修课程。如学习《数据结构》课程就必须安排在学完它的两门先修课程《离散数学》和《算法语言》之后。学习《高等数学》课程则可以随时安排,因为它是基础课程,没有先修课。若用AOV网来表示这种课程安排的先后关系,则如图3-5所示。图中的每个顶点代表一门课程,每条有向边代表起点对应的课程是终点对应课程的先修课。图中的每个顶点代表一从图中可以清楚地看出各课程之间的先修和后续的关系。如课程C5的先修课为C2,后续课程为C4和C6。 [2]






现在我们对拓扑排序有了一定的了解,我们现在来关注于如何求解拓扑序列
求解拓扑序列有两种方法:

1.Kahn算法:

算法的原理如下:
1.我们找到图中的所有的入度为0的节点加入栈
2.将栈顶的节点的所有的边的弧头顶点入度递减
3.如果递减之后的结果为0,我们入栈,否则,pass
4.弹出栈顶

如果我们的生成的拓扑序列中的节点的个数和图中的定点的个数是相同的话,说明我们的图是拓扑有序的,也就间接证明了我们的图是DAG图
但是一旦缺少节点,就说明途中存在环,因为在环路中任何的节点的入度都不是0,所以我们没有办法删除节点

Kahn算法不仅可以生成拓扑序列还可以帮助我们检测一个有向图是不是DAG
对于时间复杂度来说,因为我们的Kahn算法只进行了遍历所有的点和所有的的边的操作,所以时间复杂度是O(v+e),V,E都是顶点和边的数目
但是我们为了达到这个时间复杂度需要引入栈这个数据结构,这是唯一的缺点

2.DFS - 反向构图

我们求解拓扑序列的另一个方法就是DFS,不过在这里我们的DFS的思路有些不同
首先,我们先说明,该算法虽然我们不需要Kahn算法的引入一个额外的辅助数据结构栈,但是我们DFS需要堆图进行反向构图

先上伪代码:(摘引自Wiki)
L ← Empty list that will contain the sorted nodes
S ← Set of all nodes with no outgoing edges   //刚好和Kahn反过来,我们从没有出度的顶点开始
for each node n in S do
    visit(n) 
function visit(node n)
    if n has not been visited yet then
        mark n as visited
        for each node m with an edgefrom m to ndo
            visit(m)
        add n to L
该算法最核心的一步操作就是我们的最后的add n to l
算法的原理我描述一下就是,我们反向构图的时候,我们的目的是为了可以正确的查找到该点的前驱,就是这个目的,所以我们的所有的有向边都需要反向,在我们内存中,我们的抽象的数据的存储结构可以通过增加逆邻接表实现

算法的原理:
我们从任意一个汇点出发,一旦找到了没有出度的顶点,我们就需要开始进行回溯操作,这时候我们存出结果就是拓扑序列

相对于Kahn算法来说,我们DFS的方法虽然不需要利用栈来存储结构,但是我们需要对图进行反向,这时候一旦我们对有向图有些的要求的时候,我们就不能满足了,在实际情况中,我们视情况来决定就好
时间复杂度也是一样,因为我们都是遍历了所有的顶点和所有的边O(v+e)

2.AOV

AOV网的定义是我们的所有的顶点代表我们的活动,我们的所有的边代表我们的活动之间的次序关系,AOV网的图更符合我们的显示生活中的习惯,我们的AOV王德图实际上更像是一个哈斯图,我们吐过AOV网可以清楚地明确所哟的活动之间的发生的次序关系
AOV王德定义决定AOV必须是一个DAG图,因为AOV网中不允许一个活动是以自己为其实的条件的,这和我们的显示的逻辑是相矛盾的
但是在求解CPM问题的时候,我们的AOV网的用处不大,因为我们的CPM要求姐关键路径的话,我们需要知道活动的权值和长度,这在AOV中反应的并不明显
这时候,我们引入另一个DAG图

3.AOE+CPM(DP思想)

AOE网:

首先引入两个必要的性质

AOE网的性质

⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;

⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。


边表示活动的网,边的权值代表了活动的周期,顶点表示事件,弧表示活动,我们通常利用AOE网来估算项目的工期
首先为了求解cpm问题(关键路径)我们先来了解几个定义和性质
1.CP的定义
CP全称是Critical Path,又叫关键路径,关键路径的长度是指从项目的起始点开始一直到终止点的最长的路径的长度,在关键路径上的活动的长度可以直接的反应出我们的工期的大小
2.事件:
我们在AOV网中定义事件为网的节点,我们成一个时间代表的含义就是,该事件的表示(在该事件之前的所有的邻接的活动全部的终止和之后的所有的临街的活动的开始)这个事件,有些绕口,好好理解就明白了
3.活动:
我们在AOV网中的活动定义为DAG图的有向b边,有向边的权值代表我们的活动的周期
DP思想
4.事件的最早发生时间 - Ve

如图来说,我们的vk事件想要发生的话,按照我么AOV网的性质,vk之前的所有的活动必须都要结束才行,这里的货,我们的最早发生时间就必须要取
max(ve[j]+weight<j,k>)才可以
定义
ve[k]= max(ve[j]+weight<j,k>) (<j,k>在DAG边集中)
ve[0]=0
5.事件的最晚发生时间 - Vl

如图,我们求解事件的最晚发生时间,需要逆推
因为我们vk事件想要最晚发生,至少我们必须要让之后的所有的事件的最晚发生时间都要保证满足,这是性质2
定义:
vl[k]=min(vl[j]-weight<k,j>) (<k,j>在DAG的边集中)
vl[n]=ve[n]
6.活动的最早发生时间 - E
我们的活动的想要最早发生只有当活动的前驱关联顶点事件最早发生即可,所以这里
E[k]=Ve[i] (i是k边(活动)的前驱结点)
7.活动的最晚发生时间 - L
我们的活动的最晚发生时间仍然需要我们逆推来进行
L[k]=Vl[i]-weight<K> (i是K活动的后继节点)

我们定义E[k]=L[k]的活动就是关键活动,从起点到汇点的连续的关键活动构成关键路径

那么上面的我就已经列举了我们的所有的需要求解CPM问题的所有的内容了
下面我们开始商讨一下怎么求解
1.拓扑排序
求解关键路径,我们需要再AOE网的拓扑有序的前提下进行,我们首先需要对AOE网进行拓扑排序
如果我们的拓扑排序的结果显示AOE网不是一个DAG图的话,我们就会发现,该图就不存在CP
也就是换句话来说,我们只有在拓扑有序和逆拓扑有序的前提下,我们的AOE网存在CP,否则不存在关键路径
但是我们这里需要注意一下,并不是说所有的关键活动都可以构成关键路径,但是关键路径上的活动都是关键活动,有的关键活动并不参与构成关键路径

2.
在拓扑排序的前提下之后
我们开始在拓扑序列的基础上开始求解Ve,Vl,E,L
我们的定义拓扑排序的序列的第一个元素的Ve=0,那么我们就可以顺利的得到所有的Ve,Vl,E,L

3.
我们找到所有的E,L相同的活动,这些活动就是我们的关键活动,此时我们想要打印我们的关键路径的时候,我们可以利用DFS的图的遍历的思路,从起点开始一次的向下查找,知道找到终点的时候我们将保存的节点的序列输出就是我们的关键路径的节点的序列

4.Code:

核心的求解四个重要的参数的函数
void creat_important_data()
{
	ve[stack[0]]=0;
	for(int i=1;i<stack_num;i++)
	{
		eedge* help=reve_table[stack[i]].next;
		int maxp=-1;
		if(help==NULL)
		{
			ve[stack[i]]=0;
			continue;
		}
		while(help!=NULL) 
		{
			int sum=ve[help->i]+help->weight;
			if(maxp<sum) maxp=sum;
			help=help->next;
		}
		ve[stack[i]]=maxp;
	}
	
	vl[stack[stack_num-1]]=ve[stack[stack_num-1]];
	for(int i=stack_num-2;i>=0;i--)
	{
		eedge* help=next_table[stack[i]].next;
		int minp=INF;
		while(help!=NULL)
		{
			int sum=vl[help->j]-help->weight;
			if(minp>sum) minp=sum;
			help=help->next;
		}
		vl[stack[i]]=minp;
	}

    for(int i=0;i<num_edge;i++) e[i]=ve[search[i].i];
    for(int i=0;i<num_edge;i++) 
    {
    	l[i]=vl[search[i].j]-search[i].weight;
    }
    
    memset(important_path,0,sizeof(important_path));
    memset(important_point,0,sizeof(important_point));
    for(int i=0;i<num_edge;i++)
    {
    	if(e[i]==l[i]) important_path[i]=1,important_point[search[i].j]=1;
    }
}

5.遗留问题:

1.如何找到缩短工期的CPM的松弛时间和松弛策略
2.如何自动根据输入的活动关系自动生成AOE网

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值