I found this question on SO about a tree implementation in C#. I have no idea about delegates and I was wondering how the following code could be used to implement a tree. Also, what would be the most efficient way to keep a track of parent nodes?
delegate void TreeVisitor<T>(T nodeData);
class NTree<T>
{
T data;
LinkedList<NTree<T>> children;
public NTree(T data)
{
this.data = data;
children = new LinkedList<NTree<T>>();
}
public void addChil开发者_JAVA百科d(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);
}
}
A delegate
basically lets you talk about an arbitrary function that meets some criteria, instead of having to know exactly what function you're talking about.
Consider the following code for traversing a tree, and performing an operation on each of the elements:
void DoSomethingToAllNodes(NTree<T> node)
{
DoSomething(node);
foreach (var child in node.Children)
DoSomethingToAllNodes(child);
}
It works, but it's pretty inflexible. We'd have to reimplement the method for each different operation we want to perform on the nodes:
void DoSomethingElseToAllNodes(NTree<T> node)
{
DoSomethingElse(node);
foreach (var child in node.Children)
DoSomethingElseToAllNodes(child);
}
Instead, we can declare a delegate to represent "Any method that takes one NTree<T>
as a parameter, and returns nothing", and then we can accept a method that satisfies that as an argument. We can then implement one single Traverse
method that handles every possibility, instead of having to reimplement it for each different operation.
If you're acquainted with c or c++ at all, a delegate can be thought of as a type-safe function pointer. That's how I've managed to get the idea of what a delegate is across to most people I've had this conversation with.
What your code does is declares a delegate
, which is basically a signature to a method. The traverse() method takes any function whose signature matches the delegate you've declared as a parameter and calls it. For fun and delight, everything is generic, to boot.
delegate void TreeVisitor<T>(T nodeData);
// This function takes a handle to a method as a parameter,
// and invokes that method for each node
public void traverse(NTree<T> node, TreeVisitor<T> visitor) {
visitor(node.data);
...
}
can be called like:
public class MyEvilPlans {
public void WreakHavoc<int>(int nodeData) {
Console.WriteLine("The secret to life is: {0}", nodeData.ToString());
}
public void PlayWithTree() {
NTree<int> tree = new NTree<int>();
// Initialize tree
// If the tree has 47 nodes, WreakHavoc will be called 47 times,
// once for each node in the tree.
tree.traverse(WreakHavoc);
}
}
The visitor pattern lets you decide what code is to be executed for each node of the tree... somebody else controls the implementation of tree, but you control the responses to tree behavior via a pre-defined contract (the delegate). In a way, it breaks encapsulation, because you know something about the inner details of the method you're calling: that it's going to call you back for each node. But that's what the visitor pattern is all about : )
To answer the second part of your question, how to keep track of the parent nodes... What you have is non-binary tree. To be able to traverse back up the tree from any node, you'll have to give each NTree a member of type NTree called 'parent'. This should be set by the constructor of an NTree, so that you have:
// Added the handle to 'this'
children.AddFirst(new NTree<T>(data, this));
You can find an excellent discussion of non-binary trees here.
精彩评论