拓撲排序及其實際應用


  最近在做實際項目中遇到了一個問題,如何判斷一個層級結構的圖是否存在循環引用。剛開始想到了方法是用遞歸進行判斷,后來想到大學學過的拓撲排序可以解決該問題,於是翻了下數據結構這本書,閱讀了園友的文章,根據自己的理解寫下了這篇隨筆。

閱讀目錄

拓撲排序介紹

  百度百科定義:

  對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出現在v之前。通常,這樣的線性序列稱為滿足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之為拓撲排序。

  上面的定義看完可能不知道是什么意思,舉兩個實際的例子就明白了。

      1.大學課程排序

  大學課程的學習是有先后順序的,C語言是基礎,數據結構依賴於C語言,其它課程也有類似依賴關系。這樣的一個課程安排是怎么實現的呢?

  2.VS項目編譯順序

      假設VS中有三個項目A,B,C,它們的關系如下圖。VS編譯器是如何判斷三個項目的編譯順序的呢?

  

 

問題引入及算法實現

   這次實際項目中碰到的問題可以歸納為控件聯動選擇,即常見的省份,城市,地區聯動。為了實現通用的下拉連dog,設計了一套表結構,最終保存數據如下。

    

     看到這里也許你不明白這個和拓撲排序能扯上什么關系,假如省份下拉又依賴於地區下拉,那這樣就會形成一個死循環。為了避免這樣的情況需要在數據保存時,校驗是否存在閉環。

     下面給出,解決上述問題的兩種算法。

      1.遞歸判斷

     步驟如下

      (1)找當前節點的父級節點(也可以叫依賴的節點)  

  (2)父級節點不為為空且不等於當前節點自己,則尋找父級節點對應的父級節點

      (3)重復1,2。最終找到的節點=自己 ,則存在閉環,否則不存在

代碼實現

   首先定義了一個類似的結構   

    public class Node
    {
        /// <summary>
        /// 當前節點ID
        /// </summary>
        public int Key { get; set; }

        /// <summary>
        /// 父級節點ID
        /// </summary>
        public int? Parent { get; set; }
    }
/// <summary>
    /// 遞歸判斷是否存在循環引用
    /// </summary>
    public class RecursionSort
    {
        /// <summary>
        /// 遞歸判斷是否存在循環引用
        /// </summary>
          public  static bool CheckRecursion(List<Node> list)
        {
            foreach (var node in list)
            {
                if (RecursionSort.CheckRecursion(node.Key,node, list))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 遞歸判斷是否存在循環引用
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private static bool CheckRecursion(int key,Node curNode, List<Node> list)
        {
            if (curNode.Parent == key)
            {
                return true;
            }
            //尋找父級節點對應的父級節點信息
            if (curNode.Parent != null)
            {
                Node pNode = list.Where(e => e.Key == curNode.Parent).FirstOrDefault<Node>();
                return CheckRecursion(key,pNode, list);
            }
            return false;
        }
    }
        static void Main(string[] args)
        {
            //遞歸判斷
            List<Node> list = new List<Node>();
            list.Add(new Node { Key=1,Parent=2});
            list.Add(new Node { Key = 2, Parent = 1 });
            list.Add(new Node { Key = 3, Parent = 2 });
            Console.WriteLine(RecursionSort.CheckRecursion(list));
            Console.Read();
        }

    2.拓撲排序

   步驟如下

        (1) 從有向圖中選擇一個出度為0(即不依賴任何其它節點)的頂點並且輸出它。
    (2) 從圖中刪去該頂點,並且刪去該頂點的所有邊。
        (3) 重復上述兩步,直到剩余的圖中沒有出度為0的頂點。

      我們來看一下上面舉的VS項目編譯順序列子按照上述算法的演示過程

     第一步選擇 C節點

   

      第二步選擇 B節點

    

       至此完成了整個排序C,B,A 即先編譯C項目,再編譯B項目,最后編譯A項目

    代碼實現如下

    /// <summary>
    /// 拓撲節點類。
    /// </summary>
    public class TopologicNode<T>
    {
        /// <summary>
        /// 獲取或設置節點的鍵值。
        /// </summary>
        public T Key { get; set; }

        /// <summary>
        /// 獲取或設置依賴節點的鍵值列表。
        /// </summary>
        public List<T> Dependences { get; set; }
    }
 /// <summary>
    /// 拓撲排序類。
    /// </summary>
    public class TopologicSort
    {
        /// <summary>
        /// 拓撲順序。
        /// </summary>
        /// <typeparam name="TKey">節點的鍵值類型。</typeparam>
        /// <param name="list">一組節點。</param>
        /// <returns>拓撲序列。</returns>
        /// <exception cref="InvalidOperationException">如果存在雙向引用或循環引用,則拋出該異常。</exception>
        public static List<T> OrderBy<T>(List<TopologicNode<T>> list)
        {
            if (list == null)
            {
                throw new ArgumentNullException("參數空異常");
            }
            List<T> listResult = new List<T>();
            while (list.Count > 0)
            {
                //查找依賴項為空的節點
                var item = list.FirstOrDefault(c => c.Dependences == null || c.Dependences.Count == 0);
                if (item != null)
                {
                    listResult.Add(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("存在循環引用");
                }
            }
            return listResult;
        }
    }
 //拓撲排序
            //節點3依賴於2和1節點
            list.Add(new Node { Key = 3, Parent = 1 });

            List<TopologicNode<int>> listTopologicNode = new List<TopologicNode<int>>();
            //構建排序節點

            var group = (from p in list
                         group p by p.Key into g
                         select g);

            foreach (var g in group)
            {
                TopologicNode<int> node = new TopologicNode<int>();
                node.Key = g.Key;
                node.Dependences = new List<int>();
                foreach (Node value in g)
                {
                    if (value.Parent != null)
                    {
                        node.Dependences.Add(value.Parent.GetValueOrDefault());
                    }
                }
                listTopologicNode.Add(node);
            }

            try
            {
                List<int> result = TopologicSort.OrderBy<int>(listTopologicNode);
                result.ForEach(e => {
                    Console.WriteLine(e);
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

運行結果如下

本章總結

     本篇用到了Linq語法,如有不懂的可以到園里找找相關知識。后續我會專門寫一篇關於Linq,函數委托的文章,敬請期待!第一篇寫算法的隨筆到此完成,后續有其它算法靈感都會寫到博客園,拓撲排序的實際應用場景還有很多,最短路徑等等。如果您感覺本文不錯,對您有所幫助,請您不吝點擊下右邊的推薦按鈕,謝謝!

     本章代碼示例代碼下載地址:http://code.taobao.org/svn/TopologicalSort ,請使用SVN進行下載!


免責聲明!

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



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