尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Java拓扑排序-解决树排序问题

Java拓扑排序-解决树排序问题
📅 发布时间:2026/7/7 5:06:32

SortedUtil — 拓扑排序工具类

按父子依赖关系对列表进行稳定排序,保证子节点排在父节点之后,且保持同层兄弟节点的原始相对顺序。


快速开始

// 1. 定义你的数据结构,提供 id 和 parentId 的 getterList<YourEntity>list=...;// 2. 调用排序List<YourEntity>sorted=SortedUtil.topologicalSortByParent(list,YourEntity::getId,YourEntity::getParentId);

核心方法

publicstatic<T,ID>List<T>topologicalSortByParent(List<T>items,Function<T,ID>idGetter,Function<T,ID>parentGetter)

参数

参数类型说明
itemsList<T>原始列表,允许包含重复 id 的元素,允许 null
idGetterFunction<T, ID>提取元素唯一标识的函数
parentGetterFunction<T, ID>提取元素父标识的函数

返回值

排序后的新ArrayList<T>,原始列表不会被修改。

异常

异常条件
IllegalStateException检测到循环依赖(含自环),消息中会列出涉及的节点 id

排序规则

  1. 父在前,子在后— DFS 前序遍历保证每个节点的所有子节点紧跟在它之后
  2. 同层按原始顺序— 同级兄弟节点按在列表中首次出现的先后顺序排列
  3. 重复 id— 相同 id 的元素在组内保持原始相对顺序
  4. 多根支持— 存在多个根节点时,按根节点首次出现顺序依次处理
  5. 孤立父节点— 若某个节点的 parentId 不在列表中或为 null,该节点视为根

算法原理

四步流程

输入列表 │ ▼ [1. 分组] ─── 按 id 分组,记录首次下标 │ ▼ [2. 建图] ─── 构建 parent → [children] 邻接表 │ ▼ [3. 找根] ─── parentId 不在列表中的节点作为根 │ ▼ [4. DFS] ─── 从根开始前序遍历,三色标记检测环 │ ▼ 输出结果

三色标记法环检测

颜色含义说明
WHITE未访问节点尚未被 DFS 遍历
GRAY访问中正在遍历该节点(在递归栈上),若再次遇到说明有环
BLACK已结束该节点及其所有子节点都已遍历完毕

环检测触发条件(二选一):

  • 遍历中:DFS 访问子节点时遇到 GRAY 节点 → 环
  • 遍历后:所有根节点处理完毕,仍有节点为 WHITE → 孤岛环(不可达)

完整源码

packagecom.yourpackage;importjava.util.*;importjava.util.function.Function;importjava.util.stream.Collectors;publicclassSortedUtil{/** * 拓扑排序:按父子依赖关系稳定排序,子节点在父节点之后 * * @param items 原始列表(可能有重复 id) * @param idGetter 提取元素 id 的函数 * @param parentGetter 提取元素 parentId 的函数 * @param <T> 元素类型 * @param <ID> id 类型 * @return 排序后的新列表 */publicstatic<T,ID>List<T>topologicalSortByParent(List<T>items,Function<T,ID>idGetter,Function<T,ID>parentGetter){if(items==null||items.isEmpty())returnnewArrayList<>();// 1. 按 id 分组(保持首次出现顺序)Map<ID,List<T>>groupById=newLinkedHashMap<>();Map<ID,Integer>firstIndex=newLinkedHashMap<>();for(inti=0;i<items.size();i++){Titem=items.get(i);IDid=idGetter.apply(item);groupById.computeIfAbsent(id,k->newArrayList<>()).add(item);firstIndex.putIfAbsent(id,i);}Set<ID>allIds=groupById.keySet();// 2. 构建 parent -> children 邻接表Map<ID,Set<ID>>children=newHashMap<>();for(IDid:allIds){children.put(id,newLinkedHashSet<>());}for(Titem:items){IDparentId=parentGetter.apply(item);if(parentId!=null&&allIds.contains(parentId)){children.get(parentId).add(idGetter.apply(item));}}// 3. 找根节点(parentId 不在 allIds 中或为 null)List<ID>roots=allIds.stream().filter(id->{IDparentId=parentGetter.apply(groupById.get(id).get(0));returnparentId==null||!allIds.contains(parentId);}).sorted(Comparator.comparingInt(firstIndex::get)).collect(Collectors.toList());// 4. DFS 前序遍历(三色标记法检测环)Map<ID,Color>color=newHashMap<>();for(IDid:allIds){color.put(id,Color.WHITE);}List<ID>sortedIds=newArrayList<>();for(IDroot:roots){dfs(root,children,color,sortedIds);}// 5. 环检测:仍为 WHITE 的节点形成环for(IDid:allIds){if(color.get(id)==Color.WHITE){thrownewIllegalStateException("检测到循环依赖,涉及节点: "+id);}}// 6. 按排序后的 id 展开组内元素(保持原始顺序)List<T>result=newArrayList<>(items.size());for(IDid:sortedIds){result.addAll(groupById.get(id));}returnresult;}enumColor{WHITE,GRAY,BLACK}privatestatic<ID>voiddfs(IDid,Map<ID,Set<ID>>children,Map<ID,Color>color,List<ID>sortedIds){if(color.get(id)==Color.GRAY){thrownewIllegalStateException("检测到循环依赖,涉及节点: "+id);}if(color.get(id)==Color.BLACK){return;}color.put(id,Color.GRAY);sortedIds.add(id);for(IDchildId:children.get(id)){dfs(childId,children,color,sortedIds);}color.put(id,Color.BLACK);}}

边界情况

场景行为
null输入返回空列表
空列表返回空列表
单节点直接返回
所有节点均为根保持原始顺序
子节点先于父节点出现父节点仍排在前面(按 id 分组后从根开始遍历)
父节点不在列表中该节点被当作根节点
重复 id根据 id 分组,组内保持原始顺序
循环依赖 / 自环抛IllegalStateException
DAG(非严格树,如菱形)能正确处理,已经访问的节点不会重复遍历

复杂度

指标值
时间复杂度O(V + E)
空间复杂度O(V)

其中 V 为唯一 id 数量,E 为父子关系边数。


使用示例

示例 1:机构树排序

List<Org>orgs=Arrays.asList(newOrg("总部",null),newOrg("研发部","总部"),newOrg("前端组","研发部"),newOrg("后端组","研发部"),newOrg("市场部","总部"));List<Org>sorted=SortedUtil.topologicalSortByParent(orgs,Org::getCode,Org::getParentCode);// 结果: 总部 → 研发部 → 前端组 → 后端组 → 市场部

示例 2:带重复数据的非法组织

List<Org>orgs=Arrays.asList(newOrg("总部",null),newOrg("研发部","总部"),newOrg("研发部","总部"),// 重复newOrg("前端组","研发部"));List<Org>sorted=SortedUtil.topologicalSortByParent(orgs,Org::getCode,Org::getParentCode);// 结果: 总部 → 研发部 → 研发部(重复) → 前端组

相关新闻

  • 从零实现一个容器运行时:Docker的核心设计
  • 城市美学视角:科技融于山水,叁仟智慧灯杆重塑杭州夜间城市风貌
  • 触觉引导模型选型指南:环境适配四维解构法

最新新闻

  • 解放双手的智能游戏助手:基于图像识别的鸣潮自动化系统深度解析
  • DApp合约审计多少钱?项目有必要做智能合约审计吗?
  • 2026国产CAD软件排行
  • Claude Code 中转站怎么选?从稳定性、缓存到成本的完整判断
  • 天幕传媒全品类活动物料定制,自主供应链与标准化品控体系
  • CNN网络层功能可视化:从CIFAR-10图像看卷积、池化层如何提取特征

日新闻

  • Android逆向分析全能助手:集成化工具链与自动化工作流设计
  • 面搜索(Faceted Search)原理与工程实践指南
  • 神经网络调参避坑指南:从5个常见Loss曲线形态定位超参数问题

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号