4.有向无环图(DAG)的应用
4.1有向无环图(DAG)描述表达式
有向无环图:若一个有向图中不存在环,则称为有向无环图,简称DAG图(Directed Acyclic Graph)。
对于使用树来表示算术表达式,会可能有重复的部分:
这棵树中,红色和绿色的子树是重复的,那么就要可以删去一个:
同理,还可以合并多处重复的数据:
【2019统考真题】用有向无环图描述表达式(x + y)((x + y)/ x),需要的顶点个数至少是()。
A. 5 B. 6 C. 8 D.9答案:A.5
✨4.1.1 解题步骤
4.2拓扑排序(AOV网)
AOV网(Activity on vertex Network,用顶点表示活动的网):
用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边
<
V
i
,
V
j
>
<V_i,V_j>
<Vi,Vj>表示活动Vi必须先于活动Vj进行(活动有先后顺序)。
- 顶点:活动
- 有向边:活动有先后顺序
4.2.1 定义
所谓拓扑排序,其实就是对一个有向图构造拓扑序列的过程。每个AOV网都有一个或多个拓扑排序序列。
4.2.2 算法
拓扑排序的实现步骤:
- 从AOV网中选择一个没有前驱(入度为0)的顶点并输出。
- 从网中删除该顶点和所有以它为起点的有向边。
- 重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。
【注意】
- **一定是无环图才有拓扑排序,有环则不行。**如果输出顶点数少了,哪怕是少了一个,也说明这个网存在环(回路),不是AOV网。
- 若一个顶点有多个直接后继,则拓扑排序的结果通常不唯一。
上图所示为拓扑排序过程的示例。每一轮选择一个入度为0的顶点并输出,然后删除该顶点和所有以它为起点的有向边,最后得到拓扑排序的结果为{1,2,4,3,5}。
拓扑排序算法的实现如下:
// 使用 邻接表 是实现
//indegree[] 是当前结点的入度
//用栈来保存当前度0的顶点,也可以用数组/队列保存
bool TopologicalSort(Graph G){
InitStack(S); //初始化栈,存储入度为0的顶点
for(int i=0; i<G.vexnum; i++){
if(indegree[i] == 0){
Push(S, i); //将所有入度为0的顶点进栈
}
}
int count = 0; //计数,记录当前已经输出的顶点数
while(!IsEmpty(S)){ //栈不空,则存在入度为0的顶点
Pop(S, i); //顶点元素出栈
printf("%d ", i); //输出顶点i
count++;
for(p=G.vertices[i].finstarc; p; p=p->nextarc){
//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
v = p->adjvex; //遍历结点的边
if(!--indegree[v]){
Push(S, v); //入度为0,则入栈
}
}
}
if(count < G.vexnum){
return false; //输出顶点少了,有向图中有回路,排序失败
}else{
return true; //拓扑排序成功
}
}
由于输出每个顶点的同时还要删除以它为起点的边,故拓扑排序的时间复杂度为O(|V|+|E|)。
如果采用邻接矩阵,那么时间复杂度为O(|V|2)。
此外,利用深度优先遍历(DFS)也可实现拓扑排序。
邻接表很好写
4.3逆拓扑排序
与拓扑排序基本相似,但是是倒叙的,所以选择的是出度为0的结点。
逆拓扑排序的实现步骤:
- 从AOV网中选择一个没有前驱(出度为0)的顶点并输出。
- 从网中删除该顶点和所有以它为起点的有向边。
- 重复①和②直到当前的AOV网为空或当前网中不存在无后继的顶点为止。
邻接表不好写,邻接矩阵还可以,逆邻接表好写