zoukankan      html  css  js  c++  java
  • 【小技巧】如何判断树形结构产生循环

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

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

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

  • 相关阅读:
    积水路面Wet Road Materials 2.3
    门控时钟问题
    饮料机问题
    Codeforces Round #340 (Div. 2) E. XOR and Favorite Number (莫队)
    Educational Codeforces Round 82 (Rated for Div. 2)部分题解
    Educational Codeforces Round 86 (Rated for Div. 2)部分题解
    Grakn Forces 2020部分题解
    2020 年百度之星·程序设计大赛
    POJ Nearest Common Ancestors (RMQ+树上dfs序求LCA)
    算法竞赛进阶指南 聚会 (LCA)
  • 原文地址:https://www.cnblogs.com/redmoon/p/4122168.html
Copyright © 2011-2022 走看看