* @zh 使用 Kahn 算法进行拓扑排序 * @en Topological sort using Kahn's algorithm * * @zh Kahn 算法优势: * - 能够检测循环依赖 * - 返回所有循环中的节点 * - 时间复杂度 O(V + E)
(
items: T[],
resolveId: (id: string) => string
)
| 183 | * - 时间复杂度 O(V + E) |
| 184 | */ |
| 185 | function kahnSort<T extends IDependable>( |
| 186 | items: T[], |
| 187 | resolveId: (id: string) => string |
| 188 | ): TopologicalSortResult<T> { |
| 189 | const itemMap = new Map<string, T>(); |
| 190 | const graph = new Map<string, Set<string>>(); |
| 191 | const inDegree = new Map<string, number>(); |
| 192 | |
| 193 | // 构建节点映射 |
| 194 | for (const item of items) { |
| 195 | itemMap.set(item.id, item); |
| 196 | graph.set(item.id, new Set()); |
| 197 | inDegree.set(item.id, 0); |
| 198 | } |
| 199 | |
| 200 | // 构建边(依赖 -> 被依赖者) |
| 201 | for (const item of items) { |
| 202 | for (const dep of item.dependencies || []) { |
| 203 | const depId = resolveId(dep); |
| 204 | if (itemMap.has(depId)) { |
| 205 | graph.get(depId)!.add(item.id); |
| 206 | inDegree.set(item.id, (inDegree.get(item.id) || 0) + 1); |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | // 收集入度为 0 的节点 |
| 212 | const queue: string[] = []; |
| 213 | for (const [id, degree] of inDegree) { |
| 214 | if (degree === 0) { |
| 215 | queue.push(id); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | // BFS 处理 |
| 220 | const sorted: T[] = []; |
| 221 | while (queue.length > 0) { |
| 222 | const current = queue.shift()!; |
| 223 | sorted.push(itemMap.get(current)!); |
| 224 | |
| 225 | for (const neighbor of graph.get(current) || []) { |
| 226 | const newDegree = (inDegree.get(neighbor) || 0) - 1; |
| 227 | inDegree.set(neighbor, newDegree); |
| 228 | if (newDegree === 0) { |
| 229 | queue.push(neighbor); |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | // 检查循环依赖 |
| 235 | if (sorted.length !== items.length) { |
| 236 | const cycleIds = items |
| 237 | .filter(item => !sorted.includes(item)) |
| 238 | .map(item => item.id); |
| 239 | return { sorted, hasCycles: true, cycleIds }; |
| 240 | } |
| 241 | |
| 242 | return { sorted, hasCycles: false }; |