it-swarm.it

Struttura dei dati dell'albero in C #

Stavo cercando una struttura dati di alberi o grafici in C # ma immagino che non ce ne sia uno fornito. Un esame approfondito delle strutture dati che utilizzano C # 2.0 spiega un po 'sul perché. Esiste una comoda libreria che viene comunemente utilizzata per fornire questa funzionalità? Forse attraverso un modello di strategia per risolvere i problemi presentati nell'articolo.

Mi sento un po 'sciocco implementando il mio stesso albero, proprio come implementerei la mia ArrayList.

Voglio solo un albero generico che può essere sbilanciato. Pensa a un albero di directory. C5 sembra elegante, ma le loro strutture ad albero sembrano essere implementate come alberi rosso-nero bilanciati più adatti alla ricerca che non a rappresentare una gerarchia di nodi.

228
stimms

Il mio miglior consiglio sarebbe che non esiste una struttura dati standard per gli alberi perché ci sono così tanti modi in cui è possibile implementarli che sarebbe impossibile coprire tutte le basi con una soluzione. Più una soluzione è specifica, meno è probabile che sia applicabile a un determinato problema. Mi infastidisce persino con LinkedList - cosa succede se voglio una lista circolare circolare?

La struttura di base che dovrai implementare sarà una raccolta di nodi, e qui ci sono alcune opzioni per iniziare. Supponiamo che il codice classe sia la classe base dell'intera soluzione.

Se è necessario navigare solo lungo la struttura, una classe Node ha bisogno di una lista di bambini.

Se è necessario navigare nell'albero, la classe Node necessita di un collegamento al suo nodo genitore.

Costruire un metodo AddChild che si occupi di tutti i minimi dettagli di questi due punti e di qualsiasi altra logica aziendale che deve essere implementata (limiti figlio, ordinamento dei bambini, ecc.)

141
David Boike

Odio ammetterlo ma alla fine ho scritto la mia classe di albero usando una lista collegata. Su una nota non correlata ho appena scoperto questa cosa rotonda che, quando collegata a una cosa che chiamo un "asse", consente un trasporto più facile delle merci.

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

Semplice implementazione ricorsiva ... <40 linee di codice ... Hai solo bisogno di mantenere un riferimento alla radice dell'albero al di fuori della classe, o avvolgerlo in un'altra classe, magari rinominare in TreeNode ??

112
Aaron Gage

Ecco il mio, che è molto simile a di Aaron Gage , solo un po 'più convenzionale, a mio parere. Per i miei scopi, non ho riscontrato problemi di prestazioni con List<T>. Sarebbe abbastanza facile passare a una LinkedList se necessario.


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()));
        }
    }
}
49
Ronnie Overby

Ancora un'altra struttura ad albero:

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
}

Esempio di utilizzo:

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
Vedi albero a tutti gli effetti con:

  • iteratore
  • ricerca
  • Java/C #

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

40
Grzegorz Dev

La libreria di raccolta generica C5 generalmente eccellente ha diverse strutture di dati ad albero, inclusi insiemi, borse e dizionari. Il codice sorgente è disponibile se vuoi studiare i dettagli dell'implementazione. (Ho utilizzato le raccolte C5 nel codice di produzione con buoni risultati, anche se non ho usato nessuna delle strutture ad albero in particolare.)

22
McKenzieG1

Vedi http://quickgraph.codeplex.com/

QuickGraph fornisce strutture e algoritmi grafici generici diretti/indiretti per .Net 2.0 e versioni successive. QuickGraph viene fornito con algoritmi come profondità prima seach, breath first search, A * search, percorso più breve, k-shortest path, massimo flusso, spanning tree minimo, antenati meno comuni, ecc ... QuickGraph supporta MSAGL, GLEE e Graphviz per renderizza grafici, serializzazione in GraphML, ecc ...

10
nietras

Se vuoi scrivere da solo, puoi iniziare con questo documento in sei parti che dettaglia l'uso effettivo delle strutture di dati C # 2.0 e come analizzare l'implementazione delle strutture di dati in C #. Ogni articolo ha esempi e un programma di installazione con campioni che puoi seguire insieme.

"Un vasto esame di strutture dati con C # 2.0" di Scott Mitchell

7
user7116

Ho una piccola estensione alle soluzioni.

Usando una dichiarazione generica ricorsiva e una sottoclasse derivante puoi concentrarti meglio sul tuo obiettivo reale.

Si noti che è diverso da un'implementazione non generica, non è necessario eseguire il cast del nodo in NodeWorker.

Ecco il mio esempio:

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
Erik Nagel

Prova questo semplice esempio.

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
Berezh

Creo una classe Node che potrebbe essere utile per altre persone. La classe ha proprietà come:

  • Bambini
  • Antenati
  • Discendenti
  • Fratelli
  • Livello del nodo
  • Genitore
  • Root
  • Eccetera.

C'è anche la possibilità di convertire un elenco di elementi con un ID e un ParentId in un albero. I nodi contengono un riferimento sia ai bambini che ai genitori, in modo tale da rendere l'iterazione dei nodi abbastanza veloce.

2
Alex Siepman

Poiché non è menzionato, vorrei richiamare l'attenzione sul code-base .net ora rilasciato: in particolare il codice per un SortedSet che implementa un Red-Black-Tree:

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

Questa è, tuttavia, una struttura ad albero equilibrata. Quindi la mia risposta è più un riferimento a ciò che ritengo sia l'unica struttura ad albero nativa nella libreria principale .net.

2
Meirion Hughes

Ecco un albero

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

Puoi persino usare gli inizializzatori:

    var tree = new Tree<string>("root")
    {
        new Tree<string>("sample")
        {
            "console1"
        }
    };
2
Visar

Ecco il mio:

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

Produzione:

Fastfood
   Pizza
      Margherita
      Marinara
   Burger
      Cheese burger
      Chili burger
      Rice burger
2
moien

Ho completato il codice che @Berezh ha condiviso.

  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
Ashkan Sirous

La maggior parte degli alberi sono formati dai dati che stai elaborando.

Supponiamo che tu abbia una classe person che include i dettagli del parents di qualcuno, preferiresti avere la struttura ad albero come parte della tua "classe di dominio", o usare una classe di albero separata che contenesse collegamenti agli oggetti della tua persona? Pensa a una semplice operazione come ottenere tutto il grandchildren di un person, dovrebbe questo codice essere nella classe person, o l'utente della classe person deve conoscere una classe di albero separata?

Un altro esempio è un albero di analisi in un compilatore ...

Ciò che entrambi questi esempi mostrano è che il concetto di un albero fa parte del dominio dei dati e utilizza un albero separato per scopi generali che raddoppia almeno il numero di oggetti che vengono creati e rendendo l'API più difficile da programmare nuovamente.

Quello che vogliamo è un modo per riutilizzare le operazioni dell'albero standard, senza doverle implementare nuovamente per tutti gli alberi, mentre allo stesso tempo, non è necessario utilizzare una classe di albero standard. Boost ha provato a risolvere questo tipo di problema per C++, ma non vedo ancora alcun effetto per .NET.

1
Ian Ringrose

Ho aggiunto la soluzione completa e l'esempio usando la classe NTree sopra, ho aggiunto anche il metodo "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;
        }
    }

utilizzando

 NTree<KeyValuePair<string, string>> ret = null;
 tree.Traverse(tree, DelegateMethod, node["categoryId"].InnerText, ref ret);
1
Dmitry

Se si intende visualizzare questo albero sulla GUI, è possibile utilizzare TreeView e TreeNode . (Suppongo tecnicamente che tu possa creare un TreeNode senza metterlo su una GUI, ma ha un overhead più di una semplice implementazione TreeNode homegrown).

0
Denise Skidmore