DAG
DAG(Directed Acycline Graph,有向无环图),是指一个无环的有向图。
DAG是描述一项工程或系统的进行过程的有效工具。通常把计划、施工过程、生产流程、程序流程等都当成一个工程。除了很小的工程外,一般的工程都可分为若干个称作活动的子工程,而这些子工程之间,通常受着一定条件的约束,如其中某些子工程的开始必须在另一些子工程完成之后。
典型例子:
大学生的课程学习过程就可以通过 DAG 建模,一般来说课程之间有依赖关系,必须学完某些课程才能开始学习其他课程 。
DAG有广泛的应用,一般来说,DAG可以对流程引擎的流程进行建模。
AOV网
用顶点表示活动,用弧表示活动间的优先关系,这种有向图称为AOV网。
在AOV网中,不应该出现环,若存在环意味着某项活动以自己为先决条件,这显然是不合理的。
如何检测有向图是否存在环
方法:拓扑排序。
拓扑排序
拓扑排序就是将一个有向图的所有顶点排成一个线性序列,这个序列满足:若在图中顶点i到顶点j有一条路径,则在该序列中顶点i一定在顶点j之前。
也就是说,在拓扑序列中,图中任意一个顶点的所有"前序顶点"的拓扑序列位置都在该顶点的拓扑序列位置之前。
前序顶点是指:有向图中,一个顶点所在路径上,所有在它之前的顶点都是它的前序顶点。
拓扑排序的过程:
-
在有向图中选一个无前驱(即入度为0) 的顶点,并输出它
-
从图中删除该顶点,以及以该顶点为弧尾的所有弧
-
重复1和2的操作,直至没有无前驱的顶点
-
若输出的顶点数小于有向图的顶点数,则说明有向图存在环;否则,说明不存在环,且输出的顶点序列即为一个拓扑序列。
拓扑排序的实现
对如下DAG进行拓扑排序:
说明:采用邻接表来存储图。
为实现拓扑排序,需要以下数据结构辅助:
-
入度表:存储各顶点的入度
-
队列:暂存入度为0的顶点。
- 入队操作:当顶点入度为0,则入队
- 出队操作:出队顶点的所有出顶点的入度都减1(即模拟从图中删除出队顶点和其所有出边),并将新的入度为0的顶点入队。
// 顶点集
var allNodes []string = []string{"V1", "V2", "V3", "V4", "V5", "V6"}
// 邻接表存储有向图,本质上是一个二维结构
var dag map[string][]string = map[string][]string{
"V1": {"V2", "V3", "V4"},
"V3": {"V2", "V5"},
"V4": {"V5"},
"V6": {"V4", "V5"},
}
func main() {
// 入度表
indegreeMap := make(map[string]int)
// 初始化入度表
for _, node := range allNodes {
indegreeMap[node] = 0
}
// 遍历邻接表求各顶点入度
for _, nodes := range dag {
for _, n := range nodes {
indegreeMap[n] = indegreeMap[n] + 1
}
}
res := topologicalSort(indegreeMap)
fmt.Println(len(res) == len(allNodes))
fmt.Println(res)
}
func topologicalSort(indegreeMap map[string]int) []string {
// 队列
queue := make([]string, 0)
// 队头指针,队尾就是len(queue)-1
headIndex := 0
// 初始化队列,加入入度为0的顶点
for node, indegree := range indegreeMap {
if indegree == 0 {
queue = append(queue, node)
}
}
result := make([]string, 0)
// 不断出队入度为0的顶点
for {
if headIndex >= len(queue) {
break
}
// 出队
curNode := queue[headIndex]
headIndex++
result = append(result, curNode)
// 删除出队顶点及所有出边,则出队顶点的所有出顶点入度都减1
for _, node := range dag[curNode] {
indegree := indegreeMap[node]
indegreeMap[node] = indegree - 1
if indegree-1 == 0 {
queue = append(queue, node)
}
}
}
return result
}
总结
拓扑排序可以让我们按顶点之间的约束关系、依赖关系顺序遍历图中的所有顶点。
应用场景
工作中遇到要执行一批任务且任务之间有约束关系的(如,任务A必须先执行完才能执行任务B)(能构成AOV网的) ,就可以构造DAG,然后采用拓扑排序按依赖顺序执行任务。