拓扑排序算法的一个应用

在Visual Studio .NET中,一个解决方案可以包含多个项目,一个项目可以引用若干其它项目。编译的时候,VS会自动确定每个项目的编译顺序。VS究竟是如何计算出这个顺序的呢?

如果学习过数据结构,可以很容易回答出这个问题:拓扑排序(Topological Sort)。

 

什么是拓扑排序?让我们来温习一下。百度百科上的介绍如下:
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前

 

上述介绍抽象,不如用实际案例来解释一下。假如在VS中创建一个MVC的解决方案XMedia,该解决方案包含的项目,以及项目之间的引用关系如下表所示:   

项目

引用

XMedia

XMedia.Controllers、XMedia.Models、XMedia.Logics、XMedia.Commons

XMedia.Controllers

XMedia.Models、XMedia.Logics、XMedia.Commons

XMedia.Models

 

XMedia.Logics

XMedia.Models、XMedia.Commons

XMedia.Commons

 

 

 

 

 

 

 

 

 

 

 

项目之间的引用关系,是一种依赖关系。如果项目A引用项目B,则表示A依赖B。所以,必须先编译项目B,再编译项目A。

根据经验,我们可以得出上述项目的编译顺序依次是:XMedia.Commons、XMedia.Models、XMedia.Logics、XMedia.Controllers、XMedia。当然,也可以把前两项对调一下。

项目和引用关系构成了一张有向图图,项目相当于有向图中的顶点(Vertex),引用关系相当于有向图中的边(Edge),而项目的编译顺序就是一个拓扑序列,产生该序列的算法称为拓扑排序算法。

以下是项目引用关系的有向图展示:

拓扑排序算法的一个应用

拓扑排序算法的简要描述:
(1) 从有向图中选择一个出度为0的顶点并且输出它。
(2) 从图中删去该顶点,并且删去该顶点的所有边。
(3) 重复上述两步,直到剩余的图中没有出度为0的顶点。

按照上述算法,运行过程演示如下:

第一步 选择 XMedia.Commons节点

拓扑排序算法的一个应用


第二步 选择XMedia.Models节点

拓扑排序算法的一个应用


第三步 选择XMedia.Logics节点

拓扑排序算法的一个应用


第四步 选择XMedia.Controllers节点

拓扑排序算法的一个应用


第五步 选择XMedia节点

 

接下来我们用C#实现代码实现这个算法。

由于拓扑排序是一个应用很多的算法,所以,我们将实现一个通用的排序算法。在这个通用的算法中,我们将顶点之间的关系作为依赖关系。代码如下:

using System;

using System.Collections.Generic;

using System.Linq;



namespace ConsoleApplication1 {

    /// <summary>

    /// 拓扑排序类。

    /// </summary>

    public class TopologicSort {

        /// <summary>

        /// 拓扑顺序。

        /// </summary>

        /// <typeparam name="TKey">节点的键值类型。</typeparam>

        /// <param name="nodes">一组节点。</param>

        /// <returns>拓扑序列。</returns>

        /// <exception cref="InvalidOperationException">如果存在双向引用或循环引用,则抛出该异常。</exception>

        public IEnumerable<string> OrderBy(IEnumerable<TopologicNode> nodes) {

            if (nodes == null) yield break;



            //复制一份,便于操作

            List<TopologicNode> list = new List<TopologicNode>();

            foreach (var item in nodes) {

                TopologicNode node = new TopologicNode() { Key = item.Key };

                if (item.Dependences != null)

                    node.Dependences = new List<string>(item.Dependences);

                list.Add(node);

            }



            while (list.Count > 0) {

                //查找依赖项为空的节点

                var item = list.FirstOrDefault(c => c.Dependences == null || c.Dependences.Count == 0);

                if (item != null) {

                    yield return item.Key;



                    //移除用过的节点,以及与其相关的依赖关系

                    list.Remove(item);

                    foreach (var otherNode in list) {

                        if (otherNode.Dependences != null)

                            otherNode.Dependences.Remove(item.Key);

                    }

                } else if (list.Count > 0) {

                    //如果发现有向环,则抛出异常

                    throw new InvalidOperationException("存在双向引用或循环引用。");

                }

            }

        }

    }



    /// <summary>

    /// 拓扑节点类。

    /// </summary>

    public class TopologicNode {

        /// <summary>

        /// 获取或设置节点的键值。

        /// </summary>

        public string Key { get; set; }



        /// <summary>

        /// 获取或设置依赖节点的键值列表。

        /// </summary>

        public List<string> Dependences { get; set; }

    }

}

 

 


 测试代码如下:

using System;

using System.Collections.Generic;



namespace ConsoleApplication1 {

    class Program {

        static void Main(string[] args) {

            List<TopologicNode> nodes = new List<TopologicNode>()

            {

                new TopologicNode(){ Key = "XMedia", 

                    Dependences = new List<string>(){ "XMedia.Controllers", "XMedia.Models", "XMedia.Logics", "XMedia.Commons" } },

                new TopologicNode(){ Key = "XMedia.Controllers",

                    Dependences = new List<string>(){"XMedia.Models","XMedia.Logics","XMedia.Commons"}},

                new TopologicNode(){ Key = "XMedia.Logics", 

                    Dependences = new List<string>(){ "XMedia.Models","XMedia.Commons"}},

                new TopologicNode(){ Key = "XMedia.Models" },

                new TopologicNode(){ Key = "XMedia.Commons" }

            };



            //输出拓扑排序的结果

            TopologicSort sort = new TopologicSort();

            foreach (var key in sort.OrderBy(nodes)) {

                Console.WriteLine(key);

            }

            Console.ReadLine();

        }

    }

}

运行结果如下图所示:

拓扑排序算法的一个应用

 

你可能感兴趣的:(排序算法)