【小技巧】如何判断树形结构产生循环
2018-06-18 03:04:42来源:未知 阅读 ()
在呈现层级数据为一个树形视图(TreeView)的时候,经常会遇到一个问题,就是要判断这些层级数据会不会造成循环,不然在构造树形的时候会出现堆栈溢出(StackoverflowException)的错误。
那么如何判断是否循环呢?尤其在保存层级数据是通过父节点Id的递归方式来保存的情况下(保存层级数据还有一种方式就是层级化的Id)。两种保存方式都必须要求每个节点数据都具有唯一的Id。
之前自己写过一种简单的判断方法,昨天又要重新实现类似算法(且数据结构不太一样),就打算看看网络上有没有一种更好的方式。结果搜索后,居然看到假设树形层级的一个限值,如果超过限值就停止构造。这样的解决办法看着也是醉了。
下面简单介绍一下我用过的两种解决办法。
第一种最简单的方式就是每次新增子节点都判断要新增的节点的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记录链 }
当然,第一种方法要比第二种方法简便(甚至效率也可能会更好,我没有实际测试,也没有计算算法复杂度),且第一种方法易于封装为通用的函数。
最后留一个问题,如何在树形上显示循环的数据?
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 如何0基础学习C/C++? 2020-06-06
- 【图论】几个例题~ 2020-04-14
- 标准输入重定向到文件后,如何连续读入,如何判断标准输入流 2020-03-20
- C、C++ 标准输入重定向 & 万能头 - 编程技巧 2020-03-20
- 位运算的应用 2020-02-13
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash