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;
}
相关推荐
菜鸟233号9 分钟前
力扣416 分割等和子串 java实现
java·数据结构·算法·leetcode
YJlio10 分钟前
Registry Usage (RU) 学习笔记(15.5):注册表内存占用体检与 Hive 体量分析
服务器·windows·笔记·python·学习·tcp/ip·django
Swift社区14 分钟前
LeetCode 469 凸多边形
算法·leetcode·职场和发展
chilavert31817 分钟前
技术演进中的开发沉思-298 计算机原理:算法的本质
算法·计算机原理
Aaron158823 分钟前
全频段SDR干扰源模块设计
人工智能·嵌入式硬件·算法·fpga开发·硬件架构·信息与通信·基带工程
求梦82028 分钟前
【力扣hot100题】缺失的第一个正数(12)
数据结构·算法·leetcode
散峰而望44 分钟前
【算法竞赛】顺序表和vector
c语言·开发语言·数据结构·c++·人工智能·算法·github
千金裘换酒44 分钟前
LeetCode 回文链表
算法·leetcode·链表
CSDN_RTKLIB1 小时前
【std::map】与std::unordered_map差异
算法·stl·哈希算法
FL171713141 小时前
Geometric Control
人工智能·算法