Estructura de datos en árbol en C#


Estaba buscando una estructura de datos de árbol o gráfico en C# pero supongo que no hay uno proporcionado. Un Examen Extenso de las Estructuras de Datos Usando C# 2.0 explica un poco sobre por qué. ¿Hay una biblioteca conveniente que se utiliza comúnmente para proporcionar esta funcionalidad? Tal vez a través de un patrón de estrategia para resolver los problemas presentados en el artículo.

Me siento un poco tonto implementando mi propio árbol, al igual que implementaría mi propio ArrayList.

Solo quiero un árbol genérico que puede estar desequilibrado. Piense en un árbol de directorios. C5 parece ingenioso, pero sus estructuras de árboles parecen implementarse como árboles rojos-negros equilibrados más adecuados para la búsqueda que representar una jerarquía de nodos.

Author: Tim B, 2008-09-16

20 answers

Mi mejor consejo sería que no hay una estructura de datos de árbol estándar porque hay tantas maneras de implementarla que sería imposible cubrir todas las bases con una sola solución. Cuanto más específica sea una solución, menos probable será que sea aplicable a cualquier problema dado. Incluso me molesto con LinkedList - ¿qué pasa si quiero una lista circular vinculada?

La estructura básica que necesitará implementar será una colección de nodos, y aquí hay algunas opciones para comenzar. Supongamos que el Nodo de clase es la clase base de toda la solución.

Si solo necesita navegar por el árbol, entonces una clase de nodo necesita una Lista de hijos.

Si necesita navegar por el árbol, entonces la clase Node necesita un enlace a su nodo padre.

Construya un método addChild que se encargue de todas las minucias de estos dos puntos y cualquier otra lógica de negocio que deba implementarse (límites hijos, clasificación de los hijos, etc.).)

 136
Author: David Boike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-15 21:04:39

Odio admitirlo, pero terminé escribiendo mi propia clase de árbol usando una lista vinculada. En una nota no relacionada acabo de descubrir esta cosa redonda que, cuando se une a una cosa que estoy llamando un " eje " permite un transporte más fácil de mercancías.

 186
Author: stimms,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-16 03:30:39
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);
    }
}

Implementación recursiva simple...

 106
Author: Aaron Gage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-30 13:36:27

Aquí está el mío, que es muy similar al de Aaron Gage, solo un poco más convencional, en mi opinión. Para mis propósitos, no me he topado con ningún problema de rendimiento con List<T>. Sería bastante fácil cambiar a una lista de enlaces si es necesario.


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()));
        }
    }
}
 46
Author: Ronnie Overby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-10 15:20:15

Otra estructura de árbol:

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
}

Uso de la muestra:

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
Ver árbol de pleno derecho con:

  • iterador
  • buscando
  • Java/C #

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

 34
Author: Grzegorz Dev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-05 15:12:42

La generalmente excelente C5 Generic Collection Library tiene varias estructuras de datos diferentes basadas en árboles, incluidos conjuntos, bolsas y diccionarios. El código fuente está disponible si desea estudiar los detalles de su implementación. (He utilizado colecciones C5 en código de producción con buenos resultados, aunque no he utilizado ninguna de las estructuras de árbol específicamente.)

 21
Author: McKenzieG1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-15 21:01:58

Véase http://quickgraph.codeplex.com /

QuickGraph proporciona estructuras de datos y algoritmos genéricos dirigidos/no dirigidos para.Net 2.0 y versiones posteriores. QuickGraph viene con algoritmos como profundidad primero seach, respiración primero búsqueda, búsqueda A*, camino más corto, k-camino más corto, flujo máximo, árbol de expansión mínimo, antepasados menos comunes, etc... QuickGraph soporta MSAGL, GLEE y Graphviz para renderizar los gráficos, serialización a GraphML, etc...

 10
Author: nietras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-25 15:34:10

Si desea escribir el suyo propio, puede comenzar con este documento de seis partes que detalla el uso efectivo de las estructuras de datos de C# 2.0 y cómo analizar su implementación de estructuras de datos en C#. Cada artículo tiene ejemplos y un instalador con muestras que puede seguir junto con.

"Un Examen Extenso de Estructuras de Datos Usando C# 2.0" por Scott Mitchell

 7
Author: user7116,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-15 21:11:34

Tengo una pequeña extensión de las soluciones.

Usando una declaración recursiva genérica y una subclase derivada puedes concentrarte mejor en tu objetivo real.

Observe que es diferente de una implementación no genérica, no necesita convertir 'node' en 'NodeWorker'.

Aquí está mi ejemplo:

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);  
}  
 6
Author: Erik Nagel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-28 17:06:30

Pruebe esta simple muestra.

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
}
 4
Author: Berezh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-27 00:10:53

Creo una clase de nodo que podría ser útil para otras personas. La clase tiene propiedades como:

  • Niños
  • Ancestros
  • Descendientes
  • Hermanos
  • Nivel del nodo
  • Padre
  • Raíz
  • Etc.

También existe la posibilidad de convertir una lista plana de elementos con un Id y un ParentId en un árbol. Los nodos contienen una referencia tanto a los hijos como al padre, por lo que hace que los nodos iterantes sean bastante rápida.

 2
Author: Alex Siepman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-01 13:32:58

Debido a que no se menciona, me gustaría llamar la atención sobre el código base. net ahora publicado: específicamente el código para un SortedSet que implementa un Árbol Rojo-Negro:

Https://github.com/Microsoft/referencesource/blob/master/System/compmod/system/collections/generic/sortedset.cs

Esto es, sin embargo, una estructura de árbol equilibrada. Así que mi respuesta es más una referencia a lo que creo que es la única estructura de árbol nativa en la biblioteca.net core.

 2
Author: Meirion Hughes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-27 09:51:56

He completado el código que @Berezh ha compartido.

  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;
        }
    }
 2
Author: Ashkan Sirous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-24 18:46:50

Aquí hay un Árbol

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

Incluso puedes usar inicializadores:

    var tree = new Tree<string>("root")
    {
        new Tree<string>("sample")
        {
            "console1"
        }
    };
 2
Author: Visar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-29 07:48:16

La mayoría de los árboles están formados por los datos que está procesando.

Digamos que tienes una clase person que incluye detalles de la parents, preferiría tener la estructura de árbol como parte de su "clase de dominio", o usar una clase de árbol separada que contenga enlaces a ¿tu persona objeta? Piense en una operación simple como conseguir todo el grandchildren de un person, si este código está en el person clase, o si el usuario de la clase person tiene que saber acerca de un árbol separado la clase?

Otro ejemplo es un árbol de análisis en un compilador {

Lo que ambos ejemplos muestran es que el concepto de árbol es parte del dominio de los datos y el uso de un árbol de propósito general separado al menos duplica el número de objetos que se crean, así como hace que la API sea más difícil de programar de nuevo.

Lo que queremos es una forma de reutilizar las operaciones de árbol estándar, sin tener que volver a implementarlas para todos los árboles, mientras que al mismo tiempo, no tener usar una clase de árbol estándar. Boost ha intentado resolver este tipo de problema para C++, pero aún no veo ningún efecto para.NET que se adapte.

 1
Author: Ian Ringrose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-11 11:00:14

He añadido una solución completa y un ejemplo usando la clase NTree anterior, también he añadido el método "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;
        }
    }

Usando

 NTree<KeyValuePair<string, string>> ret = null;
 tree.Traverse(tree, DelegateMethod, node["categoryId"].InnerText, ref ret);
 1
Author: Dmitry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-13 05:09:47

Aquí está el mío:

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

Salida:

Fastfood
   Pizza
      Margherita
      Marinara
   Burger
      Cheese burger
      Chili burger
      Rice burger
 1
Author: moien,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-25 06:46:08

Si vas a mostrar este árbol en la GUI, puedes usar TreeView y TreeNode. (Supongo que técnicamente puedes crear un TreeNode sin ponerlo en una GUI, pero tiene más sobrecarga que una simple implementación de TreeNode de cosecha propia.)

 0
Author: Denise Skidmore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-04 18:18:56

Aquí está mi implementación 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();
    }
}
 -1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-18 08:05:43

En caso de que necesite una implementación de estructura de datos de árbol arraigado que use menos memoria, puede escribir su clase de nodo de la siguiente manera (implementación de 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
}
 -3
Author: Jake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-15 03:18:30