workflow 拓扑排序算法

1. WorkflowSubTaskEntity定义

必须要包含workflowId、taskName、dependsOn字段

复制代码
public class WorkflowSubTaskEntity extends BaseEntity implements Serializable {

    @JsonProperty("workflowId")
    private String workflowId;

    /**
     * 任务ID,用于日志、追踪、重试等
     */
    @JsonProperty("taskName")
    private String taskName;

    /**
     * 任务类型,由子类隐式指定(Jackson 自动处理)
     */
    @JsonProperty("taskType")
    private TaskType taskType;

    /**
     * 描述信息
     */
    @JsonProperty("description")
    private String description;

    /**
     * 依赖
     */
    @JsonProperty("dependsOn")
    private List<String> dependsOn;

    @JsonProperty("config")
    private Map<String, Object> config;
}

2. 拓扑排序核心思想(Kahn 算法变种)

我们使用 入度(in-degree)为 0 的节点逐层剥离法(Kahn 算法的扩展):

步骤概览:

  1. 计算每个任务的 入度(有多少前置任务)
  2. 找出所有 入度为 0 的任务 → 它们组成 第 0 层
  3. 移除这些任务,并更新它们的后继任务的入度
  4. 重复步骤 2--3,得到第 1 层、第 2 层......直到没有任务剩下

逐层详解(以具体例子说明)

假设任务依赖如下(用 → 表示依赖):

任务列表:

  • Start
  • Validate
  • FetchData
  • EnrichData
  • Process
  • End
    依赖关系(前置 → 后置):
  • Start → Validate
  • Validate → FetchData
  • Validate → EnrichData
  • FetchData → Process
  • EnrichData → Process
  • Process → End

🔹 第 0 层:初始化

  1. 计算每个任务的入度(in-degree):
    This content is only supported in a Feishu Docs
  2. 入度为 0 的任务:只有 Start
    → Level 0 = [Start]

🔹 第 1 层:移除 Level 0,更新入度

  • 移除 Start
  • Start 的后继是 Validate → Validate 入度减 1(从 1 → 0)
    更新后入度:
    This content is only supported in a Feishu Docs
  • 入度为 0 的任务:Validate
    → Level 1 = [Validate]

🔹 第 2 层:移除 Level 1,更新入度

  • 移除 Validate
  • 它的后继:FetchData 和 EnrichData
    • FetchData 入度:1 → 0 ✅
    • EnrichData 入度:1 → 0 ✅
      更新后入度:
      This content is only supported in a Feishu Docs
  • 入度为 0 的任务:FetchData, EnrichData
    → Level 2 = [FetchData, EnrichData] ← 可并行!

🔹 第 3 层:移除 Level 2,更新入度

  • 移除 FetchData 和 EnrichData
  • 它们的共同后继:Process
    • Process 原入度 2,减 2 → 0 ✅
      更新后入度:
      This content is only supported in a Feishu Docs
  • 入度为 0 的任务:Process
    → Level 3 = [Process]

🔹 第 4 层:移除 Level 3

  • 移除 Process
  • 后继:End → 入度 1 → 0 ✅
    → Level 4 = [End]

✅ 最终分层结果:

java

编辑

List<List> levels = [

"Start"\], \["Validate"\], \["FetchData", "EnrichData"\], // 并行执行 \["Process"\], \["End"

]

环判断

原理:如果图中存在环(cycle),拓扑排序算法就无法处理所有节点,最终输出的节点总数会少于输入的节点总数。

假设有一个环:A → B → C → A

  • 初始入度:
    • A: 1(来自 C)
    • B: 1(来自 A)
    • C: 1(来自 B)
  • 没有任何节点入度为 0 → 队列一开始就是空的
  • 算法直接结束,levels 为空或只包含环外节点
  • 最终 levels 中的节点数 < 总节点数

3. topologicalSort算法实现

复制代码
public static List<List<WorkflowSubTaskEntity>> topologicalSort(List<WorkflowSubTaskEntity> workflowSubTaskEntities) {
    if (workflowSubTaskEntities == null || workflowSubTaskEntities.isEmpty()) {
        return Collections.emptyList();
    }

    // 构建 taskId -> Task 映射
    Map<String, WorkflowSubTaskEntity> taskMap = new HashMap<>();
    for (WorkflowSubTaskEntity t : workflowSubTaskEntities) {
        if (taskMap.containsKey(t.getTaskName())) {
            throw new IllegalArgumentException("Duplicate task ID: " + t.getTaskName());
        }
        taskMap.put(t.getTaskName(), t);
    }

    // 构建入度表和邻接表
    Map<String, Integer> inDegree = new HashMap<>();
    Map<String, List<String>> graph = new HashMap<>();

    for (String id : taskMap.keySet()) {
        inDegree.put(id, 0);
        graph.put(id, new ArrayList<>());
    }

    // 填充依赖关系
    for (WorkflowSubTaskEntity workflowSubTaskEntity : workflowSubTaskEntities) {
        String currentId = workflowSubTaskEntity.getTaskName();
        List<String> deps = workflowSubTaskEntity.getDependsOn();
        if (deps != null) {
            for (String dep : deps) {
                if (!taskMap.containsKey(dep)) {
                    throw new IllegalArgumentException("Dependency not found: " + dep + " for task " + currentId);
                }
                graph.get(dep).add(currentId); // dep → current
                inDegree.put(currentId, inDegree.get(currentId) + 1);
            }
        }
    }

    // Kahn 算法拓扑排序
    Queue<String> queue = new LinkedList<>();
    for (Map.Entry<String, Integer> entry : inDegree.entrySet()) {
        if (entry.getValue() == 0) {
            queue.offer(entry.getKey());
        }
    }

    List<List<WorkflowSubTaskEntity>> levels = new ArrayList<>();
    while (!queue.isEmpty()) {
        int size = queue.size();
        List<WorkflowSubTaskEntity> currentLevel = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            String id = queue.poll();
            currentLevel.add(taskMap.get(id));

            // 减少后继节点的入度
            for (String neighbor : graph.get(id)) {
                inDegree.put(neighbor, inDegree.get(neighbor) - 1);
                if (inDegree.get(neighbor) == 0) {
                    queue.offer(neighbor);
                }
            }
        }
        levels.add(currentLevel);
    }

    // 检查环
    if (levels.stream().mapToInt(List::size).sum() != workflowSubTaskEntities.size()) {
        throw new IllegalArgumentException("Cycle detected in workflow DAG");
    }

    return levels;
}
相关推荐
好学且牛逼的马21 小时前
【Hot100|15-LeetCode 238. 除自身以外数组的乘积】
数据结构·算法·leetcode
Tisfy1 天前
LeetCode 3651.带传送的最小路径成本:动态规划
算法·leetcode·动态规划·题解·排序
努力学习的小廉1 天前
我爱学算法之—— 递归回溯综合(一)
算法·深度优先
m0_736919101 天前
C++中的策略模式实战
开发语言·c++·算法
孞㐑¥1 天前
算法—位运算
c++·经验分享·笔记·算法
软件算法开发1 天前
基于卷尾猴优化的LSTM深度学习网络模型(CSA-LSTM)的一维时间序列预测算法matlab仿真
深度学习·算法·matlab·lstm·一维时间序列预测·卷尾猴优化·csa-lstm
小白郭莫搞科技1 天前
鸿蒙跨端框架Flutter学习:ListView卡片样式详解
linux·服务器·windows
高洁011 天前
知识图谱如何在制造业实际落地应用
深度学习·算法·机器学习·数据挖掘·知识图谱
BHXDML1 天前
数据结构:(二)逻辑之门——栈与队列
java·数据结构·算法
晚风吹长发1 天前
初步了解Linux中的信号捕捉
linux·运维·服务器·c++·算法·进程·x信号