G
N
I
D
A
O
L

C# 通用树形数据结构

前言

树在图论中是一种重要的图,由于其自身的许多特殊性质,也是一种重要的计算机数据结构,在很多地方都有用。但是这些树大多都是作为其他应用的内部数据结构来使用。我们无法了解这些树的详细信息,而 .Net 也没有在内置的集合类库中提供树形数据结构的类。很多时候我们都需要树形数据完成一些工作,在自己的实践经验和查阅大量相关资料后,我编写了一个使用简单,能方便地将普通集合转换为树形集合(当然前提是这些集合元素之间存在能够表明层级关系的数据),提供了大量图论中有关节点和整棵树的信息和常用算法的通用树形数据结构类。希望能够简化此类需求的开发。

正文

图论中有关树的定义和性质都能很容易查到资料,就不再啰嗦,进入正题。首先,定义一个树形结构有很多方法,在此我选用最直观的双亲孩子表示法定义。为了能将包含层级信息的普通数据转换为树形数据,使用接口进行定义,并以接口为核心进行扩展。

接口定义如下:

/// <summary>
/// 可分层数据接口
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public interface IHierarchical<out T>
{
    /// <summary>
    /// 当前节点的数据
    /// </summary>
    T Current { get; }

    /// <summary>
    /// 根节点
    /// </summary>
    IHierarchical<T> Root { get; }

    /// <summary>
    /// 双亲(父)节点
    /// </summary>
    IHierarchical<T> Parent { get; }

    /// <summary>
    /// 祖先节点集合(按路径距离升序)
    /// </summary>
    IEnumerable<IHierarchical<T>> Ancestors { get; }

    /// <summary>
    /// 子节点集合
    /// </summary>
    IEnumerable<IHierarchical<T>> Children { get; }

    /// <summary>
    /// 后代节点集合(深度优先先序遍历)
    /// </summary>
    IEnumerable<IHierarchical<T>> Descendants { get; }

    /// <summary>
    /// 兄弟节点集合(不包括自身节点)
    /// </summary>
    IEnumerable<IHierarchical<T>> Siblings { get; }

    /// <summary>
    /// 在兄弟节点中的排行
    /// </summary>
    int IndexOfSiblings { get; }

    /// <summary>
    /// 节点的层
    /// </summary>
    int Level { get; }

    /// <summary>
    /// 节点(以当前节点为根的子树)的高度
    /// </summary>
    int Height { get; }

    /// <summary>
    /// 节点的度
    /// </summary>
    int Degree { get; }

    /// <summary>
    /// 树(以当前节点为根的子树)的所有节点中度最大的节点的度
    /// </summary>
    int MaxDegreeOfTree { get; }

    /// <summary>
    /// 当前节点是否是根节点
    /// </summary>
    bool IsRoot { get; }

    /// <summary>
    /// 当前节点是否是叶子节点
    /// </summary>
    bool IsLeaf { get; }

    /// <summary>
    /// 当前节点是否有子节点
    /// </summary>
    bool HasChild { get; }

    /// <summary>
    /// 以当前节点为根返回树形排版的结构字符串
    /// </summary>
    /// <param name="formatter">数据对象格式化器(内容要为一行,否则会影响排版)</param>
    /// <param name="convertToSingleLine">处理掉换行符变成单行文本</param>
    /// <returns></returns>
    string ToString(Func<T, string> formatter, bool convertToSingleLine = false);
}

然后根据接口定义扩展类型。实现如下:

/// <summary>
/// 可分层数据扩展
/// </summary>
public static class HierarchicalExtensions
{
    /// <summary>
    /// 转换为可分层数据
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="item">数据</param>
    /// <param name="childSelector">下层数据选择器</param>
    /// <returns>已分层的数据</returns>
    public static IHierarchical<T> AsHierarchical<T>(this T item, Func<T, IEnumerable<T>> childSelector)
    {
        return new Hierarchical<T>(item, childSelector);
    }

    /// <summary>
    /// 分层数据
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    private class Hierarchical<T> : IHierarchical<T>
    {
        private readonly object _locker;
        private readonly Func<T, IEnumerable<T>> _childSelector;
        private IEnumerable<IHierarchical<T>> _children;

        /// <summary>
        /// 实例化分层数据
        /// </summary>
        /// <param name="item">数据</param>
        /// <param name="childSelector">下层数据选择器</param>
        public Hierarchical(T item, Func<T, IEnumerable<T>> childSelector)
        {
            _locker = new object();
            _children = null;
            Current = item;
            _childSelector = childSelector;
        }

        /// <summary>
        /// 实例化分层数据
        /// </summary>
        /// <param name="item">数据</param>
        /// <param name="parent">上层数据</param>
        /// <param name="childSelector">下层数据选择器</param>
        private Hierarchical(T item, IHierarchical<T> parent, Func<T, IEnumerable<T>> childSelector)
            : this(item, childSelector)
        {
            Parent = parent;
        }

        /// <summary>
        /// 初始化下层节点集合
        /// </summary>
        /// <returns>迭代结果集合接口</returns>
        private IEnumerable<IHierarchical<T>> InitializeChildren()
        {
            var children = _childSelector(Current);
            if (children == null)
                yield break;

            foreach (T item in children)
            {
                yield return new Hierarchical<T>(item, this, _childSelector);
            }
        }

        #region IHierarchicalDataItem<T> 成员

        public T Current { get; }

        public IHierarchical<T> Root
        {
            get
            {
                IHierarchical<T> node = this;

                while (node.Parent != null)
                    node = node.Parent;

                return node;
            }
        }

        public IHierarchical<T> Parent { get; }

        public IEnumerable<IHierarchical<T>> Ancestors
        {
            get
            {
                IHierarchical<T> node = Parent;

                while (node != null)
                {
                    yield return node;
                    node = node.Parent;
                }
            }
        }

        public IEnumerable<IHierarchical<T>> Children
        {
            get
            {
                if (_children == null)
                    lock (_locker)
                        if (_children == null)
                            _children = InitializeChildren().ToArray();

                return _children;
            }
        }

        public IEnumerable<IHierarchical<T>> Descendants
        {
            get
            {
                Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(Children.Reverse());

                while (stack.Count > 0)
                {
                    IHierarchical<T> node = stack.Pop();
                    yield return node;

                    foreach (IHierarchical<T> child in node.Children.Reverse())
                    {
                        stack.Push(child);
                    }
                }
            }
        }

        public IEnumerable<IHierarchical<T>> Siblings => Parent?.Children?.Where(node => node != this);

        public int IndexOfSiblings
        {
            get
            {
                if (Parent == null) return 0;

                int index = 0;
                foreach (var child in Parent.Children)
                {
                    if (child == this) break;
                    index++;
                }

                return index;
            }
        }

        //无缓存方法,每次访问相同节点都会重新枚举数据源并生成结果对象
        //包含相同数据T的包装IHierarchical<T>每次都不一样
        //public IEnumerable<IHierarchical<T>> Children
        //{
        //    get
        //    {
        //        var children = _childSelector(Current);
        //        if (children == null)
        //            yield break;

        //        foreach (T item in children)
        //        {
        //            yield return new Hierarchical<T>(item, this, _childSelector);
        //        }
        //    }
        //}

        public int Level => Parent?.Level + 1 ?? 1;

        public int Height => (Descendants.Any() ? Descendants.Select(node => node.Level).Max() - Level : 0) + 1;

        public int Degree => Children?.Count() ?? 0;

        public int MaxDegreeOfTree => Math.Max(Degree, Descendants.Any() ? Descendants.Select(node => node.Degree).Max() : 0);

        public bool IsRoot => Parent == null;

        public bool IsLeaf => Degree == 0;

        public bool HasChild => !IsLeaf;

        public string ToString(Func<T, string> formatter, bool convertToSingleLine = false)
        {
            var sbr = new StringBuilder();
            sbr.AppendLine(convertToSingleLine
                ? formatter(Current).Replace("\r", @"\r").Replace("\n", @"\n")
                : formatter(Current));

            var sb = new StringBuilder();
            foreach (var node in Descendants)
            {
                sb.Append(convertToSingleLine
                    ? formatter(node.Current).Replace("\r", @"\r").Replace("\n", @"\n")
                    : formatter(node.Current));
                sb.Insert(0, node == node.Parent.Children.Last() ? "└─" : "├─");

                for (int i = 0; i < node.Ancestors.Count() - (Ancestors?.Count() ?? 0) - 1; i++)
                {
                    sb.Insert(0,
                        node.Ancestors.ElementAt(i) == node.Ancestors.ElementAt(i + 1).Children.Last()
                            ? "    "
                            : "│  ");
                }

                sbr.AppendLine(sb.ToString());
                sb.Clear();
            }

            return sbr.ToString();
        }

        public override string ToString()
        {
            return ToString(node => node.ToString());
        }

        #endregion
    }

    /// <summary>
    /// 获取从根到节点的路径(中顺序经过的节点集合)
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="node">节点</param>
    /// <returns>路径中按经过顺序组成的节点集合</returns>
    public static IEnumerable<IHierarchical<T>> GetPathFromRoot<T>(this IHierarchical<T> node) =>
        node.Ancestors.Reverse();

    /// <summary>
    /// 判断节点是否是指定节点的祖先节点
    /// </summary>
    /// <typeparam name="T">节点数据类型</typeparam>
    /// <param name="node">待判断的节点</param>
    /// <param name="target">目标节点</param>
    /// <returns>判断结果</returns>
    public static bool IsAncestorOf<T>(this IHierarchical<T> node, IHierarchical<T> target)
    {
        if (node.Root != target.Root)
            throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree.");

        return target.Ancestors.SingleOrDefault(n => n == node) != null;
    }

    /// <summary>
    /// 判断节点是否是指定节点的后代节点
    /// </summary>
    /// <typeparam name="T">节点数据类型</typeparam>
    /// <param name="node">待判断的节点</param>
    /// <param name="target">目标节点</param>
    /// <returns>判断结果</returns>
    public static bool IsDescendantOf<T>(this IHierarchical<T> node, IHierarchical<T> target)
    {
        if (node.Root != target.Root)
            throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree.");

        return target.IsAncestorOf(node);
    }

    /// <summary>
    /// 获取节点与指定节点的最近公共祖先节点
    /// </summary>
    /// <typeparam name="T">节点数据类型</typeparam>
    /// <param name="node">待查找的节点</param>
    /// <param name="target">目标节点</param>
    /// <returns>最近的公共祖先节点</returns>
    public static IHierarchical<T> GetNearestCommonAncestor<T>(this IHierarchical<T> node, IHierarchical<T> target)
    {
        if (node.Root != target.Root)
            throw new InvalidOperationException($"{nameof(node)} and {nameof(target)} are not at same tree.");

        if (node.IsAncestorOf(target)) return node;
        if (target.IsAncestorOf(node)) return target;

        return node.Ancestors.Intersect(target.Ancestors).OrderByDescending(no => no.Level).First();
    }

    /// <summary>
    /// 获取从指定节点到当前节点的路径
    /// </summary>
    /// <typeparam name="T">节点数据类型</typeparam>
    /// <param name="node">当前节点(终点)</param>
    /// <param name="from">目标节点(起点)</param>
    /// <returns>按从目标节点到当前节点顺序经过的节点集合</returns>
    public static IEnumerable<IHierarchical<T>> GetPathFromNode<T>(this IHierarchical<T> node,
        IHierarchical<T> from)
    {
        if(node.Root != from.Root)
            throw new InvalidOperationException($"{nameof(node)} and {nameof(from)} are not at same tree.");

        yield return from;

        if (node == from) yield break;

        if (node.IsAncestorOf(from))
        {
            foreach (var ancestor in from.Ancestors)
            {
                yield return ancestor;
                if (ancestor == node)
                {
                    yield break;
                }
            }
        }

        var ancestorsOfNode = node.Ancestors.ToArray();
        if (node.IsDescendantOf(from))
        {
            for (int i = Array.IndexOf(ancestorsOfNode, from) - 1; i >= 0; i--)
            {
                yield return ancestorsOfNode[i];
            }

            yield return node;
            yield break;
        }

        var keyNode = ancestorsOfNode.Intersect(from.Ancestors).OrderByDescending(no => no.Level).First();
        foreach (var ancestor in from.Ancestors)
        {
            yield return ancestor;
            if (ancestor == keyNode)
            {
                break;
            }
        }

        for (int i = Array.IndexOf(ancestorsOfNode, keyNode) - 1; i >= 0; i--)
        {
            yield return ancestorsOfNode[i];
        }

        yield return node;
    }

    /// <summary>
    /// 获取从当前节点到指定节点的路径
    /// </summary>
    /// <typeparam name="T">节点数据类型</typeparam>
    /// <param name="node">当前节点(起点)</param>
    /// <param name="to">目标节点(终点)</param>
    /// <returns>按从当前节点到目标节点顺序经过的节点集合</returns>
    public static IEnumerable<IHierarchical<T>> GetPathToNode<T>(this IHierarchical<T> node, IHierarchical<T> to)
    {
        if (node.Root != to.Root)
            throw new InvalidOperationException($"{nameof(node)} and {nameof(to)} are not at same tree.");

        return to.GetPathFromNode(node);
    }

    /// <summary>
    /// 获取子孙数据(深度优先,先序)
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="root">根</param>
    /// <param name="predicate">子孙筛选条件</param>
    /// <returns>筛选的子孙</returns>
    public static IEnumerable<IHierarchical<T>> GetDescendantsDfsDlr<T>(this IHierarchical<T> root,
        Func<IHierarchical<T>, bool> predicate = null)
    {
        var children = predicate == null ? root.Children : root.Children.Where(predicate);
        Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(children.Reverse());

        while (stack.Count > 0)
        {
            IHierarchical<T> node = stack.Pop();
            yield return node;

            children = predicate == null ? node.Children : node.Children.Where(predicate);
            foreach (IHierarchical<T> child in children.Reverse())
            {
                stack.Push(child);
            }
        }

        #region 递归方式

        //foreach (T t in childSelector(root))
        //{
        //    if (predicate(t))
        //        yield return t;
        //    foreach (T child in GetDescendantDfsDlr(t, childSelector, predicate))
        //        yield return child;
        //}

        #endregion 递归方式
    }

    /// <summary>
    /// 获取子孙数据(深度优先,后序)
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="root">根</param>
    /// <param name="predicate">子孙筛选条件</param>
    /// <returns>筛选的子孙</returns>
    public static IEnumerable<IHierarchical<T>> GetDescendantsDfsLrd<T>(this IHierarchical<T> root,
        Func<IHierarchical<T>, bool> predicate = null)
    {
        var children = predicate == null ? root.Children : root.Children.Where(predicate);
        Stack<IHierarchical<T>> stack = new Stack<IHierarchical<T>>(children.Reverse());

        IHierarchical<T> lastAccessedNode = null;

        while (stack.Count > 0)
        {
            var node = stack.Peek();

            if (node.Children.Any() && node.Children.Last() != lastAccessedNode)
            {
                children = predicate == null ? node.Children : node.Children.Where(predicate);
                foreach (IHierarchical<T> child in children.Reverse())
                {
                    stack.Push(child);
                }
            }
            else
            {
                yield return node;

                lastAccessedNode = node;
                stack.Pop();
            }
        }
    }

    /// <summary>
    /// 获取子孙数据(广度优先)
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="root">根</param>
    /// <param name="predicate">子孙筛选条件</param>
    /// <returns>筛选的子孙</returns>
    public static IEnumerable<IHierarchical<T>> GetDescendantsBfs<T>(this IHierarchical<T> root,
        Func<IHierarchical<T>, bool> predicate = null)
    {
        predicate = predicate ?? (t => true);
        Queue<IHierarchical<T>> queue = new Queue<IHierarchical<T>>(root.Children.Where(predicate));

        while (queue.Count > 0)
        {
            IHierarchical<T> node = queue.Dequeue();
            yield return node;

            foreach (IHierarchical<T> child in node.Children.Where(predicate))
            {
                queue.Enqueue(child);
            }
        }
    }

    /// <summary>
    /// 转换为可枚举集合
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="root">根</param>
    /// <param name="predicate">子孙筛选条件</param>
    /// <param name="enumerateType">枚举方式</param>
    /// <returns>已枚举的集合</returns>
    public static IEnumerable<IHierarchical<T>> AsEnumerable<T>(this IHierarchical<T> root,
        EnumerateType enumerateType = EnumerateType.DfsDlr,
        Func<IHierarchical<T>, bool> predicate = null)
    {
        switch (enumerateType)
        {
            case EnumerateType.DfsDlr:
                yield return root;

                foreach (var descendant in GetDescendantsDfsDlr(root, predicate))
                {
                    yield return descendant;
                }

                break;
            case EnumerateType.DfsLrd:
                foreach (var descendant in GetDescendantsDfsLrd(root, predicate))
                {
                    yield return descendant;
                }

                yield return root;

                break;
            case EnumerateType.Bfs:
                yield return root;

                foreach (var descendant in GetDescendantsBfs(root, predicate))
                {
                    yield return descendant;
                }
                
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(enumerateType), enumerateType, null);
        }
    }
}

里面需要用到一个枚举,定义如下:

/// <summary>
/// 枚举方式
/// </summary>
public enum EnumerateType : byte
{
    /// <summary>
    /// 深度优先,先序
    /// </summary>
    DfsDlr,

    /// <summary>
    /// 深度优先,后序
    /// </summary>
    DfsLrd,

    /// <summary>
    /// 广度优先(层序)
    /// </summary>
    Bfs
}

这个实现类是一个不可变的树形数据类,一旦生成,无法修改,主要是作为普通数据的转换包装使用。但是其中的Children属性是延迟加载的,只有第一次访问Children属性或其他直接或间接依赖Children属性的成员时才会初始化Children属性,所以如果在访问到Children之前修改了源数据集,修改是会反映到Children中的。这个实现的遍历算法都采用了循环法,而非递归法,能有效避免栈溢出错误,运行效率也更好。

使用public static IHierarchical<T> AsHierarchical<T>(this T item, Func<T, IEnumerable<T>> childSelector)扩展方法就可以获取根节点的引用。其中childSelector是一个委托,用于获取源数据集中每个节点的子节点集合。

GetDescendants系列扩展方法可以以指定的遍历方式获取某个节点后代节点(不包括自身节点),并且其中的Func<IHierarchical<T>, bool> predicate参数可以对后代节点进行筛选,只有符合条件的才会返回,类似Linq中的Where方法,不过要注意,如果某个节点不符合条件,那么那个节点的后代也会被过滤掉。

AsEnumerable方法和GetDescendants系列方法差不多,不过这个方法会遍历包括自身在内的整棵树,这个方法同样可以进行过滤,注意点也和GetDescendants系列方法一样。

GetPath系列扩展方法可以对同一颗树的两个节点进行路径查找,返回的集合按顺序是从起点到终点所经过的所有节点,包括起点和终点。

IsXxxOf系列扩展方法可以判断同一颗树的两个节点是否存在祖先后代关系。

GetNearestCommonAncestor扩展方法可以查找同一颗树两个节点的最近公共祖先节点,如果两个节点间存在祖先后代关系则返回祖先节点。

string ToString(Func<T, string> formatter, bool convertToSingleLine = false)接口方法可以生成类似 Windows cmd 命令 tree 生成的目录树样式的字符串,方便总览树的结构。Func<T, string> formatter委托用于获取数据T的字符串表示形式,bool convertToSingleLine用于指定是否要强行把获得的字符串中的换行符处理掉,避免排版混乱。

结语

至此,一个通用的树形结构类就大功告成,图论中有关树的信息和常用算法基本上已经包括在内。数据集只要逻辑上包含层次关系就可以轻松包装成树形数据,除了不能修改这棵树这个小问题。自定义实现 IHierarchical<out T>接口的类可以实现包含特殊功能的树形数据类。如果想起来能增加什么功能的话,会在Github上更新代码。

转载请完整保留以下内容,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!

本文地址:https://www.cnblogs.com/coredx/p/10517448.html

完整源代码(包含示例):Github

里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

posted @ 2019-03-12 16:14  coredx  阅读(16604)  评论(2编辑  收藏  举报