Structure des données arborescentes en C#
Je cherchais une structure de données d'arbre ou de graphique en C # mais je suppose qu'il n'y en a pas fourni. Un examen approfondi des Structures de données utilisant C # 2.0 explique un peu pourquoi. Existe-t-il une bibliothèque pratique couramment utilisée pour fournir cette fonctionnalité? Peut-être à travers un modèle de stratégie pour résoudre les problèmes présentés dans l'article.
Je me sens un peu idiot d'implémenter mon propre arbre, tout comme j'implémenterais ma propre ArrayList.
Je veux juste un arbre générique qui peut être déséquilibré. Pensez à une arborescence de répertoires. C5 semble astucieux, mais leurs structures arborescentes semblent être implémentées comme des arbres rouges-noirs équilibrés mieux adaptés à la recherche que représentant une hiérarchie de nœuds.
20 réponses
Mon meilleur conseil serait qu'il n'y a pas de structure de données arborescente standard car il y a tellement de façons de l'implémenter qu'il serait impossible de couvrir toutes les bases avec une solution. Plus une solution est spécifique, moins elle est susceptible d'être applicable à un problème donné. Je suis même ennuyé avec LinkedList-que faire si je veux une liste liée circulaire?
La structure de base que vous devrez implémenter sera une collection de nœuds, et voici quelques options pour vous aider à démarrer. Supposons que le nœud de classe est la classe de base de la solution entière.
Si vous devez uniquement naviguer dans l'arborescence, une classe Node a besoin d'une liste d'enfants.
Si vous devez naviguer dans l'arborescence, la classe Node a besoin d'un lien vers son nœud parent.
Construisez une méthode AddChild qui prend en charge toutes les minuties de ces deux points et toute autre logique métier qui doit être implémentée (limites enfants, tri des enfants, etc.)
Je déteste l'admettre mais j'ai fini par écrire ma propre classe d'arbre en utilisant une liste liée. Sur une note sans rapport, je viens de découvrir cette chose ronde qui, lorsqu'elle est attachée à une chose que j'appelle un "essieu", permet un transport plus facile des marchandises.
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);
}
}
Implémentation récursive Simple...
Voici le mien, qui est très similaire à D'Aaron Gage, juste un peu plus conventionnel, à mon avis. Pour mes besoins, Je n'ai rencontré aucun problème de performance avec List<T>
. Il serait assez facile de passer à une LinkedList si nécessaire.
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()));
}
}
}
Encore une autre structure arborescente:
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
}
Exemple d'utilisation:
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
Voir arbre à part entière avec:
- itérateur
- recherche
- Java / C #
La bibliothèque de Collection Générique C5 généralement excellente possède plusieurs structures de données arborescentes différentes, y compris des ensembles, des sacs et des dictionnaires. Le code Source est disponible si vous souhaitez étudier leurs détails d'implémentation. (J'ai utilisé des collections C5 dans le code de production avec de bons résultats, bien que je n'ai utilisé aucune des structures arborescentes spécifiquement.)
Voir http://quickgraph.codeplex.com/
QuickGraph fournit des structures de données et des algorithmes génériques pour.Net 2.0 et versions ultérieures. QuickGraph est livré avec des algorithmes tels que profondeur première seach, souffle première recherche, a * Recherche, chemin le plus court, K-chemin le plus court, débit maximum, minimum spanning tree, ancêtres les moins communs, etc... QuickGraph prend en charge MSAGL, GLEE et Graphviz pour rendre les graphiques, la sérialisation en GraphML, etc...
Si vous souhaitez écrire le vôtre, vous pouvez commencer par ce document en six parties détaillant l'utilisation efficace des structures de données C# 2.0 et comment analyser votre implémentation de structures de données en C#. Chaque article présente des exemples et un programme d'installation avec des échantillons que vous pouvez suivre.
"Un examen approfondi des Structures de données en utilisant C # 2.0" {[4] } par Scott Mitchell
J'ai une petite extension aux solutions.
En utilisant une déclaration Générique récursive et une sous-classe dérivante, vous pouvez mieux vous concentrer sur votre cible réelle.
Notez que c'est différent d'une implémentation non générique, vous n'avez pas besoin de lancer 'node' dans 'NodeWorker'.
Voici mon exemple:
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);
}
Essayez cet exemple simple.
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
}
Je crée une classe de noeud qui pourrait être utile pour d'autres personnes. La classe a des propriétés comme:
- Enfants
- Ancêtres
- Descendants
- frères et sœurs
- Niveau du noeud
- Parent
- Racine
- etc.
Il y a aussi la possibilité de convertir une liste plate d'éléments avec un Id et un ParentId en arbre. Les nœuds contiennent une référence à la fois aux enfants et au parent, ce qui rend les nœuds itératifs assez rapide.
Parce qu'il n'est pas mentionné, je voudrais que vous attiriez l'attention sur le code-base. NET maintenant publié: en particulier le code pour un {[0] } qui implémente un arbre rouge-noir:
Il s'agit cependant d'une structure arborescente équilibrée. Donc, ma réponse est plus une référence à ce que je crois être la seule structure arborescente native dans la bibliothèque. net core.
J'ai complété le code que @Berezh a partagé.
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;
}
}
Voici un Arbre
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;
}
}
Vous pouvez même utiliser des initialiseurs:
var tree = new Tree<string>("root")
{
new Tree<string>("sample")
{
"console1"
}
};
La plupart des arbres sont formés par les données que vous traitez.
Dites que vous avez une classe
person
qui inclut les détails de quelqu'unparents
, préféreriez-vous avoir la structure arborescente dans le cadre de votre "classe de domaine", ou utilisez une classe d'arborescence distincte contenant des liens vers votre personne s'oppose? Pensez à une opération simple comme obtenir tout legrandchildren
deperson
, ce code dans leperson
classe, ou l'utilisateur de la classeperson
doit-il connaître un séparé de l'arbre de classe?
Un autre exemple est un arbre d'analyse dans un compilateur ...
Ce que ces deux exemples montrent, c'est que le concept d'arbre fait partie du domaine des données et que l'utilisation d'un arbre général distinct double au moins le nombre d'objets créés et rend l'API plus difficile à programmer.
Ce que nous voulons, c'est un moyen de réutiliser les opérations d'arborescence standard, sans avoir à les ré-implémenter pour tous les arbres, tout en n'ayant pas pour utiliser une classe d'arborescence standard. Boost a essayé de résoudre ce type de problème pour C++, mais je n'ai pas encore vu d'effet pour.NET s'adapter.
J'ai ajouté une solution complète et un exemple en utilisant la classe NTree ci-dessus, a également ajouté la méthode "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;
}
}
En utilisant
NTree<KeyValuePair<string, string>> ret = null;
tree.Traverse(tree, DelegateMethod, node["categoryId"].InnerText, ref ret);
Voici mon propre:
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;
}
}
Sortie:
Fastfood
Pizza
Margherita
Marinara
Burger
Cheese burger
Chili burger
Rice burger
Si vous allez à l'affichage de cet arbre sur l'interface graphique, vous pouvez utiliser TreeView et TreeNode. (Je suppose que techniquement vous pouvez créer un TreeNode sans le mettre sur une interface graphique, mais il a plus de frais généraux qu'une simple implémentation de TreeNode locale.)
Voici mon implémentation DE 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();
}
}
Si vous avez besoin d'une implémentation de structure de données arborescente enracinée qui utilise moins de mémoire, vous pouvez écrire votre classe Node comme suit (implémentation 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
}