Question

Je cherchais une structure de données arborescente ou graphique en C #, mais je suppose qu’il n’en existe pas. Un examen approfondi des structures de données à l'aide de 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 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.

Était-ce utile?

La solution

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'ennuie même - et si je veux une liste chaînée circulaire?

La structure de base que vous devrez implémenter sera une collection de nœuds. Voici quelques options pour vous aider à démarrer. Supposons que la classe Node soit la classe de base de la solution entière.

Si vous ne devez parcourir que l'arborescence, une classe de nœuds nécessite 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 devant être implémentée (limitation du nombre d'enfants, tri des enfants, etc.)

Autres conseils

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

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 l'envelopper dans une autre classe, peut-être renommer TreeNode?

Voici le mien, qui est très similaire à celui de Aaron Gage , un peu plus conventionnel, à mon avis. Pour mes besoins, 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 un arbre à part entière avec:

  • itérateur
  • recherche
  • Java / C #

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

L'excellente bibliothèque de collections génériques C5 comprend plusieurs structures de données arborescentes, notamment: ensembles, sacs et dictionnaires. Le code source est disponible si vous souhaitez étudier les détails de leur implémentation. (J'ai utilisé les collections C5 dans le code de production avec de bons résultats, bien que je n'ai utilisé aucune des arborescences en particulier.)

Voir http://quickgraph.codeplex.com/

QuickGraph fournit des algorithmes et des infrastructures de données 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.

& # 8220; Un examen approfondi des données Structures utilisant C # 2.0 & # 8221; 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.

Notez que 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 nœud classe qui pourrait être utile pour d’autres personnes. La classe a des propriétés telles que:

  • Enfants
  • ancêtres
  • Descendants
  • frères et soeurs
  • Niveau du noeud
  • parent
  • racine
  • Etc.

Il est également possible de convertir une liste non hiérarchique d’articles avec un identifiant et un identifiant parent en un 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 rapides.

Parce que cela n’est pas mentionné, je voudrais attirer votre attention sur la base de code .net publiée: en particulier le code pour un SortedSet qui implémente un arbre rouge-noir:

https: // github. com / Microsoft / referencesource / blob / maître / Système / compmod / système / collections / generic / sortedset.cs

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.

J'ai complété le code partagé par @Berezh.

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

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

La plupart des arbres sont formés par les données que vous traitez.

  

Disons que vous avez une classe person qui inclut les détails de quelqu'un & # 8217; s   parents, préféreriez-vous que l’arborescence fasse partie de votre   & # 8220; classe de domaine & # 8221 ;, ou utilisez une classe d'arborescence séparée contenant des liens vers   votre personne s'objecte? Pensez à une opération simple comme obtenir tous   le grandchildren d'un <=>, ce code doit-il figurer dans la <=>   classe, ou l’utilisateur de la <=> classe doit-il connaître une   classe d'arbre séparée?

Un autre exemple est un arbre d'analyse dans un compilateur & # 8230;

Ces deux exemples montrent que le concept d'arborescence fait partie du domaine des données et que l'utilisation d'un arborescence à usage général distinct double au moins le nombre d'objets créés. rendant l’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, tout en n’ayant pas à utiliser une classe d’arborescence standard. Boost a essayé de résoudre ce type de problème en C ++, mais je n’ai encore trouvé aucun effet pour .NET.

J'ai ajouté la solution complète et l'exemple utilisant la classe NTree ci-dessus, ainsi que & "AddChild &"; méthode ...

    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 dans une simple implémentation TreeNode locale.)

Voici ma mise en œuvre de la 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 d'arborescence 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
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top