ChatGPT解决这个技术问题 Extra ChatGPT

Tree data structure in C#

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.

Bit more extreme trees: stackoverflow.com/questions/196294/… ;-)
I would consider it to be a bad idea to import an entire UI library for a very simple tree.
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.
Here's a simple tree type: public class Tree<T> : List<Tree<T>> { public T Value; }.
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?

D
David Boike

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.)


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 I believe that SortedDictionary and SortedSet are built atop red/black trees, so using these should work.
Take a look at composite pattern ;-) Exactly what you're looking for
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. github.com/Unskilledcrab/Hierarchy. Cyclic data is also handled
S
Steve Morgan
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??


In this case, in C# anyway, you could avoid writing your own delegate and use the pre-made Action<T> delegate: public void traverse(NTree<T> node, Action<T> visitor) . Action<>'s signature is: void Action<T>( T obj ) . There are also versions from 0 to 4 different parameters. There's also an analogous delegate for functions called Func<>.
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.
how would i call this delegate?
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)
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)?)
R
Ronnie Overby

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

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?
Sure, why not make it immutable? Edited.
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.)
I liked this solution. I also found I needed to insert, I added the following method to do that. public TreeNode<T> InsertChild(TreeNode<T> parent, T value) { var node = new TreeNode<T>(value) { Parent = parent }; parent._children.Add(node); return node; } var five = myTree.AddChild(5); myTree.InsertChild(five, 55);
This is an exceptional piece of code and in my opinion the best answer. Reading through it was a lecture in of itself.
G
Grzegorz Dev

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


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?
@GrzegorzDev Maybe -1 because it does not implement all IEnumerable<> members, so it doesn't compile.
@UweKeim Good Job, next time try using the code with the actual usings.
only problem i see is that it will not be correctly serialized with basic JsonConvert as it implement IEnumerable<>
@Grzegorz Dev - Hi is there a way to get all the nodes at level two as a list of strings?
M
McKenzieG1

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.)


Don't know if maybe things have changed but right now the book is freely available to download as PDF from the C5 site.
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!
From what I understand, this C5 library doesn't have trees at all, but only some tree-derived data structures.
n
nietras

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.


The QuickGraph link is broken: "Hmm. We’re having trouble finding that site. We can’t connect to the server at quickgraph.codeplex.com."
m
moien

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

P
Peter Mortensen

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

what is depth and where and how do you get it from?
@WeDoTDD.com looking at his class you see Traverse declares it as 0 to start at the root node, then uses the method traverse adding to that int each iteration.
How would you search the whole tree for a a particular node?
B
Berezh

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
}

P
Peter Mortensen

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.


P
Peter Mortensen

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.


P
Peter Mortensen

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

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
P
Peter Mortensen

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.


@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!!
P
Peter Mortensen

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

Should traverse maybe be a static method? It seems very awkward as a n instance method passing itself into itself
F
Florian Lavorel

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


This is a good opportunity to practice French, but perhaps provide the corresponding English ones as well?
D
Denise Skidmore

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.)


P
Peter Mortensen

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

B
Bar Nuri

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


What unit test framework? NUnit?
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, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).
A
ADM-IT

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.


J
Jake

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
}

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 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 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.