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;
}
相关推荐
Black蜡笔小新6 小时前
自动化AI算法训练服务器DLTM助力医学影像分析进入AI智能分析新时代
人工智能·算法·自动化
手写码匠6 小时前
深入解析大模型架构之争:全能通用模型 vs 领域专精模型
人工智能·深度学习·算法·aigc
浅念-7 小时前
LeetCode 回溯算法题——综合练习
数据结构·c++·算法·leetcode·职场和发展·深度优先·dfs
列星随旋7 小时前
线段树和树状数组的学习
学习·算法
解道Jdon9 小时前
[Budi插件:VsCode状态栏显示Copilot使用情况
ide·windows·git·svn·eclipse·github·visual studio
全糖可乐气泡水9 小时前
Codex适配国产信创环境安装部署与技术适配全解析
开发语言·git·python·算法·百度
h_a_o777oah10 小时前
状态机+划分型 DP :深度解析K-划分问题下 DP 状态的转移逻辑(洛谷P2679 P2331 附C++代码)
c++·算法·动态规划·acm·状态机dp·划分型dp·滚动数组优化
一个人旅程~10 小时前
如何避免在使用win安装U盘启动macbook时候出现键盘触摸板卡死的问题
windows·经验分享·macos·电脑
05候补工程师10 小时前
从算法理想向工程现实的跨越:SLAM 核心架构、思维误区与 Nav2 实战避坑指南
人工智能·算法·安全·架构·机器人