Древовидная структура данных в C#



Я искал структуру данных дерева или графика в C#, но я думаю, что ее нет. обширное исследование структур данных с использованием C# 2.0 объясняет немного о том, почему. Есть ли удобная библиотека, которая обычно используется для обеспечения этой функциональности? Возможно, через стратегическую схему решить вопросы, представленные в статье.



Я чувствую себя немного глупо, реализуя свое собственное дерево, так же, как я бы реализовал свой собственный ArrayList.



Я просто хочу общее дерево, которое может быть несбалансированным. Подумайте о дереве каталогов. C5 выглядит изящно, но их древовидные структуры, похоже, реализованы как сбалансированные красно-черные деревья, лучше подходящие для поиска, чем представляющие иерархию узлов.

2052   20  

20 ответов:

мой лучший совет заключается в том, что нет стандартной структуры данных дерева, потому что есть так много способов ее реализации, что было бы невозможно охватить все базы одним решением. Чем конкретнее решение, тем менее вероятно, что оно применимо к любой данной проблеме. Я даже раздражаюсь LinkedList - что делать, если я хочу круговой связанный список?

основная структура, которую вам нужно будет реализовать, будет представлять собой набор узлов, и вот несколько вариантов, чтобы вы могли начатый. Предположим, что узел класса является базовым классом всего решения.

Если вам нужно только перемещаться вниз по дереву, то класс узла нуждается в списке дочерних элементов.

Если вам нужно перейти вверх по дереву, то классу узла нужна ссылка на его родительский узел.

создайте метод AddChild, который заботится обо всех мелочах этих двух точек и любой другой бизнес-логики, которая должна быть реализована (дочерние ограничения, сортировка дочерних элементов, так далее.)

Я ненавижу признавать это, но я закончил тем, что написал свой собственный класс дерева, используя связанный список. На несвязанной ноте я только что обнаружил эту круглую вещь, которая, будучи прикрепленной к вещи, которую я называю "осью", позволяет легче транспортировать товары.

delegate void TreeVisitor<T>(T nodeData);

class NTree<T>
{
    private T data;
    private LinkedList<NTree<T>> children;

    public NTree(T data)
    {
         this.data = data;
        children = new LinkedList<NTree<T>>();
    }

    public void AddChild(T data)
    {
        children.AddFirst(new NTree<T>(data));
    }

    public NTree<T> GetChild(int i)
    {
        foreach (NTree<T> n in children)
            if (--i == 0)
                return n;
        return null;
    }

    public void Traverse(NTree<T> node, TreeVisitor<T> visitor)
    {
        visitor(node.data);
        foreach (NTree<T> kid in node.children)
            Traverse(kid, visitor);
    }
}

простая рекурсивная реализация...

вот мой, который очень похож на Аарона Гейджа, просто немного более обычный, на мой взгляд. Для моих целей, я не столкнулся с какими-либо проблемами производительности с List<T>. Было бы достаточно легко переключиться на LinkedList, если это необходимо.


namespace Overby.Collections
{
    public class TreeNode<T>
    {
        private readonly T _value;
        private readonly List<TreeNode<T>> _children = new List<TreeNode<T>>();

        public TreeNode(T value)
        {
            _value = value;
        }

        public TreeNode<T> this[int i]
        {
            get { return _children[i]; }
        }

        public TreeNode<T> Parent { get; private set; }

        public T Value { get { return _value; } }

        public ReadOnlyCollection<TreeNode<T>> Children
        {
            get { return _children.AsReadOnly(); }
        }

        public TreeNode<T> AddChild(T value)
        {
            var node = new TreeNode<T>(value) {Parent = this};
            _children.Add(node);
            return node;
        }

        public TreeNode<T>[] AddChildren(params T[] values)
        {
            return values.Select(AddChild).ToArray();
        }

        public bool RemoveChild(TreeNode<T> node)
        {
            return _children.Remove(node);
        }

        public void Traverse(Action<T> action)
        {
            action(Value);
            foreach (var child in _children)
                child.Traverse(action);
        }

        public IEnumerable<T> Flatten()
        {
            return new[] {Value}.Concat(_children.SelectMany(x => x.Flatten()));
        }
    }
}

еще одна древовидная структура:

public class TreeNode<T> : IEnumerable<TreeNode<T>>
{

    public T Data { get; set; }
    public TreeNode<T> Parent { get; set; }
    public ICollection<TreeNode<T>> Children { get; set; }

    public TreeNode(T data)
    {
        this.Data = data;
        this.Children = new LinkedList<TreeNode<T>>();
    }

    public TreeNode<T> AddChild(T child)
    {
        TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };
        this.Children.Add(childNode);
        return childNode;
    }

    ... // for iterator details see below link
}

пример использования:

TreeNode<string> root = new TreeNode<string>("root");
{
    TreeNode<string> node0 = root.AddChild("node0");
    TreeNode<string> node1 = root.AddChild("node1");
    TreeNode<string> node2 = root.AddChild("node2");
    {
        TreeNode<string> node20 = node2.AddChild(null);
        TreeNode<string> node21 = node2.AddChild("node21");
        {
            TreeNode<string> node210 = node21.AddChild("node210");
            TreeNode<string> node211 = node21.AddChild("node211");
        }
    }
    TreeNode<string> node3 = root.AddChild("node3");
    {
        TreeNode<string> node30 = node3.AddChild("node30");
    }
}

бонус
Смотрите полноценное дерево с:

  • итератор
  • поиск
  • Java / C#

https://github.com/gt4dev/yet-another-tree-structure

вообще отлично C5 Generic Collection Library имеет несколько различных древовидных структур данных, включая наборы, пакеты и словари. Исходный код доступен, если вы хотите изучить их детали реализации. (Я использовал коллекции C5 в производственном коде с хорошими результатами, хотя я не использовал ни одну из древовидных структур специально.)

см.http://quickgraph.codeplex.com/

QuickGraph предоставляет общие направленные / неориентированные структуры данных графа и алгоритмы для .Net 2.0 и выше. QuickGraph поставляется с такими алгоритмами, как глубина первого seach, дыхание первый поиск, a* поиск, кратчайший путь, k-кратчайший путь, максимальный поток, минимальное связующее дерево, наименее общие предки и т. д... QuickGraph поддерживает MSAGL, GLEE и Graphviz для визуализации графиков, сериализации в GraphML и т. д...

Если вы хотите написать свой собственный, Вы можете начать с этого документа из шести частей, подробно описывающего эффективное использование структур данных C# 2.0 и как анализировать вашу реализацию структур данных в C#. В каждой статье есть примеры и установщик с примерами, которые вы можете следовать вместе.

"обширное исследование структур данных с использованием C# 2.0" Скотт Митчелл

У меня есть небольшое расширение для решений.

используя рекурсивное общее объявление и производный подкласс, вы можете лучше сосредоточиться на своей фактической цели.

обратите внимание, что это отличается от не универсальной реализации, вам не нужно бросать "узел" в "NodeWorker".

вот мой пример:

public class GenericTree<T> where T : GenericTree<T> // recursive constraint  
{
  // no specific data declaration  

  protected List<T> children;

  public GenericTree()
  {
    this.children = new List<T>();
  }

  public virtual void AddChild(T newChild)
  {
    this.children.Add(newChild);
  }

  public void Traverse(Action<int, T> visitor)
  {
    this.traverse(0, visitor);
  }

  protected virtual void traverse(int depth, Action<int, T> visitor)
  {
    visitor(depth, (T)this);
    foreach (T child in this.children)
      child.traverse(depth + 1, visitor);
  }
}

public class GenericTreeNext : GenericTree<GenericTreeNext> // concrete derivation
{
  public string Name {get; set;} // user-data example

  public GenericTreeNext(string name)
  {
    this.Name = name;
  }
}

static void Main(string[] args)  
{  
  GenericTreeNext tree = new GenericTreeNext("Main-Harry");  
  tree.AddChild(new GenericTreeNext("Main-Sub-Willy"));  
  GenericTreeNext inter = new GenericTreeNext("Main-Inter-Willy");  
  inter.AddChild(new GenericTreeNext("Inter-Sub-Tom"));  
  inter.AddChild(new GenericTreeNext("Inter-Sub-Magda"));  
  tree.AddChild(inter);  
  tree.AddChild(new GenericTreeNext("Main-Sub-Chantal"));  
  tree.Traverse(NodeWorker);  
}  

static void NodeWorker(int depth, GenericTreeNext node)  
{                                // a little one-line string-concatenation (n-times)
  Console.WriteLine("{0}{1}: {2}", String.Join("   ", new string[depth + 1]), depth, node.Name);  
}  

попробуйте этот простой пример.

public class TreeNode<TValue>
{
    #region Properties
    public TValue Value { get; set; }
    public List<TreeNode<TValue>> Children { get; private set; }
    public bool HasChild { get { return Children.Any(); } }
    #endregion
    #region Constructor
    public TreeNode()
    {
        this.Children = new List<TreeNode<TValue>>();
    }
    public TreeNode(TValue value)
        : this()
    {
        this.Value = value;
    }
    #endregion
    #region Methods
    public void AddChild(TreeNode<TValue> treeNode)
    {
        Children.Add(treeNode);
    }
    public void AddChild(TValue value)
    {
        var treeNode = new TreeNode<TValue>(value);
        AddChild(treeNode);
    }
    #endregion
}

создать узел класс что может быть полезно для других людей. Класс имеет такие свойства, как:

  • дети
  • предков
  • потомки
  • братья и сестры

поскольку это не упоминается, я хотел бы, чтобы вы обратили внимание на Теперь выпущенную .net code-base: в частности, код для SortedSet что реализует красно-черное дерево:

https://github.com/Microsoft/referencesource/blob/master/System/compmod/system/collections/generic/sortedset.cs

это, однако, сбалансированная древовидная структура. Поэтому мой ответ-это скорее Ссылка на то, что я считаю единственной родной древовидной структурой в библиотеке .net core.

Я выполнил код, которым поделился @Berezh.

  public class TreeNode<T> : IEnumerable<TreeNode<T>>
    {

        public T Data { get; set; }
        public TreeNode<T> Parent { get; set; }
        public ICollection<TreeNode<T>> Children { get; set; }

        public TreeNode(T data)
        {
            this.Data = data;
            this.Children = new LinkedList<TreeNode<T>>();
        }

        public TreeNode<T> AddChild(T child)
        {
            TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };
            this.Children.Add(childNode);
            return childNode;
        }

        public IEnumerator<TreeNode<T>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }
    }
    public class TreeNodeEnum<T> : IEnumerator<TreeNode<T>>
    {

        int position = -1;
        public List<TreeNode<T>> Nodes { get; set; }

        public TreeNode<T> Current
        {
            get
            {
                try
                {
                    return Nodes[position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }


        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }


        public TreeNodeEnum(List<TreeNode<T>> nodes)
        {
            Nodes = nodes;
        }

        public void Dispose()
        {
        }

        public bool MoveNext()
        {
            position++;
            return (position < Nodes.Count);
        }

        public void Reset()
        {
            position = -1;
        }
    }

вот дерево

public class Tree<T> : List<Tree<T>>
{
    public  T Data { get; private set; }

    public Tree(T data)
    {
        this.Data = data;
    }

    public Tree<T> Add(T data)
    {
        var node = new Tree<T>(data);
        this.Add(node);
        return node;
    }
}

вы даже можете использовать инициализаторы:

    var tree = new Tree<string>("root")
    {
        new Tree<string>("sample")
        {
            "console1"
        }
    };

большинство деревьев формируются данными, которые вы обрабатываете.

скажем, у вас есть person класс, включает в себя сведения о Кто-то parents, вы бы предпочли иметь древовидную структуру, как часть вашего "класс домена", или использовать отдельный класс дерева, содержащий ссылки на ваш человек возражает? Думать о простой операции, как получить все элемент grandchildren на person, должен ли этот код быть в person класс, или должен пользователь person класс должен знать о отдельный класс дерева?

другой пример-дерево синтаксического анализа в компиляторе...

оба этих примера показывают, что понятие дерева является частью домен данных и использование отдельного дерева общего назначения по крайней мере удваивает количество создаваемых объектов, а также делает API более сложным для повторного программирования.

то, что мы хотим, это способ повторного использования стандартных операций дерева, без необходимости повторно реализовать их для всех деревьев, и в то же время, не использовать стандартный класс дерева. Boost попытался решить эту проблему для C++, но мне еще предстоит увидеть какой-либо эффект для .NET, который будет адаптирован.

я добавил полное решение и пример, используя класс NTree выше, также добавил метод "AddChild"...

    public class NTree<T>
    {
        public T data;
        public LinkedList<NTree<T>> children;

        public NTree(T data)
        {
            this.data = data;
            children = new LinkedList<NTree<T>>();
        }

        public void AddChild(T data)
        {
            var node = new NTree<T>(data) { Parent = this };
            children.AddFirst(node);
        }

        public NTree<T> Parent { get; private set; }

        public NTree<T> GetChild(int i)
        {
            foreach (NTree<T> n in children)
                if (--i == 0)
                    return n;
            return null;
        }

        public void Traverse(NTree<T> node, TreeVisitor<T> visitor, string t, ref NTree<T> r)
        {
            visitor(node.data, node, t, ref r);
            foreach (NTree<T> kid in node.children)
                Traverse(kid, visitor, t, ref r);
        }
    }
    public static void DelegateMethod(KeyValuePair<string, string> data, NTree<KeyValuePair<string, string>> node, string t, ref NTree<KeyValuePair<string, string>> r)
    {
        string a = string.Empty;
        if (node.data.Key == t)
        {
            r = node;
            return;
        }
    }

используя

 NTree<KeyValuePair<string, string>> ret = null;
 tree.Traverse(tree, DelegateMethod, node["categoryId"].InnerText, ref ret);

вот мой:

class Program
{
    static void Main(string[] args)
    {
        var tree = new Tree<string>()
            .Begin("Fastfood")
                .Begin("Pizza")
                    .Add("Margherita")
                    .Add("Marinara")
                .End()
                .Begin("Burger")
                    .Add("Cheese burger")
                    .Add("Chili burger")
                    .Add("Rice burger")
                .End()
            .End();

        tree.Nodes.ForEach(p => PrintNode(p, 0));
        Console.ReadKey();
    }

    static void PrintNode<T>(TreeNode<T> node, int level)
    {
        Console.WriteLine("{0}{1}", new string(' ', level * 3), node.Value);
        level++;
        node.Children.ForEach(p => PrintNode(p, level));
    }
}

public class Tree<T>
{
    private Stack<TreeNode<T>> m_Stack = new Stack<TreeNode<T>>();

    public List<TreeNode<T>> Nodes { get; } = new List<TreeNode<T>>();

    public Tree<T> Begin(T val)
    {
        if (m_Stack.Count == 0)
        {
            var node = new TreeNode<T>(val, null);
            Nodes.Add(node);
            m_Stack.Push(node);
        }
        else
        {
            var node = m_Stack.Peek().Add(val);
            m_Stack.Push(node);
        }

        return this;
    }

    public Tree<T> Add(T val)
    {
        m_Stack.Peek().Add(val);
        return this;
    }

    public Tree<T> End()
    {
        m_Stack.Pop();
        return this;
    }
}

public class TreeNode<T>
{
    public T Value { get; }
    public TreeNode<T> Parent { get; }
    public List<TreeNode<T>> Children { get; }

    public TreeNode(T val, TreeNode<T> parent)
    {
        Value = val;
        Parent = parent;
        Children = new List<TreeNode<T>>();
    }

    public TreeNode<T> Add(T val)
    {
        var node = new TreeNode<T>(val, this);
        Children.Add(node);
        return node;
    }
}

выход:

Fastfood
   Pizza
      Margherita
      Marinara
   Burger
      Cheese burger
      Chili burger
      Rice burger

Если вы собираетесь отобразить это дерево на GUI, вы можете использовать TreeView и TreeNode. (Я полагаю, технически вы можете создать TreeNode, не помещая его в графический интерфейс, но у него есть больше накладных расходов, чем простая реализация доморощенного TreeNode.)

вот моя реализация BST

class BST
{
    public class Node
    {
        public Node Left { get; set; }
        public object Data { get; set; }
        public Node Right { get; set; }

        public Node()
        {
            Data = null;
        }

        public Node(int Data)
        {
            this.Data = (object)Data;
        }

        public void Insert(int Data)
        {
            if (this.Data == null)
            {
                this.Data = (object)Data;
                return;
            }
            if (Data > (int)this.Data)
            {
                if (this.Right == null)
                {
                    this.Right = new Node(Data);
                }
                else
                {
                    this.Right.Insert(Data);
                }
            }
            if (Data <= (int)this.Data)
            {
                if (this.Left == null)
                {
                    this.Left = new Node(Data);
                }
                else
                {
                    this.Left.Insert(Data);
                }
            }
        }

        public void TraverseInOrder()
        {
            if(this.Left != null)
                this.Left.TraverseInOrder();
            Console.Write("{0} ", this.Data);
            if (this.Right != null)
                this.Right.TraverseInOrder();
        }
    }

    public Node Root { get; set; }
    public BST()
    {
        Root = new Node();
    }
}

Если вам нужна реализация структуры данных корневого дерева, которая использует меньше памяти, вы можете написать свой класс узла следующим образом (реализация C++):

class Node {
       Node* parent;
       int item; // depending on your needs

       Node* firstChild; //pointer to left most child of node
       Node* nextSibling; //pointer to the sibling to the right
}

Comments

    Ничего не найдено.