系統中用到了進度計劃編制功能,支持從project文件直接導入數據,並可以在系統中對wbs任務進行增、刪、改操做。wbs任務分解中一個重要的概念就是前置任務,前置任務設置肯定了不一樣任務項之間的依賴關係,以軟件開發的通常過程爲例,需求調研就是系統設計的前置任務。具體來講前置任務又分爲如下四種類型node
把這個任務的開始日期和前提條件任務的結束日期對齊,通常用於串行的任務安排,前一個任務必須完成後才能啓動下一個新任務算法
把這個任務的開始日期和前提條件任務的開始日期對齊,通常用於並行任務的安排,也能夠一個任務啓動後,第二個任務延後或提早很多天啓動。數據庫
把這個任務的結束日期和前提條件任務的結束日期對齊,能夠用於協調任務的統一時間完成,這樣能夠定義好任務的開始時間數組
把這個任務的結束日期和前提條件任務的開始日期對齊,或者說是前置任務開始的日期決定了後續任務的完成時間數據結構
不論是哪一種類型,某項任務老是依賴於其前置任務,這就要求,任務的前置關係不能出現循環(閉環),好比A->B->A這種狀況是絕對不容許的。函數
任務關係表基本數據格式以下oop
SourceId跟TargetId標識任務的Id,經過SourceId、TargetId肯定任務之間先後置關係。每一個任務項能夠看做是一個節點,任務的前置關係能夠標識節點與節點之間有向連線,這在數據結構中是一種標準的有向圖。測試
先看一下數據結構中對圖的定義:圖是由有窮、非空點集和邊集合組成,簡寫成G(V,E);編碼
其中G表示Graph,V和E是圖中兩個基本元素,V表示Vertex(頂點),E表示Edge(邊)。圖按照邊是否有方向又分爲有向圖和無向圖,上面咱們看到用箭頭表示邊方向的是一個有向圖,無向圖通常用下圖方式表示。spa
本文還涉及到關於圖的一個重要概念是度。
度:與某個頂點相鏈接的邊數稱爲該定點的度
出度、入度:對於有向圖的概念,出度表示此頂點爲起點的邊的數目,入度表示此頂點爲終點的邊的數目
圖的存儲結構設計有不少種,經常使用的有鄰接矩陣和鄰接鏈表兩種。
鄰接矩陣採用2個數組,一個1維數組用來存儲節點信息,一個2維數組用來存儲邊信息。其中二維數組arr[i][j]表示節點i到j的邊信息,若是爲1表示有邊,若是爲0表示無邊。
從這個矩陣中,很容易知道圖中的信息。
一、能夠判斷任意兩頂點之間是否有邊無邊
二、要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行或(第i列)的元素之和
三、求頂點vi的全部鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]爲1就是鄰接點
鄰接鏈表整體思路以下:
圖中頂點用一個一維數組存儲,固然,頂點也能夠用單鏈表來存儲,不過,數組能夠較容易的讀取頂點的信息,更加方便。
圖中每一個頂點vi的全部鄰接點構成一個線性表,因爲鄰接點的個數不定,因此,用單鏈表存儲,無向圖稱爲頂點vi的邊表,有向圖則稱爲頂點vi做爲弧尾的出邊表。
頂點表的各個結點由data和firstedge兩個域表示,data是數據域,存儲頂點的信息,firstedge是指針域,指向邊表的第一個結點,即此頂點的第一個鄰接點。邊表結點由adjvex和next兩個域組成。adjvex是鄰接點域,存儲某頂點的鄰接點在頂點表中的下標,next則存儲指向邊表中下一個結點的指針。
關於圖的多種存儲結構設計方式,請參考數據結構相關數據,慢慢理解。
本文采用鄰接鏈表存儲結構實現,對於有向圖是否包含閉環的判斷,採用的是拓撲排序方法,若是可以用拓撲排序完成對圖中全部節點的排序的話,就說明這個圖中沒有環,而若是不能完成,則說明有環。
拓撲排序算法的主要操做步驟以下:
一、從有向圖中選取一個沒有前驅(即入度爲0)的頂點,並輸出之;
二、從有向圖中刪去此頂點同時找到該頂點的鄰接點,將該頂點的鄰接點的入度-1,若入度爲0則壓入棧中
重複上述兩步,直至圖空,或者圖不空但找不到入度爲0的頂點爲止。若是找到的頂點數與圖的頂點集合總數相等,說明無閉環,不然說明存在閉環。具體實現思路還須要慢慢體會。
根據上面對圖的鄰接鏈表相關定義及理解,首先定義圖的頂點類。
/// <summary> /// 頂點 /// </summary> /// <typeparam name="TValue">數據類型泛型</typeparam> public class Vertex<TValue> { public TValue data; // 數據 public Node<TValue> firstLinkNode; // 第一個鄰接節點 public bool visited; // 訪問標誌,遍歷時使用 public int inDegree; // 表示該節點入度 /// <summary> /// 構造函數 /// </summary> /// <param name="value"></param> public Vertex(TValue value) { data = value; } }
定義鏈表鄰接點類
/// <summary> /// 表示鏈表中的鄰接點 /// </summary> public class Node<TValue> { public Vertex<TValue> adjvex; //頂點 public Node<TValue> next; //下一個鄰接點 /// <summary> /// 構造函數 /// </summary> /// <param name="value"></param> public Node(Vertex<TValue> value) { adjvex = value; } }
定義鄰接鏈表表示類,其中包含圖的頂點集合的屬性、添加頂點、添加邊(有向邊、無向邊)、拓撲排序是否成功(有向圖閉環檢測)等操做方法,具體的實現及說明參看代碼註釋。
/// <summary> /// 圖的鄰接表表示類 /// </summary> /// <typeparam name="T">泛型類型</typeparam> public class AdjacencyList<T> { List<Vertex<T>> items; // 圖的頂點集合 /// <summary> /// 構造函數 /// </summary> public AdjacencyList() { items = new List<Vertex<T>>(); } /// <summary> /// 添加一個頂點 /// </summary> /// <param name="item"></param> public void AddVertex(T item) { // 頂點不存在 if (!Contains(item)) { items.Add(new Vertex<T>(item)); } } /// <summary> /// 添加無向邊 /// </summary> /// <param name="from">頭頂點</param> /// <param name="to">尾頂點</param> public void AddEdge(T from, T to) { Vertex<T> fromVer = Find(from); //找到起始頂點 if (fromVer == null) throw new ArgumentException("頭頂點並不存在!"); Vertex<T> toVer = Find(to); //找到結束頂點 if (toVer == null) throw new ArgumentException("尾頂點並不存在!"); //無向圖的兩個頂點都需記錄邊信息,有向圖只需記錄單邊信息 //即無相圖的邊其實就是兩個雙向的有向圖邊 AddDirectedEdge(fromVer, toVer); AddDirectedEdge(toVer, fromVer); } /// <summary> /// 查找圖中是否包含某項 /// </summary> /// <param name="item"></param> /// <returns></returns> public bool Contains(T data) { foreach (Vertex<T> v in items) { if (v.data.Equals(data)) return true; } return false; } /// <summary> /// 根據頂點數據查找頂點 /// </summary> /// <param name="data">數據</param> /// <returns></returns> public Vertex<T> Find(T data) { foreach (Vertex<T> v in items) { if (v.data.Equals(data)) return v; } return null; } /// <summary> /// 添加有向邊 /// </summary> /// <param name="fromVer">頭頂點</param> /// <param name="toVer">尾頂點</param> public void AddDirectedEdge(Vertex<T> fromVer, Vertex<T> toVer) { if (fromVer.firstLinkNode == null) //無鄰接點時,當前添加的尾頂點就是firstLinkNode { fromVer.firstLinkNode = new Node<T>(toVer); } else // 該頭頂點已經存在鄰接點,則找到該頭頂點鏈表最後一個Node,將toVer添加到鏈表末尾 { Node<T> tmp, node = fromVer.firstLinkNode; do { // 檢查是否添加了重複有向邊 if (node.adjvex.data.Equals(toVer.data)) { throw new ArgumentException("添加了重複的邊!"); } tmp = node; node = node.next; } while (node != null); tmp.next = new Node<T>(toVer); //添加到鏈表未尾 } } /// <summary> /// 拓撲排序是否能成功執行 /// 對有向圖來講,若是可以用拓撲排序完成對圖中全部節點的排序的話,就說明這個圖中沒有環,而若是不能完成,則說明有環。 /// </summary> /// <returns></returns> public bool TopologicalSort() { Stack<Vertex<T>> stack = new Stack<Vertex<T>>(); // 定義棧 items.ForEach(it => // 循環頂點集合,將入度爲0的頂點入棧 { if (it.inDegree == 0) stack.Push(it); //入度爲0的頂點入棧 }); int count = 0; // 定義查找到的頂點總數 while (stack.Count > 0) { Vertex<T> t = stack.Pop(); // 出棧 count++; if (t.firstLinkNode != null) { Node<T> tmp = t.firstLinkNode; while (tmp != null) { tmp.adjvex.inDegree--; // 鄰接點入度-1 if (tmp.adjvex.inDegree == 0) // 若是鄰接點入度爲0,則入棧 stack.Push(tmp.adjvex); tmp = tmp.next; // 遞歸全部鄰接點 } } } if (count < items.Count) // 找到的結果數量小於圖頂點個數相同,表示拓撲排序失敗,表示有閉環 { return false; } return true; } }
根據數據庫存儲的SourceId和TargetId集合,封裝一個GraphHelper類,提供一個檢測有向圖閉環的CheckDigraphLoop的靜態方法
/// <summary> /// 圖操做輔助類 /// </summary> public class GraphHelper { /// <summary> /// 檢測有向圖是否有閉環迴路 /// </summary> /// <param name="originalData">初始數據:逗號分割的from跟to字符串集合</param> /// <returns></returns> public static bool CheckDigraphLoop(List<string> originalData) { AdjacencyList<string> adjacencyList = new AdjacencyList<string>(); string fromData = string.Empty; string toData = string.Empty; //構造有向圖的鄰接表表示 originalData.ForEach(it => { fromData = it.Split(',')[0]; //獲得from頂點數據 toData = it.Split(',')[1]; //獲得to定點數據 adjacencyList.AddVertex(fromData); adjacencyList.AddVertex(toData); var fromVertex = adjacencyList.Find(fromData); // 找到起始頂點 var toVertex = adjacencyList.Find(toData); // 找到目標頂點 toVertex.inDegree++; //目標頂點的入度+1 adjacencyList.AddDirectedEdge(fromVertex, toVertex); //添加有向邊 }); return adjacencyList.TopologicalSort(); } }
測試
static void Main(string[] args) { List<string> temp = new List<string>(); temp.Add("1,2"); temp.Add("1,3"); temp.Add("2,4"); temp.Add("2,5"); temp.Add("3,6"); temp.Add("3,7"); temp.Add("5,6"); temp.Add("6,1"); var result= GraphHelper.CheckDigraphLoop(temp); Console.WriteLine(result); Console.ReadLine(); }
參考數據結構關於圖的相關C語言實現,用C#實現了經過拓撲排序算法進行的有向圖閉環檢測功能。
對於無向圖的閉環檢測檢測通常採用以下思路:
第一步:刪除全部度<=1的頂點及相關的邊,並將另外與這些邊相關的其它頂點的度減一。
第二步:將度數變爲1的頂點排入隊列,並從該隊列中取出一個頂點重複步驟一。
若是最後還有未刪除頂點,則存在環,不然沒有環。
感興趣的朋友能夠本身去揣摩實現一下。