Je cherchais une structure de données arborescente ou graphique en C #, mais je suppose qu'il n'en existe aucune. n examen approfondi des structures de données en utilisant C # 2. 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 bête de mettre en œuvre mon propre arbre, tout comme je mettrais en œuvre mon propre ArrayList.
Je veux juste un arbre générique qui peut être déséquilibré. Pensez à une arborescence de répertoires. C5 a l'air chouette, mais leurs structures arborescentes semblent être implémentées comme des arbres équilibrés rouge-noir, mieux adaptés à la recherche que de représenter une hiérarchie de noeuds.
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 la mettre en œuvre qu'il est impossible de couvrir toutes les bases avec une seule solution. Plus une solution est spécifique, moins elle est applicable à un problème donné. LinkedList m'énerve même - et si je veux une liste chaînée circulaire?
La structure de base que vous devrez implémenter sera un ensemble de nœuds. Voici quelques options pour vous aider à démarrer. Supposons que la classe Node est la classe de base de la solution entière.
Si vous ne devez parcourir que l'arborescence, une classe Node nécessite une liste d'enfants.
Si vous devez naviguer dans l'arborescence, la classe Node nécessite 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 à implémenter (limite d'enfant, tri des enfants, etc.)
Je déteste l'admettre, mais j'ai fini par écrire ma propre classe d'arborescence à l'aide d'une liste chaînée. Sur une note distincte, je viens de découvrir cette chose ronde qui, attachée à une chose que j'appelle un "essieu", facilite le transport 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);
}
}
Une implémentation récursive simple ... <40 lignes de code ... Il vous suffit de garder une référence à la racine de l'arbre en dehors de la classe ou de l'envelopper dans une autre classe, éventuellement renommer TreeNode?
Voici le mien, qui est très similaire à Aaron Gage , juste un peu plus conventionnel, à mon avis. Pour ce qui me concerne, je n’ai rencontré aucun problème de performances avec List<T>
. Il serait assez facile de passer à une liste liée 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 arborescence:
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:
L’excellent généralement C5 Generic Collection Library possède plusieurs structures de données arborescentes, notamment des ensembles, des sacs et des dictionnaires. Le code source est disponible si vous souhaitez étudier leurs détails de mise en œuvre. (J'ai utilisé des collections C5 dans le code de production avec de bons résultats, bien que je n'ai utilisé aucune des arborescences de manière spécifique.)
Voir http://quickgraph.codeplex.com/
QuickGraph fournit des infrastructures de données et des algorithmes de graphes génériques dirigés/non dirigés pour .Net 2.0 et versions ultérieures. QuickGraph est livré avec des algorithmes tels que recherche de profondeur d'abord, recherche de souffle d'abord, recherche A *, chemin le plus court, k-chemin le plus court, débit maximum, arbre de recouvrement minimal, ancêtres les moins communs, etc ... QuickGraph prend en charge MSAGL, GLEE et Graphviz pour rendre les graphes, la sérialisation en GraphML, etc ...
Si vous souhaitez écrire le vôtre, vous pouvez commencer par ce document en six parties, qui détaille l'utilisation efficace des structures de données C # 2.0 et explique comment analyser l'implémentation des structures de données en C #. Chaque article contient des exemples et un programme d'installation avec des exemples que vous pouvez suivre.
“Un examen approfondi des structures de données utilisant C # 2.0” 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érivée, vous pouvez mieux vous concentrer sur votre cible réelle.
Remarquez, c'est différent d'une implémentation non générique, vous n'avez pas besoin de transtyper 'noeud' 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 un classe de nœuds qui pourrait être utile pour d'autres personnes. La classe a des propriétés comme:
Il existe également la possibilité de convertir une liste simple d'éléments avec un ID et un ParentId en un arbre. Les nœuds contiennent une référence à la fois aux enfants et au parent, ce qui rend l'itération des nœuds très rapide.
Parce que cela n'est pas mentionné, j'aimerais attirer votre attention sur la base de code .net maintenant publiée: spécifiquement le code pour un SortedSet
qui implémente un arbre rouge-noir:
Il s’agit toutefois d’une arborescence équilibrée. Ma réponse est donc plutôt une référence à ce que je crois être la seule structure arborescente native de la bibliothèque principale .net.
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"
}
};
Voici le mien:
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
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;
}
}
La plupart des arbres sont formés par les données que vous traitez.
Supposons que vous ayez une classe
person
comprenant des détails sur la personneparents
, préféreriez-vous que l'arborescence fasse partie de votre "classe de domaine" ou utilisez une classe d'arborescence séparée contenant des liens vers vos objets personne ? Pensez à une opération simple, comme obtenir tous lesgrandchildren
d'unperson
, si ce code devait être dans la classeperson
ou si l'utilisateur de la classeperson
devait le savoir. sur une classe d'arbre séparée?
Un autre exemple est un arbre d'analyse dans un compilateur…
Ces deux exemples montrent que le concept d’arbre fait partie du domaine des données et que l’utilisation d’un arbre distinct à des fins générales double au moins le nombre d’objets créés et rend le API plus difficile à programmer à nouveau.
Ce que nous voulons, c'est un moyen de réutiliser les opérations d'arborescence standard, sans avoir à les réimplémenter pour toutes les arborescences, sans avoir à utiliser une classe d'arborescence standard. Boost a essayé de résoudre ce type de problème en C++, mais je n’ai encore vu aucun effet pour .NET s’adapter.
J'ai ajouté la solution complète et l'exemple utilisant la classe NTree ci-dessus, ainsi que 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);
Si vous souhaitez afficher cette arborescence sur l'interface graphique, vous pouvez utiliser TreeView et TreeNode . (Je suppose que techniquement, vous pouvez créer un TreeNode sans le placer dans une interface graphique, mais cela entraîne plus de temps système que celui d'une simple implémentation TreeNode locale.)