尊重他人的劳动成果,贴上参考的资料地址,本文仅做学习记录之用。html
本身以前并无接触过拓扑排序,顶多据说过拓扑图。在写前一篇文章的时候,看到 Abp 框架在处理模块依赖项的时候使用了拓扑排序,来确保顶级节点始终是最早进行加载的。第一次看到以为很神奇,看了一下维基百科头也是略微大,本身的水平也是停留在冒泡排序的层次。ヽ(≧□≦)ノgit
看了第二篇参考资料才大体了解,在此记录一下。github
先来一个基本定义:算法
在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的全部顶点的线性序列。且该序列必须知足下面两个条件:框架
- 每一个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出如今顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。ide
例如,有一个集合它的依赖关系以下图:学习
能够看到他有一个依赖关系:3d
这个就是一个 DAG 图,咱们要获得它的拓扑排序,一个简单的步骤以下:code
按照以上步骤,咱们来进行一个排序试试。htm
最后的排序结果就是:
Module D -> Module E -> Module B -> Module C -> Module A
emmmm,其实一个有向无环图能够有一个或者多个拓扑序列的,由于有的时候会存在一种状况,即如下这种状况:
这个时候你就可能会有这两种结果
D->E->B->C->F->A
D->E->B->F->C->A
由于 F 与 C 是平级的,他们初始化顺序即使不一样也没有什么影响,由于他们的依赖层级是一致的,不过细心的朋友可能会发现这个顺序好像是反的,咱们还须要将其再反转一次。
上面这种方法仅适用于已知入度的时候,也就是说这些内容自己就是存在于一个有向无环图之中的,若是按照以上方法进行拓扑排序,你须要维护一个入度为 0 的队列,而后每次迭代移除入度为 0 顶点所指向的顶点入度。
例若有如下图:
按照咱们以前的算法,
这样反复循环,最终队列所有清空,退出循环,获得拓扑排序的结果4, 5, 2, 0, 3, 1 。
在参考资料 1 的代码当中使用的是深度优先算法,它适用于有向无环图。
有如下有向环图 G2:
对上图 G2 进行深度优先遍历,首先从入度为 0 的顶点 A 开始遍历:
它的步骤以下:
访问 A 。
访问 B 。
访问 C 。
在访问了 B 后应该是访问 B 的另一个顶点,这里能够是随机的也能够是有序的,具体取决于你存储的序列顺序,这里先访问 C 。
访问 E 。
访问 D 。
这里访问 D 是由于 B 已经被访问过了,因此访问顶点 D 。
访问 F 。
由于顶点 C 已经被访问过,因此应该回溯访问顶点 B 的另外一个有向边指向的顶点 F 。
访问 G 。
所以最后的访问顺序就是 A -> B -> C -> E -> D -> F -> G ,注意顺序仍是不太对哦。
看起来跟以前的方法差很少,实现当中,其 Sort()
方法内部包含一个 visited 字典,用于标记已经访问过的顶点,sorted 则是已经排序完成的集合列表。
在字典里 Key 是顶点的值,其 value 值用来标识是否已经访问完全部路径,为 true
则表示正在处理该顶点,为 false
则表示已经处理完成。
如今咱们来写实现吧:
public static IList<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies) { var sorted = new List<T>(); var visited = new Dictionary<T, bool>(); foreach (var item in source) { Visit(item, getDependencies, sorted, visited); } return sorted; } public static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited) { bool inProcess; var alreadyVisited = visited.TryGetValue(item, out inProcess); // 若是已经访问该顶点,则直接返回 if (alreadyVisited) { // 若是处理的为当前节点,则说明存在循环引用 if (inProcess) { throw new ArgumentException("Cyclic dependency found."); } } else { // 正在处理当前顶点 visited[item] = true; // 得到全部依赖项 var dependencies = getDependencies(item); // 若是依赖项集合不为空,遍历访问其依赖节点 if (dependencies != null) { foreach (var dependency in dependencies) { // 递归遍历访问 Visit(dependency, getDependencies, sorted, visited); } } // 处理完成置为 false visited[item] = false; sorted.Add(item); } }
顶点定义:
// Item 定义 public class Item { // 条目名称 public string Name { get; private set; } // 依赖项 public Item[] Dependencies { get; set; } public Item(string name, params Item[] dependencies) { Name = name; Dependencies = dependencies; } public override string ToString() { return Name; } }
调用:
static void Main(string[] args) { var moduleA = new Item("Module A"); var moduleC = new Item("Module C", moduleA); var moduleB = new Item("Module B", moduleC); var moduleE = new Item("Module E", moduleB); var moduleD = new Item("Module D", moduleE); var unsorted = new[] { moduleE, moduleA, moduleD, moduleB, moduleC }; var sorted = Sort(unsorted, x => x.Dependencies); foreach (var item in sorted) { Console.WriteLine(item.Name); } Console.ReadLine(); }
结果: