Eleganter Baum-Entwurf



  • Hallo!

    Ich habe ein kleines Problem. Ich möchte eine Baumstruktur erstellen.
    Diese ergibt sich aus meinen Objekten: ein A hat mehrere Bs, ein B hat
    mehrere Cs. Im Prinzip, wäre es sinnvoll, wenn man einfach Zeigerarrays
    auf die verbundenen Objekte anlegt (A hat ein B-Array usw.).
    Nun möchte ich aber in dem entstehenden Baum ein wenig rumforsten und
    traversieren, kopieren, umhängen...
    Da fehlt ein Elternzeiger - also rein damit: B zeig jetzt auch auf den
    Elternknoten A. Leider heißen die Funktionen in jeder Ebene anders: B hat
    ein A *getA() und ein C *getC(int i). Also habe ich mir gedacht, leite ich
    alle baumfähigen Klassen von TreeNode ab, damit ich einheitlich mit
    getParent() und mit getChild(int i) sprechen kann. Diese verweisen
    lediglich auf die "Original-Funktionen".
    Was mich etwas stört, ist, daß getParent() und getChild() immer "typlos"
    daherkommen, und ich im Zweifelsfall erst ein A,B,C zurechtcasten muß.

    So richtig glücklich bin ich damit noch nicht. Ich möchte diese Typlosigkeit
    besser verbergen. Kann mir vielleicht ein Iterator weiterhelfen?
    Der untenstehende Code ist nur so als Skizze gedacht. Das werden dann
    später sicher dynamische Arrays mit Smart-Pointern und Klasseninformationen
    (evtl. QT,Boost oder sowas). Die Bäume sollen serialisiert werden usw.

    Ich wäre für Verbesserungsvorschläge/Kritik sehr dankbar.

    Viele Grüße,
    Ulf

    class TreeNode // Basisklasse von A,B und C
    {
    public:
    	virtual TreeNode *getParent() = 0;          // wird durch 
    	virtual TreeNode *getChild(int index) = 0;  // Zeiger auf Papa
    };
    
    class A : public TreeNode  // "Wurzel"
    {
    public:
    	B *getB(int index) { return b_vector[index]; }
    	TreeNode *getParent() { return NULL; }		// NULL, weil Wurzel
    	TreeNode *getChild(int index) { return getB(index); } // weiterleiten
    protected:
    	vector<B*> b_vector; // children
    };
    
    class B : public TreeNode // "mittlere Ebene"
    {
    public:
    	A *getA() { return a; } // parent
    	C *getC(int index) { return c_vector[index]; } // children
    	TreeNode *getParent() { return getA(); }			  // weiterleiten
    	TreeNode *getChild(int index) { return getC(index); } // weiterleiten
    protected:
    	A* a;				 // parent
    	vector<C*> c_vector; // children
    };
    
    class C : public TreeNode  // "Blätter"
    {
    	B *getB() { return b; } // parent
    	TreeNode *getParent() { return getB(); }	 // weiterleiten
    	TreeNode *getChild(int index) { return NULL; } // NULL, weil Blatt
    protected:
    	B* b; // parent
    };
    


  • So eine gemeinsame Basisklasse ist nur dann wirklich zu gebrauchen, wenn du die Objekte (Wurzel, Äste, Blätter) gleichartig behandeln willst - willst du das? Bei deiner dreischichtigen Struktur ist es wahrscheinlich sicherer, doch diekt mit den spezifischen Get.-Methoden zu arbeiten.

    alternativ würde mir noch ein Einsatz mit Templates einfallen:

    template<typename p,typename c> class node
    {
    public:
      virtual p* getParent() = 0;
      virtual c* getChild(int) = 0;
    };
    
    class A;
    class B;
    class C;
    
    class A : public node<void,B>
    {
    public:
      void* getParant() {return 0;}
      B* getChild(int i) {return m_bs[i];}
    private:
      vector<B*> m_bs;
    };
    
    class B : public node<A,C>
    {...};
    
    class C : public node<B,void>
    {...};
    

    (Achtung: ungetestet)



  • warum unterscheidest du die blätter im voraus in wurzel, knoten und blatt? was ist, wenn sich der baum ändert, dann müsstest du die entsprechenden elemente umbauen.

    für nen baum reicht im grunde eine sehr einfache struktur

    template<typename Type>
    class Node
    {
    private:
     Type payload;
     Node *parent;
     std::vector<Node*> children;
    public:
     // getter & setter
    }
    

    die wurzel definiert sich dadurch, dass sie keinen parent hat, die blätter durch nicht vorhandene kinder.



  • Das habe ich vielleicht etwas unglücklich dargestellt.

    Der Baum im Beispiel hat nur drei Ebenen (Wurzel,"Äste",Blätter).
    Im Prinzip darf der Baum beliebig viele Ebenen haben, auf jeder Ebene
    ist dann aber genau ein Typ.
    Es geht auch nicht um genau einen Baum. Es soll verschiedene Bäume
    mit dem gleichen Aufbau geben (1 Elternteil, viele Kinder).
    Leider gehen Templates nicht, da eben auf jeder Ebene ein anderer
    Typ vorhanden ist.
    Ein "namentliches" Ansprechen mit getA() scheitert an der Beliebigkeit
    der Bäume. Das muß bei allen Bäumen dieser Art etwa gleich gehen, damit
    man auf so einem Baum dann Standardfunktionen (einfügen, kopieren)
    definieren kann.
    Die Idee von thordk hatte ich auch schon: eine Art Container, der pro
    Knoten einen Verweis/Zeiger auf das eigentliche Datenobjekt beinhaltet.
    Da fand ich es günstiger, wenn A tatsächlich Zeiger auf B hat und nicht
    die Baumstruktur nur in den Nodes ist, die ihrerseits auf A und B zeigen.

    Daher mein Entwurf...



  • Hallo,

    das mit den Templates klingt interessant.
    Aber folgendes dürfte z.B. für eine Funktion getRoot() nicht gehen:

    while(ptr!=NULL) ptr=ptr->getParent();
    

    ... dafür brauche ich die Gleichmacherei zur Baumverwaltung (oder?).
    Für die Datenverwaltung hätte ich lieber die Typsicherheit.

    Daher kam ja meine Idee mit dem Iterator. Der darf gleichmachen und
    rumforsten. Der Benutzer braucht/darf das nicht. Aber ich weiß nicht,
    ob das geht...

    Gruß,
    Ulf


Anmelden zum Antworten