拓撲排序算法的一個應用


在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();
        }
    }
}

運行結果如下圖所示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM