在呈現層級數據爲一個樹形視圖(TreeView)的時候,常常會遇到一個問題,就是要判斷這些層級數據會不會形成循環,否則在構造樹形的時候會出現堆棧溢出(StackoverflowException)的錯誤。css
那麼如何判斷是否循環呢?尤爲在保存層級數據是經過父節點Id的遞歸方式來保存的狀況下(保存層級數據還有一種方式就是層級化的Id)。兩種保存方式都必需要求每一個節點數據都具備惟一的Id。html
以前本身寫過一種簡單的判斷方法,昨天又要從新實現相似算法(且數據結構不太同樣),就打算看看網絡上有沒有一種更好的方式。結果搜索後,竟然看到假設樹形層級的一個限值,若是超過限值就中止構造。這樣的解決辦法看着也是醉了。node
下面簡單介紹一下我用過的兩種解決辦法。算法
第一種最簡單的方式就是每次新增子節點都判斷要新增的節點的Id是否出如今當前節點的父節點鏈上。即檢查當前節點的父祖節點的Id是否包含打算要添加節點的Id(且一直檢查到根節點,檢查的具體算法可使用循環或者遞歸的方式)。可是這種方式要求構建數據模型的時候,必須包含一個Parent的屬性。(具體代碼就省略了……,也懶得翻以前的代碼)。(2014-11-25 23:52:46更新:仍是補充點代碼,見下)網絡
public interface IHasParent<T> { T GetParent(); } public interface IHasId<T> { T GetId(); } /// <summary> /// 在本身和全部父輩層級中是否包含此Id(用於判斷是否循環) /// </summary> /// <typeparam name="TData">實現了IHasParent和IHasId的對象</typeparam> /// <typeparam name="TId">Id的類型</typeparam> /// <param name="self">當前對象</param> /// <param name="id">需判斷的Id</param> /// <returns></returns> public static bool ExistInAncestry<TData, TId>(this TData self, TId id) where TData : class, IHasParent<TData>, IHasId<TId> { var current = self; while (current != null) { if (Equals(current.GetId(), id)) return true; current = current.GetParent(); } return false; }
若是沒有Parent屬性的狀況下,又要如何判斷呢?就是昨天我使用的第二種方式:構造一個輔助字典集合來記錄全部樹形分支上的全部Id,而後判斷這些Id是否有重複。下面的代碼片斷基本能夠解釋這種用法:數據結構
public List<PartNode> GenerateTree() { var nodes = new List<PartNode>(); //建立循環判斷字典,Key是全部層級上的節點索引構造的字符串,Value是這個分支上全部的節點Id(本例爲Code) var cycleCheckerDict = new Dictionary<string, HashSet<string>>(); cycleCheckerDict.Add("-1", new HashSet<string>(new[] { Parts[0].Code })); AddNodes(Parts[0], nodes, 1, cycleCheckerDict, "-1"); return nodes; } private void AddNodes(PartNodeInfo parent, List<PartNode> nodes, double parentQuantity, Dictionary<string, HashSet<string>> cycleCheckerDict, string parentCycleCheckerKey) { int nodeIndex = -1; var groupsByCode = from part in Parts where part.ParentCode == parent.Code && !part.IsOutsourcingSubstitute group part by part.Code; foreach (var group in groupsByCode) { var part = group.ToList()[0]; var node = new PartNode { Amount = Setting.IsUnitAmount ? part.Quantity : group.Sum(o => o.Quantity) / parentQuantity, Code = part.Code, Description = part.Description, Name = part.Name, Specification = part.Specification, Unit = UnitMappings[part.Unit], Weight = part.Weight, WeightUnit = part.WeightUnit }; nodes.Add(node); nodeIndex++; //判斷是否循環 var parentCycleCheckerIdChain = cycleCheckerDict[parentCycleCheckerKey]; if (parentCycleCheckerIdChain.Contains(part.Code)) { ErrorLog.AppendLine(string.Format("零部件 {0}({1}) 會形成樹形循環", part.Name, part.Code)); continue; } var currentCycleCheckerKey = parentCycleCheckerKey + "," + nodeIndex; var currentCycleCheckerIdChain = new HashSet<string>(parentCycleCheckerIdChain); currentCycleCheckerIdChain.Add(part.Code); cycleCheckerDict.Add(currentCycleCheckerKey, currentCycleCheckerIdChain); //添加下級零部件 node.Nodes = new List<PartNode>(); AddNodes(part, node.Nodes, correctQuantity, cycleCheckerDict, currentCycleCheckerKey); } cycleCheckerDict.Remove(parentCycleCheckerKey);//刪除遺留Id記錄鏈 }
固然,第一種方法要比第二種方法簡便(甚至效率也可能會更好,我沒有實際測試,也沒有計算算法複雜度),且第一種方法易於封裝爲通用的函數。app
最後留一個問題,如何在樹形上顯示循環的數據?函數