SortedUtil — 拓扑排序工具类 按父子依赖关系对列表进行稳定排序,保证子节点排在父节点之后,且保持同层兄弟节点的原始相对顺序。
快速开始 // 1. 定义你的数据结构,提供 id 和 parentId 的 getter List < YourEntity > list= . . . ; // 2. 调用排序 List < YourEntity > sorted= SortedUtil . topologicalSortByParent ( list, YourEntity :: getId , YourEntity :: getParentId ) ; 核心方法 public static < 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
排序规则 父在前,子在后 — DFS 前序遍历保证每个节点的所有子节点紧跟在它之后同层按原始顺序 — 同级兄弟节点按在列表中首次出现的先后顺序排列重复 id — 相同 id 的元素在组内保持原始相对顺序多根支持 — 存在多个根节点时,按根节点首次出现顺序依次处理孤立父节点 — 若某个节点的 parentId 不在列表中或为 null,该节点视为根算法原理 四步流程 输入列表 │ ▼ [1. 分组] ─── 按 id 分组,记录首次下标 │ ▼ [2. 建图] ─── 构建 parent → [children] 邻接表 │ ▼ [3. 找根] ─── parentId 不在列表中的节点作为根 │ ▼ [4. DFS] ─── 从根开始前序遍历,三色标记检测环 │ ▼ 输出结果三色标记法环检测 颜色 含义 说明 WHITE未访问 节点尚未被 DFS 遍历 GRAY访问中 正在遍历该节点(在递归栈上),若再次遇到说明有环 BLACK已结束 该节点及其所有子节点都已遍历完毕
环检测触发条件 (二选一):
遍历中 :DFS 访问子节点时遇到 GRAY 节点 → 环遍历后 :所有根节点处理完毕,仍有节点为 WHITE → 孤岛环(不可达)完整源码 package com. yourpackage ; import java. util. * ; import java. util. function. Function ; import java. util. stream. Collectors ; public class SortedUtil { /** * 拓扑排序:按父子依赖关系稳定排序,子节点在父节点之后 * * @param items 原始列表(可能有重复 id) * @param idGetter 提取元素 id 的函数 * @param parentGetter 提取元素 parentId 的函数 * @param <T> 元素类型 * @param <ID> id 类型 * @return 排序后的新列表 */ public static < T , ID> List < T > topologicalSortByParent ( List < T > items, Function < T , ID> idGetter, Function < T , ID> parentGetter) { if ( items== null || items. isEmpty ( ) ) return new ArrayList < > ( ) ; // 1. 按 id 分组(保持首次出现顺序) Map < ID, List < T > > groupById= new LinkedHashMap < > ( ) ; Map < ID, Integer > firstIndex= new LinkedHashMap < > ( ) ; for ( int i= 0 ; i< items. size ( ) ; i++ ) { T item= items. get ( i) ; ID id= idGetter. apply ( item) ; groupById. computeIfAbsent ( id, k-> new ArrayList < > ( ) ) . add ( item) ; firstIndex. putIfAbsent ( id, i) ; } Set < ID> allIds= groupById. keySet ( ) ; // 2. 构建 parent -> children 邻接表 Map < ID, Set < ID> > children= new HashMap < > ( ) ; for ( ID id: allIds) { children. put ( id, new LinkedHashSet < > ( ) ) ; } for ( T item: items) { ID parentId= 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-> { ID parentId= parentGetter. apply ( groupById. get ( id) . get ( 0 ) ) ; return parentId== null || ! allIds. contains ( parentId) ; } ) . sorted ( Comparator . comparingInt ( firstIndex:: get ) ) . collect ( Collectors . toList ( ) ) ; // 4. DFS 前序遍历(三色标记法检测环) Map < ID, Color > color= new HashMap < > ( ) ; for ( ID id: allIds) { color. put ( id, Color . WHITE ) ; } List < ID> sortedIds= new ArrayList < > ( ) ; for ( ID root: roots) { dfs ( root, children, color, sortedIds) ; } // 5. 环检测:仍为 WHITE 的节点形成环 for ( ID id: allIds) { if ( color. get ( id) == Color . WHITE ) { throw new IllegalStateException ( "检测到循环依赖,涉及节点: " + id) ; } } // 6. 按排序后的 id 展开组内元素(保持原始顺序) List < T > result= new ArrayList < > ( items. size ( ) ) ; for ( ID id: sortedIds) { result. addAll ( groupById. get ( id) ) ; } return result; } enum Color { WHITE , GRAY , BLACK } private static < ID> void dfs ( ID id, Map < ID, Set < ID> > children, Map < ID, Color > color, List < ID> sortedIds) { if ( color. get ( id) == Color . GRAY ) { throw new IllegalStateException ( "检测到循环依赖,涉及节点: " + id) ; } if ( color. get ( id) == Color . BLACK ) { return ; } color. put ( id, Color . GRAY ) ; sortedIds. add ( id) ; for ( ID childId: 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 ( new Org ( "总部" , null ) , new Org ( "研发部" , "总部" ) , new Org ( "前端组" , "研发部" ) , new Org ( "后端组" , "研发部" ) , new Org ( "市场部" , "总部" ) ) ; List < Org > sorted= SortedUtil . topologicalSortByParent ( orgs, Org :: getCode , Org :: getParentCode ) ; // 结果: 总部 → 研发部 → 前端组 → 后端组 → 市场部 示例 2:带重复数据的非法组织 List < Org > orgs= Arrays . asList ( new Org ( "总部" , null ) , new Org ( "研发部" , "总部" ) , new Org ( "研发部" , "总部" ) , // 重复 new Org ( "前端组" , "研发部" ) ) ; List < Org > sorted= SortedUtil . topologicalSortByParent ( orgs, Org :: getCode , Org :: getParentCode ) ; // 结果: 总部 → 研发部 → 研发部(重复) → 前端组