在做一些较为底层的开发工作时,基本都会使用到流程编排,即将相关业务作为节点,组成有效无环图(DAG
)。
拓扑排序是对有向无环图(DAG
)的顶点进行排序,使得对于图中的每条有向边 (u, v
),顶点u
在排序结果中都出现在顶点v
之前。同时,通过拓扑排序可以校验DAG
中是否存在环。
以下是一个使用Go
语言实现的拓扑排序算法来判断带有父节点、子节点的有向无环图(DAG
)中是否有环的示例代码:
package main
import (
"fmt"
)
// Node 结构体表示图中的节点
type Node struct {
ID string
// 有时候还会定义一个bool类型的Root字段,标记当前节点是否是根节点
Root bool
Children []*Node
Parents []*Node
}
// Graph 结构体表示整个图
type Graph struct {
Nodes map[string]*Node
}
// NewGraph 创建一个新的图实例
func NewGraph() *Graph {
return &Graph{
Nodes: make(map[string]*Node),
}
}
// AddNode 向图中添加一个节点
func (g *Graph) AddNode(id string) {
if _, ok := g.Nodes[id];!ok {
g.Nodes[id] = &Node{
ID: id,
Children: []*Node{},
Parents: []*Node{},
}
}
}
// AddEdge 添加一条有向边,连接from节点到to节点
func (g *Graph) AddEdge(from, to string) {
fromNode, ok := g.Nodes[from]
if!ok {
fmt.Printf("节点 %s 不存在于图中\n", from)
return
}
toNode, ok := g.Nodes[to]
if!ok {
fmt.Printf("节点 %s 不存在于图中\n", to)
return
}
fromNode.Children = append(fromNode.Children, toNode)
toNode.Parents = append(toNode.Parents, fromNode)
}
// TopologicalSort 执行拓扑排序并判断是否有环,返回排序后的节点列表和是否有环的标志
func (g *Graph) TopologicalSort() ([]*Node, bool) {
inDegree := make(map[*Node]int)
for _, node := range g.Nodes {
inDegree[node] = len(node.Parents)
}
var queue []*Node
// 将入度为0的节点加入队列
for node, degree := range inDegree {
if degree == 0 {
queue = append(queue, node)
}
}
var sortedNodes []*Node
hasCycle := false
// 进行拓扑排序并判断是否有环
// 类似树的层序遍历
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
sortedNodes = append(sortedNodes, node)
// 更新相邻节点的入度,并将入度变为0的节点加入队列
for _, child := range node.Children {
inDegree[child]--
if inDegree[child] == 0 {
queue = append(queue, child)
}
}
}
// 如果排序后的节点数量不等于图中的节点数量,说明图中有环
if len(sortedNodes)!= len(g.Nodes) {
hasCycle = true
}
return sortedNodes, hasCycle
}
在上述代码中:
Node
结构体用于表示图中的一个节点,包含节点的唯一标识ID
,指向子节点的指针数组Children
以及指向父节点的指针数组Parents
。Graph
结构体用于表示整个图,通过一个字典Nodes
来存储图中的所有节点。AddNode
方法用于向图中添加一个新的节点。AddEdge
方法用于在图中添加一条有向边,连接指定的两个节点,并更新相关节点的Children
和Parents
数组。TopologicalSort
方法首先计算每个节点的入度(即指向该节点的父节点数量),然后将入度为0
的节点放入队列。接着通过不断从队列中取出节点,更新相邻节点的入度,并将入度变为0
的节点再次加入队列,同时判断是否能将所有节点都排序完成。如果排序后的节点数量不等于图中的节点数量,就说明图中有环,否则说明图中不存在环。最后返回排序后的节点列表以及是否有环的判断结果。