【小技巧】如何判断树形结构产生循环

在呈现层级数据为一个树形视图(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;
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

如果没有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记录链
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

当然,第一种方法要比第二种方法简便(甚至效率也可能会更好,我没有实际测试,也没有计算算法复杂度),且第一种方法易于封装为通用的函数。

最后留一个问题,如何在树形上显示循环的数据?

时间: 2024-07-31 14:23:13

【小技巧】如何判断树形结构产生循环的相关文章

iOS小技巧---swift 判断IOS版本及适配

operatingSystemVersion 为了更复杂的版本比较,operatingSystemVersion能够被直接检查.将它和Swift模式比较和switch语句组合,可以使得代码更简洁. let os = NSProcessInfo().operatingSystemVersion switch  (os.majorVersion, os.minorVersion, os.patchVersion) { case  (8, _, _):      println( "iOS >=

【技术积累】树形结构的循环查找实现案例1

无限级服务端数据组织方案的实现,提供解决方案,其中数据库查询可替换为List的方式查找等其它方式. function queryAllSubCustomers($cstId) {$sqlA="SELECT ID, Name, ParentID FROM T_CustomerInfo WHERE ID = $cstId ";$custList=Sql_Query($sqlA);$AllCustInfs=array(); $result=array(); while(count($cust

winform之权限判断小技巧

每个页面都要判断用户是否登陆并且判断用户是否拥有相应的权限,,以至于每个页面都要判断Session["user"]是否为空,后期不好维护 小技巧: 因为每个页面都继承与Page类,又因为继承的单根性,所以 再新建一个基类,让这个基类继承与Page类, 让页面继承与这个基类. 同时,这个基类还要实现Page中的一个方法来初始化 //继承page页面,必须实现page中的方法,用来初始化基类 public class Pagebase:Page { protected override v

HDU 5895 Mathematician QSC(矩阵乘法+循环节降幂+除法取模小技巧+快速幂)

传送门:HDU 5895 Mathematician QSC 这是一篇很好的题解,我想讲的他基本都讲了http://blog.csdn.net/queuelovestack/article/details/52577212 [分析]一开始想简单了,对于a^x mod p这种形式的直接用欧拉定理的数论定理降幂了 结果可想而知,肯定错,因为题目并没有保证gcd(x,s+1)=1,而欧拉定理的数论定理是明确规定的 所以得另谋出路 那么网上提供了一种指数循环节降幂的方法 具体证明可以自行从网上找一找 有

递归、嵌套for循环、map集合方式实现树形结构菜单列表查询

有时候, 我们需要用到菜单列表,但是怎么样去实现一个菜单列表的编写呢,这是一重要的问题. 比如我们需要编写一个树形结构的菜单,那么我们可以使用JQuery的zTree插件:http://www.treejs.cn/v3/main.php#_zTreeInfo 例如现在需要编写一个这样的菜单列表.那么就可以使用JQuery的zTree插件. 先看一下数据库表结构. CREATE TABLE `permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `

《ServerSuperIO Designer IDE使用教程》- 5.树形结构管理设备驱动,小版本更新。发布:v4.2.3.1版本

v4.2.3.1 更新内容:1.选择和管理设备驱动,增加树状结构显示.2.优化ide代码,核心代码没有改动.下载地址:官方下载 5. 树形结构管理设备驱动,小版本更新 5.1    概述 此次升级主要是对增加设备驱动的树形结构显示,方便按类选择设备驱动.涉及到增加设备驱动和管理设备驱动两个功能.此次是小版本更新,不影响以前版本的使用. 5.2    树形结构管理设备驱动 增加设备驱动功能,按树形结构选择.现阶段主要驱动包括:Modbus Serial.Modbus Tcp.SuperLink(网

【前端】javascript中10常用的个小技巧总结

javascript中10常用的个小技巧总结 本文转自:http://www.cnblogs.com/libin-1/p/6756393.html 1. new Set() 可能有人知道ES6中提供了新的数据结构 Set,但是能够灵活运用的人或许不多.利用Set数据结构我们能够轻松的去重一个数组,比如: let arr = [1, 2, 2, 3]; let set = new Set(arr); let newArr = Array.from(set); // Array.from方法可以将

一些Python的惯用法和小技巧:Pythonic

Pythonic其实是个模糊的含义,没有确定的解释.网上也没有过多关于Pythonic的说明,我个人的理解是更加Python,更符合Python的行为习惯.本文主要是说明一些Python的惯用法和小技巧,其实与上一篇<编码规范>有异曲同工之妙,都是为了增加代码可读性,但Pythonic可能还会从性能的角度进行考虑. 首先是两个不得不说的Python的特性List Comprehension和Generator Expression,非常精简的语法,很大程度上取代了冗长for循环. 1. 列表解

javascript小技巧-js小技巧收集(转)

本文转载自:http://blog.csdn.net/ocean20/article/details/2498699 每一项都是js中的小技巧,但十分的实用! 1.document.write(""); 输出语句 2.JS中的注释为// 3.传统的HTML文档顺序是:document->html->(head,body) 4.一个浏览器窗口中的DOM顺序是:window->(navigator,screen,history,location,document) 5.得