0.参考资料

尊重他人的劳动成果,贴上参考的资料地址,本文仅作学习记录之用。

  1. https://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp
  2. https://songlee24.github.io/2015/05/07/topological-sorting/
  3. https://www.cnblogs.com/skywang12345/p/3711483.html

1.介绍

自己之前并没有接触过拓扑排序,顶多听说过拓扑图。在写前一篇文章的时候,看到 Abp 框架在处理模块依赖项的时候使用了拓扑排序,来确保顶级节点始终是最先进行加载的。第一次看到觉得很神奇,看了一下维基百科头也是略微大,自己的水平也是停留在冒泡排序的层次。ヽ(≧□≦)ノ

看了第二篇参考资料才大致了解,在此记录一下。

2.原理

先来一个基本定义:

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

  1. 每个顶点出现且只出现一次。
  2. 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

例如,有一个集合它的依赖关系如下图:

可以看到他有一个依赖关系:

  1. Module D 依赖于 Module E 与 Module B 。
  2. Module E 依赖于 Module B 与 Module C 。
  3. Module B 依赖于 Module A 与 Module C 。
  4. Module C 依赖于 Module A 。
  5. Module A 无依赖 。

这个就是一个 DAG 图,我们要得到它的拓扑排序,一个简单的步骤如下:

  1. 从 DAG 图中选择一个没有前驱的顶点并输出。
  2. 从 DAG 图中删除该顶点,以及以它为起点的有向边。
  3. 重复步骤 1、2 直到当前的 DAG 图为空,或者当前图不存在无前驱的顶点为止

按照以上步骤,我们来进行一个排序试试。

最后的排序结果就是:

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 是平级的,他们初始化顺序即便不同也没有什么影响,因为他们的依赖层级是一致的,不过细心的朋友可能会发现这个顺序好像是反的,我们还需要将其再反转一次。

3.实现

上面这种方法仅适用于已知入度的时候,也就是说这些内容本身就是存在于一个有向无环图之中的,如果按照以上方法进行拓扑排序,你需要维护一个入度为 0 的队列,然后每次迭代移除入度为 0 顶点所指向的顶点入度。

例如有以下图:

按照我们之前的算法,

  1. 首先初始化队列,将 5 与 4 这两个入度为 0 的顶点加入队列当中。
  2. 执行 While 循环,条件是队列不为空。
  3. 之后首先拿出 4 。
  4. 然后针对其指向的顶点 0 与 顶点 1 的入度减去 1。
  5. 减去指向顶点入度的时候同时判断,被减去入度的顶点其值是否为 0 。
  6. 这里 1 入度被减去 1 ,为 0 ,添加到队列。
  7. 0 顶点入度减去 1 ,为 1。
  8. 队列现在有 5 与 1 这两个顶点,循环判断队列不为空。
  9. 5 指向的顶点 0 入度 减去 1,顶点 0 入度为 0 ,插入队列。

这样反复循环,最终队列全部清空,退出循环,得到拓扑排序的结果4, 5, 2, 0, 3, 1 。

4.深度优先搜索实现

在参考资料 1 的代码当中使用的是深度优先算法,它适用于有向无环图。

有以下有向环图 G2:

对上图 G2 进行深度优先遍历,首先从入度为 0 的顶点 A 开始遍历:

它的步骤如下:

  1. 访问 A 。

  2. 访问 B 。

  3. 访问 C 。

    在访问了 B 后应该是访问 B 的另外一个顶点,这里可以是随机的也可以是有序的,具体取决于你存储的序列顺序,这里先访问 C 。

  4. 访问 E 。

  5. 访问 D 。

    这里访问 D 是因为 B 已经被访问过了,所以访问顶点 D 。

  6. 访问 F 。

    因为顶点 C 已经被访问过,所以应该回溯访问顶点 B 的另一个有向边指向的顶点 F 。

  7. 访问 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();
}

结果:

使用 C# 代码实现拓扑排序的更多相关文章

  1. [C#]使用 C# 代码实现拓扑排序 dotNet Core WEB程序使用 Nginx反向代理 C#里面获得应用程序的当前路径 关于Nginx设置端口号,在Asp.net 获取不到的,解决办法 .Net程序员 初学Ubuntu ,配置Nignix 夜深了,写了个JQuery的省市区三级级联效果

    [C#]使用 C# 代码实现拓扑排序   目录 0.参考资料 1.介绍 2.原理 3.实现 4.深度优先搜索实现 回到顶部 0.参考资料 尊重他人的劳动成果,贴上参考的资料地址,本文仅作学习记录之用. ...

  2. *HDU1285 拓扑排序

    确定比赛名次 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Subm ...

  3. 拓扑排序(三)之 Java详解

    前面分别介绍了拓扑排序的C和C++实现,本文通过Java实现拓扑排序. 目录 1. 拓扑排序介绍 2. 拓扑排序的算法图解 3. 拓扑排序的代码说明 4. 拓扑排序的完整源码和测试程序 转载请注明出处 ...

  4. 拓扑排序(二)之 C++详解

    本章是通过C++实现拓扑排序. 目录 1. 拓扑排序介绍 2. 拓扑排序的算法图解 3. 拓扑排序的代码说明 4. 拓扑排序的完整源码和测试程序 转载请注明出处:http://www.cnblogs. ...

  5. 拓扑排序(一)之 C语言详解

    本章介绍图的拓扑排序.和以往一样,本文会先对拓扑排序的理论知识进行介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现. 目录 1. 拓扑排序介绍 2. 拓扑排序的算法图解 3. 拓扑 ...

  6. 算法与数据结构(七) AOV网的拓扑排序

    今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...

  7. 【BZOJ-2938】病毒 Trie图 + 拓扑排序

    2938: [Poi2000]病毒 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 609  Solved: 318[Submit][Status][Di ...

  8. BZOJ1565 [NOI2009]植物大战僵尸(拓扑排序 + 最大权闭合子图)

    题目 Source http://www.lydsy.com/JudgeOnline/problem.php?id=1565 Description Input Output 仅包含一个整数,表示可以 ...

  9. 拓扑排序&&欧拉(回)路

    摘要:最近是不适合写代码么?忘记初始化wa到死<_=_=_>.唔--最近在学习图论,从基础搞起,先搞了拓扑排序和欧拉(回)路. Part 0. 拓扑排序 ==挖坑== Part 1. 欧拉 ...

随机推荐

  1. 【ES6】Generator+Promise异步编程

    一.概念 首先我们要理解Generator和Promise的概念. Generator:意思是生成器,可以在函数内部通过yeild来控制语句的执行或暂停状态. *Foo(){ yeild consol ...

  2. HeadFirst学习笔记-1. 设计模式入门

    1.概念 在开始学习前,我们先了解一些概念,方便我们接下来的学习. OO基础 抽象 继承 多态 封装 OO原则 封装变化 多用组合,少用继承 针对接口编程,不针对实现编程 设计模式 设计模式(Desi ...

  3. Jmeter中基本操作

    Jmeter中基本操作包括 1:线程组 2:HTTP信息头管理器 3:HTTP请求默认值 4:HTTP请求 5:查看结果树 操作步骤如下: 1.创建一个线程组 通俗的讲一个线程组,,可以看做一个虚拟用 ...

  4. python中用xlsxwriter创建图表

    缺点:xlsxwriter不能对已存在的Excel进行编辑插入图标   生成图标需要: 1.先准备数据 2.将数据插入到excel中 3.根据插入的数据生成图表 这里的生成excel主要分为准备多维数 ...

  5. 怎么给easyui中的datagrid加水平滚动条

    注意如下几个点就行: 1.数据网格(DataGrid)所在的table属性上级div无需设置width: 2..datagrid属性:fitColumns为false 或者不填 3.在style中给. ...

  6. APP产品设计及运营时常见的问题

    目录 一.APP设计之初必须预埋的功能 二.H5活动页入口设计 三.全套icon /menu icon / logo图,统一后台设置便于活动推广 四.webview与原生页面的路由应统一改善用户体验 ...

  7. [转]webpack中require和import的区别

    webpack中可以写commonjs格式的require同步语法,可以写AMD格式的require回调语法,还有一个require.ensure,以及webpack自己定义的require.incl ...

  8. look back to 2018

    只写展望怎么行,还是缺一篇总结.2018年几乎没有怎么发朋友圈,需要一些文字记录一下这一年发生的事. 去年的现在,2018年的开端,结束了研一上学期充实的生活,下学期一项艰巨的任务就是完成大项目,一个 ...

  9. Spiring系列__03IOC补充

    这篇文章是对前一篇的一些补充: 1.SpringIOC容器可以管理Bean的声明周期: 通过构造器或工厂方法创建bean的实例: 为bean属性设置值或者引入其他bean: 调用bean的初始化方法, ...

  10. 关于H5在微信浏览器内自动转格式,导致不能正常打开的问题

    从最近开始,微信朋友圈打开我们的H5页面有时会出现了“此网页已被QQ浏览器重新排版”的提示,直接结果就是导致H5网页或H5小游戏不能正常显示了!!这个问题什么严重,不知道腾讯又是怎么考虑的,难道真的是 ...