291

I was looking for a tree or graph data structure in C#, but I guess there isn't one provided. An Extensive Examination of Data Structures Using C# 2.0 a bit about why. Is there a convenient library which is commonly used to provide this functionality? Perhaps through a strategy pattern to solve the issues presented in the article.

I feel a bit silly implementing my own tree, just as I would implementing my own ArrayList.

I just want a generic tree which can be unbalanced. Think of a directory tree. C5 looks nifty, but their tree structures seem to be implemented as balanced red-black trees better suited to search than representing a hierarchy of nodes.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
stimms
  • 42,945
  • 30
  • 96
  • 149
  • 2
    Bit more extreme trees: http://stackoverflow.com/questions/196294/what-is-a-catamorphism-and-can-it-be-implemented-in-c-sharp-3-0/11583895 ;-) – Tuomas Hietanen Jul 20 '12 at 17:14
  • Is there some reason one cannot include a TreeView in the project and use it? There is no reason to actually show it to a user. Of course there are several form of projects when this is not an option. One can always create new classes that inherit from example TreeNode if special complexity is needed? – Simply G. Oct 02 '12 at 13:16
  • 14
    I would consider it to be a bad idea to import an entire UI library for a very simple tree. – stimms Oct 03 '12 at 19:22
  • 2
    Could you motivate? Its not like actual harddrive space requirement is an issue anymore? Clumsy? As I mentioned before, I can understand that this is not a solution for an specialised software or something without an existing user interface. I am a lazy programmer, if I can get a structure for free its all good. And an existing library does have a lot for free, one can find a lot of code from people that used it for a lot of things. – Simply G. Oct 04 '12 at 10:49
  • 1
    I am not arguing, I just want to know your reasoning. – Simply G. Oct 04 '12 at 10:56
  • 4
    Here's a simple tree type: `public class Tree : List> { public T Value; }`. – Enigmativity Jan 08 '16 at 14:40
  • Have a look at https://www.codeproject.com/Articles/12476/A-Generic-Tree-Collection – cskwg May 02 '19 at 05:54
  • 1
    I would think the reasoning for not using a hidden tree control would be that it contributes to unnecessary bloat that impact not just space but memory and execution time. Don't drive a 747 if you need to cross the street, it might be quicker to walk. – Chad Nov 26 '19 at 00:15
  • 3
    Also, it might creates a lot of compatibility and maintenance issue. Your program is Windows only... just because you used some UI tree for winforms or WPF? What happens if you want to update your software, but you also depend on the (probably lots of) dependencies compatibility of the UI machinery? – Pac0 Jun 15 '20 at 08:13

20 Answers20

179

My best advice would be that there is no standard tree data structure because there are so many ways you could implement it that it would be impossible to cover all bases with one solution. The more specific a solution, the less likely it is applicable to any given problem. I even get annoyed with LinkedList - what if I want a circular linked list?

The basic structure you'll need to implement will be a collection of nodes, and here are some options to get you started. Let's assume that the class Node is the base class of the entire solution.

If you need to only navigate down the tree, then a Node class needs a List of children.

If you need to navigate up the tree, then the Node class needs a link to its parent node.

Build an AddChild method that takes care of all the minutia of these two points and any other business logic that must be implemented (child limits, sorting the children, etc.)

David Boike
  • 18,545
  • 7
  • 59
  • 94
  • 7
    personally i wouldn't mind some sort of self-balancing binary tree to be added to the library as this is some extra work than just using an adjaceny list. – jk. Jan 06 '10 at 13:00
  • 10
    @jk I believe that SortedDictionary and SortedSet are built atop red/black trees, so using these should work. – jonp Sep 24 '10 at 13:41
  • Take a look at composite pattern ;-) Exactly what you're looking for – Nicolas Voron Oct 12 '12 at 09:35
  • I've made a library that does everything you are saying can't be standardized. It simply wraps all of the extra bits that you are talking about so that you don't need to know how they are made. https://github.com/Unskilledcrab/Hierarchy. Cyclic data is also handled – Jeremy Buentello Apr 19 '22 at 15:07
138
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);
    }
}

Simple recursive implementation... < 40 lines of code... You just need to keep a reference to the root of the tree outside of the class, or wrap it in another class, maybe rename to TreeNode??

Steve Morgan
  • 12,978
  • 2
  • 40
  • 49
Aaron Gage
  • 2,373
  • 1
  • 16
  • 15
  • is there a reason for the linked list? I tend to use List or ObservableCollection. What do you get from using the linked list. As an aside, using this method, its really easy to build the Descendants() method on the tree that LINQ to XML has. This is very useful for organizing data based on a predicate. – Steve Jan 25 '10 at 15:39
  • 27
    In this case, in C# anyway, you could avoid writing your own delegate and use the pre-made `Action` delegate: `public void traverse(NTree node, Action visitor)` . Action<>'s signature is: `void Action( T obj )` . There are also versions from 0 to 4 different parameters. There's also an analogous delegate for functions called `Func<>`. – Benny Jobigan Feb 06 '10 at 23:45
  • 1
    Advantage of LinkedList is it is more efficient for the purposes we put it to here, and it only consumes as much memory as it needs for however many child nodes are stored. The only action that would be more efficient with an array based List implementation is the getChild(int), but I would expect that to be invoked sparingly, usually add and traverse will be used, for which LinkedList is ideally suited. Completing the implementation and adding in Remove may complicate things. Be nice if C#s generics allowed the user to specify the List implementation to best suit usage, but it doesnt. – Aaron Gage Mar 09 '10 at 04:30
  • It is a mixup of LinkedList and List, children is of type LinkedList but the methods Add or [] smells of List –  Aug 11 '10 at 13:25
  • You are correct, I just wrote the code straight into the SO editor. Now changed/corrected... (but essentially what an indexer implementation might look like anyway) – Aaron Gage Aug 27 '10 at 07:04
  • 2
    how would i call this delegate? – Freakishly Jan 17 '11 at 00:54
  • 3
    changing the traverse method to be static or possibly wrapping it to hide the recursive nature would be a good idea, but it is simple to traverse: create a method with the signature of delegate ie for a tree of ints: void my_visitor_impl(int datum) - make it static if you need, instantiate a delgate: TreeVisitor my_visitor = my_visitor_impl; and then invoke on the root node or NTree class if u make it static: NTree.traverse(my_tree, my_visitor) – Aaron Gage Jan 17 '11 at 07:01
  • 11
    Making addChild() return the NTree that it added would make it nicer for adding data to a tree. (Unless I'm missing a cunning way to build a tree with this, without relying on the implementation detail that a newly added child == getChild(1)?) – Rory Aug 16 '11 at 23:40
  • @Rory am I doing this right? public NTree AddChild(T data) { var childTree = new NTree(data); Children.AddFirst(childTree); return childTree; } – Stefan Anghel May 20 '14 at 16:17
  • @AaronGage - fancy making that change to your sample implementation? I'm sure tons of people use your sample verbatim. – Rory May 21 '14 at 17:09
  • I think I understand the search technique used in the `GetChild()` method described in this example, but I'm just wondering about the reason for implementing it in this "last-to-first" manner as it seems a little obscure to the untrained eye. Is it for performance reasons (i.e. decrementing loops) or is this just how tree navigation is conventionally defined, or is just a developer preference? – Matt Borja Nov 16 '15 at 18:32
  • @AaronGage RIP. The programming world lost a brilliant mind. – Arkiliknam Nov 25 '15 at 14:46
  • 1
    I think this statement `--i == 0` will be function only at single case? Is this true. This made me confuse – Waseem Ahmad Naeem Mar 05 '19 at 07:35
  • @WaseemAhmadNaeem I think it checks if i is zero with the side effect of changing i. – Justin Stafford Sep 18 '20 at 07:47
  • @Arkiliknam Did he die? :( – Vandrey Oct 22 '20 at 18:47
  • I honestly dont understand the need for the `Traverse(...)` method. Can someone please explain why I would need that method? Thank you! – Vandrey Oct 22 '20 at 18:48
  • 1
    @Sunburst275 You use Traverse() to "enumerate" or "navigate" all nodes in the tree (or in all children of a given node). If you use foreach directly you will see only the top-level nodes, not the children of these nodes and the children of the children, and so on. – Click Ok Apr 08 '22 at 19:00
  • The delegate can reference a function with the same return- and parameter-type. When the delegate gets called in the traverse method, your referenced function will be called. Therefore you could use that function to perform some kind of operation on the node data, when traversing the tree. – Zui0per Oct 14 '22 at 01:30
81

Here's mine, which is very similar to Aaron Gage's, just a little more conventional, in my opinion. For my purposes, I haven't ran into any performance issues with List<T>. It would be easy enough to switch to a LinkedList if needed.


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()));
        }
    }
}
Ronnie Overby
  • 45,287
  • 73
  • 267
  • 346
  • why is your Value property exposed when you're setting it in the constructor? that leaves it open for manipulation AFTER you've already set it via constructor right? Should be private set? – PositiveGuy Mar 29 '13 at 20:47
  • Sure, why not make it immutable? Edited. – Ronnie Overby Mar 30 '13 at 04:44
  • 1
    Thanks! I quite liked not having to write my own. (Still can't believe it isn't a thing that exists natively. I always thought .net, or at least .net 4.0, had *everything*.) – neminem May 14 '13 at 17:40
  • 4
    I liked this solution. I also found I needed to insert, I added the following method to do that. `public TreeNode InsertChild(TreeNode parent, T value) { var node = new TreeNode(value) { Parent = parent }; parent._children.Add(node); return node; }` `var five = myTree.AddChild(5); myTree.InsertChild(five, 55);` – JabberwockyDecompiler Oct 08 '15 at 16:21
  • 1
    This is an exceptional piece of code and in my opinion the best answer. Reading through it was a lecture in of itself. – Dean P May 04 '21 at 20:17
55

Yet another tree structure:

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
}

Sample usage:

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");
    }
}

BONUS
See fully-fledged tree with:

  • iterator
  • searching
  • Java/C#

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

Grzegorz Dev
  • 3,073
  • 1
  • 18
  • 22
  • How do I use the search in your code example? Where does `node` come from? Does it mean I have to iterate over the tree in order to use the search code? – BadmintonCat Oct 01 '15 at 16:25
  • @GrzegorzDev Maybe -1 because it does not implement all `IEnumerable<>` members, so it doesn't compile. – Uwe Keim May 03 '16 at 06:25
  • 3
    @UweKeim Good Job, next time try using the code with the actual usings. – szab.kel Aug 21 '17 at 13:05
  • only problem i see is that it will not be correctly serialized with basic JsonConvert as it implement IEnumerable<> – Rakiah Jun 18 '19 at 11:11
  • @Grzegorz Dev - Hi is there a way to get all the nodes at level two as a list of strings? – liv2hak Sep 17 '20 at 08:29
24

The generally excellent C5 Generic Collection Library has several different tree-based data structures, including sets, bags and dictionaries. Source code is available if you want to study their implementation details. (I have used C5 collections in production code with good results, although I haven't used any of the tree structures specifically.)

McKenzieG1
  • 13,960
  • 7
  • 36
  • 42
  • 7
    Don't know if maybe things have changed but right now the book is freely available to download as PDF from the C5 site. – Oskar Aug 06 '09 at 12:10
  • 4
    Lack of documentation is no more a concern as there's a 272 pages long pdf complementing the library... Can't comment on code quality, but judging from the doc quality, I'm really looking forward to digging into this tonight! – Florian Doyon Jan 25 '10 at 16:00
  • 2
    From what I understand, this C5 library doesn't have trees at all, but only some tree-derived data structures. – roim Aug 20 '14 at 12:38
11

See https://github.com/YaccConstructor/QuickGraph (previously http://quickgraph.codeplex.com/)

QuickGraph provides generic directed/undirected graph data structures and algorithms for .NET 2.0 and up. QuickGraph comes with algorithms such as depth-first search, breadth-first search, A* search, shortest path, k-shortest path, maximum flow, minimum spanning tree, least common ancestors, etc... QuickGraph supports MSAGL, GLEE, and Graphviz to render the graphs, serialization to GraphML, etc.

nietras
  • 3,949
  • 1
  • 34
  • 38
  • The QuickGraph link is broken: *"Hmm. We’re having trouble finding that site. We can’t connect to the server at quickgraph.codeplex.com."* – Peter Mortensen Dec 05 '21 at 17:36
10

Here's my own:

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;
    }
}

Output:

Fastfood
   Pizza
      Margherita
      Marinara
   Burger
      Cheese burger
      Chili burger
      Rice burger
moien
  • 999
  • 11
  • 26
9

I have a little extension to the solutions.

Using a recursive generic declaration and a deriving subclass, you can better concentrate on your actual target.

Notice, it’s different from a non generic implementation, you don’t need to cast 'node' to 'NodeWorker'.

Here's my example:

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);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Erik Nagel
  • 326
  • 4
  • 4
5

I created a Node<T> class that could be helpful for other people. The class has properties like:

  • Children
  • Ancestors
  • Descendants
  • Siblings
  • Level of the node
  • Parent
  • Root
  • Etc.

There is also the possibility to convert a flat list of items with an Id and a ParentId to a tree. The nodes holds a reference to both the children and the parent, so that makes iterating nodes quite fast.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex Siepman
  • 2,499
  • 23
  • 31
4

Try this simple sample.

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
}
Berezh
  • 942
  • 9
  • 12
2

There is the now released .NET codebase: specifically the code for a SortedSet that implements a red-black tree: sortedset.cs

This is, however, a balanced tree structure. So my answer is more a reference to what I believe is the only native tree-structure in the .NET core library.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
2

I've completed the code that Berezh has shared.

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;
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ashkan S
  • 10,464
  • 6
  • 51
  • 80
  • Good design. However I'm not sure if a node 'is' a sequence of its child node. I'd consider the following: a node 'has' zero or more child nodes, so a node is not derived from a sequence of child nodes, but it is an aggregation (composition?) of its child nodes – Harald Coppoolse Oct 12 '16 at 09:01
2

I have added a complete solution and example using the NTree class above. I also added the "AddChild" method...

    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;
        }
    }

Using it

 NTree<KeyValuePair<string, string>> ret = null;
 tree.Traverse(tree, DelegateMethod, node["categoryId"].InnerText, ref ret);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dmitry
  • 21
  • 1
2

There is also the possibility to use XML with LINQ:

Create XML tree in C# (LINQ to XML)

XML is the most mature and flexible solution when it comes to using trees and LINQ provides you with all the tools that you need. The configuration of your tree also gets much cleaner and user-friendly as you can simply use an XML file for the initialization.

If you need to work with objects, you can use XML serialization:

XML serialization

Florian Lavorel
  • 744
  • 2
  • 8
  • 14
1

Most trees are formed by the data you are processing.

Say you have a person class that includes details of someone’s parents, would you rather have the tree structure as part of your “domain class”, or use a separate tree class that contained links to your person objects? Think about a simple operation like getting all the grandchildren of a person, should this code be in the person class, or should the user of the person class have to know about a separate tree class?

Another example is a parse tree in a compiler…

Both of these examples show that the concept of a tree is part of the domain of the data and using a separate general-purpose tree at least doubles the number of objects that are created as well as making the API harder to program again.

We want a way to reuse the standard tree operations, without having to reimplement them for all trees, while at the same time, not having to use a standard tree class. Boost has tried to solve this type of problem for C++, but I am yet to see any effect for .NET to get it adapted.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
  • @Puchacz, sorry I am 15 years out of data on C++, have a look at Boost and Templates, after a few weak of study you may understand them. Power has high learning costs!! – Ian Ringrose Nov 18 '16 at 09:59
0

Here is my implementation of a 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();
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

If you are going to display this tree on the GUI, you can use TreeView and TreeNode. (I suppose technically you can create a TreeNode without putting it on a GUI, but it does have more overhead than a simple homegrown TreeNode implementation.)

Denise Skidmore
  • 2,286
  • 22
  • 51
-1

Tree With Generic Data

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class Tree<T>
{
    public T Data { get; set; }
    public LinkedList<Tree<T>> Children { get; set; } = new LinkedList<Tree<T>>();
    public Task Traverse(Func<T, Task> actionOnNode, int maxDegreeOfParallelism = 1) => Traverse(actionOnNode, new SemaphoreSlim(maxDegreeOfParallelism, maxDegreeOfParallelism));
    private async Task Traverse(Func<T, Task> actionOnNode, SemaphoreSlim semaphore)
    {
        await actionOnNode(Data);
        SafeRelease(semaphore);
        IEnumerable<Task> tasks = Children.Select(async input =>
        {
            await semaphore.WaitAsync().ConfigureAwait(false);
            try
            {
                await input.Traverse(actionOnNode, semaphore).ConfigureAwait(false);
            }
            finally
            {
                SafeRelease(semaphore);
            }
        });
        await Task.WhenAll(tasks);
    }
    private void SafeRelease(SemaphoreSlim semaphore)
    {
        try
        {
            semaphore.Release();
        }
        catch (Exception ex)
        {
            if (ex.Message.ToLower() != "Adding the specified count to the semaphore would cause it to exceed its maximum count.".ToLower())
            {
                throw;
            }
        }
    }

    public async Task<IEnumerable<T>> ToList()
    {
        ConcurrentBag<T> lst = new ConcurrentBag<T>();
        await Traverse(async (data) => lst.Add(data));
        return lst;
    }
    public async Task<int> Count() => (await ToList()).Count();
}



Unit Tests

using System.Threading.Tasks;
using Xunit;

public class Tree_Tests
{
    [Fact]
    public async Task Tree_ToList_Count()
    {
        Tree<int> head = new Tree<int>();

        Assert.NotEmpty(await head.ToList());
        Assert.True(await head.Count() == 1);

        // child
        var child = new Tree<int>();
        head.Children.AddFirst(child);
        Assert.True(await head.Count() == 2);
        Assert.NotEmpty(await head.ToList());

        // grandson
        child.Children.AddFirst(new Tree<int>());
        child.Children.AddFirst(new Tree<int>());
        Assert.True(await head.Count() == 4);
        Assert.NotEmpty(await head.ToList());
    }

    [Fact]
    public async Task Tree_Traverse()
    {
        Tree<int> head = new Tree<int>() { Data = 1 };

        // child
        var child = new Tree<int>() { Data = 2 };
        head.Children.AddFirst(child);

        // grandson
        child.Children.AddFirst(new Tree<int>() { Data = 3 });
        child.Children.AddLast(new Tree<int>() { Data = 4 });

        int counter = 0;
        await head.Traverse(async (data) => counter += data);
        Assert.True(counter == 10);

        counter = 0;
        await child.Traverse(async (data) => counter += data);
        Assert.True(counter == 9);

        counter = 0;
        await child.Children.First!.Value.Traverse(async (data) => counter += data);
        Assert.True(counter == 3);

        counter = 0;
        await child.Children.Last!.Value.Traverse(async (data) => counter += data);
        Assert.True(counter == 4);
    }
}

Bar Nuri
  • 762
  • 1
  • 9
  • 15
  • What unit test framework? [NUnit](https://en.wikipedia.org/wiki/NUnit)? – Peter Mortensen Dec 05 '21 at 20:02
  • An explanation would be in order. E.g., what is the idea/gist? What is the purpose of SafeRelease()? E.g., why is SafeRelease() necessary? Thread safety? What is the thinking behind the decision to use `async` and `await`? What is the minimum version of C# required? Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/64960280/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Dec 05 '21 at 20:05
-1

I don't like a tree aproach. It gets things overcomplicated including search or dril-down or even ui controls populating.

I would suggest to use a very simple approach with IDictionary<TChild, TParent>. This also allows to have no connections between nodes or levels.

ADM-IT
  • 3,719
  • 1
  • 25
  • 26
-3

In case you need a rooted tree data structure implementation that uses less memory, you can write your Node class as follows (C++ implementation):

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
}
Jake
  • 16,329
  • 50
  • 126
  • 202
  • 13
    Posting C++ code on a question specifically for C# is not the best idea, Jake. Especially one that includes pointers. You do know that pointers are being hunted down mercilessly in C#, right? :p – ThunderGr Oct 16 '13 at 11:40
  • 2
    @ThunderGr that is not fair. Answering in C# would have been better, but those C++ pointers can be understood by C#-speakers as references (they're less safe, ok). After David Boike, Aaron Gage, Ronnie Overby, Grzegorz Dev, Berezh and Erik Nagel all having sugested basically the same data structure with minor differences only in expression, Jake sugested breaking down the linked list yielding a simpler structures with only one type of node and sibling navigability. Don't express your dislike of C++ by down-voting a constructive answer. – migle May 20 '14 at 09:35
  • 3
    @migle I did not downvote the answer(did not upvote either). And I do not dislike C++. I saw that the answer was downvoted without anyone suggesting anything to Jake about why and how he would improve his answer. It is not about "being better". The question is tagged for C# only. Posting answers in another language than the tag is not recommended and some people will downvote. – ThunderGr May 20 '14 at 10:02