Binary Tree Rotation

voix
3

Je travaille sur la mise en œuvre d'un arbre AVL de recherche. Jusqu'à présent, j'ai terminé la partie codante et j'ai commencé à tester pour les bugs. J'ai découvert que mes méthodes de rotation des noeuds sont mis sur écoute et pour l'amour de Dieu, je ne comprends pas quel est le problème.

L'algorithme fonctionne comme il se doit sur le papier, mais lorsqu'il est exécuté sur une machine bien ... fuites nœuds d'arbres.

Ceci est la méthode utilisée pour faire tourner un noeud vers la gauche: http://pastebin.com/mPHj29Af

bool avl_search_tree::avl_tree_node::rotate_left()
{
    if (_right_child != NULL) {
        avl_tree_node *new_root = _right_child;
 
        if (_parent != NULL) {
            if (_parent->_left_child == this) {
                _parent->_left_child = new_root;
            } else {
                _parent->_right_child = new_root;
            }
        }
 
        new_root->_parent = _parent;
        _parent = new_root;
 
        _right_child = new_root->_left_child;
        new_root->_left_child = this;
 
        if (_right_child != NULL) {
            _right_child->_parent = this;
        }
 
        //update heights
        update_height();
        new_root->update_height();
 
        return true;
    }
 
    return false;
}

Dans ma méthode d'insertion je commentais la partie d'équilibrage AVL et au lieu que je suis juste essayer de faire tourner le nouveau nœud inséré à gauche. Le résultat pour l'insertion des nombres entiers dans l'ordre croissant: mon arbre ne contient que la racine initial (premier noeud inséré) et tous les autres noeuds sont divulgués.

Toute aide à identifier le problème est très apprécié que je commence à devenir fou.

Pour mémoire: si je n'utilise pas la rotation de l'arbre ne fuira pas les nœuds et il fonctionne comme un arbre de recherche binaire asymétrique normale (pour l'insertion et recherche).

Edit: En raison du commentaire de AJG85 Je vais ajouter les observations:

J'ai ajouté des « contrôles » printf à la méthode destructor de avl_search_tree :: avl_tree_node qui imprimera la valeur de clé (dans mon cas des entiers 32 bits) avant le nettoyage et la méthode d'insertion du avl_search_tree qui imprimera la clé juste insérée.

Ensuite, dans le point d'entrée du programme que j'allouer un avl_search_tree sur le tas et ajouter des clés à elle dans l'ordre croissant, puis le supprimer.

Avec AVL équilibrage activé, je reçois la sortie suivante dans le terminal:

bool avl_search_tree::insert(const int&) : 1
bool avl_search_tree::insert(const int&) : 2
bool avl_search_tree::insert(const int&) : 3
bool avl_search_tree::insert(const int&) : 4
bool avl_search_tree::insert(const int&) : 5
bool avl_search_tree::insert(const int&) : 6
bool avl_search_tree::insert(const int&) : 7
bool avl_search_tree::insert(const int&) : 8
avl_search_tree::avl_tree_node::~avl_tree_node() : 1

Ce qui signifie que des insertions ont réussi quetous mais seulement la racine a été supprimé.

Avec l'équilibrage AVL commenté cela fonctionne comme un arbre de recherche binaire normal. La borne de sortie est la suivante:

bool avl_search_tree::insert(const int&) : 1
bool avl_search_tree::insert(const int&) : 2
bool avl_search_tree::insert(const int&) : 3
bool avl_search_tree::insert(const int&) : 4
bool avl_search_tree::insert(const int&) : 5
bool avl_search_tree::insert(const int&) : 6
bool avl_search_tree::insert(const int&) : 7
bool avl_search_tree::insert(const int&) : 8
avl_search_tree::avl_tree_node::~avl_tree_node() : 1
avl_search_tree::avl_tree_node::~avl_tree_node() : 2
avl_search_tree::avl_tree_node::~avl_tree_node() : 3
avl_search_tree::avl_tree_node::~avl_tree_node() : 4
avl_search_tree::avl_tree_node::~avl_tree_node() : 5
avl_search_tree::avl_tree_node::~avl_tree_node() : 6
avl_search_tree::avl_tree_node::~avl_tree_node() : 7
avl_search_tree::avl_tree_node::~avl_tree_node() : 8

Ce qui signifie que tout est correctement nettoyé.

Maintenant ... Comment suis-je arrivé à la conclusion que les méthodes de rotation sont les enjeux? Dans le cadre du sous-programme d'équilibrage de AVL a commenté J'ai ajouté une ligne qui tourne chaque nœud nouvellement inséré à gauche. Le résultat? La même chose que si le sous-programme d'équilibrage AVL a été activé.

Et en ce qui concerne la méthode update_height (), il ne modifie pas la structure de l'arbre de quelque façon.

J'espère que cela le préciser.

Edit 2:

Pour clarifier les choses encore plus, comment le son est destructor de avl_tree_node est mis en œuvre:

avl_search_tree::avl_tree_node::~avl_tree_node()
{
    printf(%s : %d\n, __PRETTY_FUNCTION__, *_key);

    if (_left_child != NULL) {
        delete _left_child;
    }

    if (_right_child != NULL) {
        delete _right_child;
    }

    if (_key != NULL) {
        delete _key;
    }
}

_left_child et _right_child sont des pointeurs vers des objets avl_tree_node alloués sur le tas.

Edit 3:

Merci au 2 commentaire de AGJ85 Je trouve la question. Dans mes méthodes rotate j'ai oublié que j'ai fait mettre à jour le pointeur de la racine de l'arbre à la nouvelle racine à chaque fois que la racine a été déplacée.

Fondamentalement, la racine de l'arbre a toujours été dirigée vers le premier noeud inséré et sans mettre à jour le pointeur en cas de besoin, mes méthodes rotate seraient fuir la racine du nouvel arbre qui a été configuré en fait à droite. :)

Merci AGJ85!

Créé 02/08/2011 à 18:19
source utilisateur
Dans d'autres langues...                            


3 réponses

voix
2

EDIT - Bon sang - je ne vois pas que la question est déjà résolue (réponse en question). Pourtant, peut - être il y a quelques conseils non-réponse dans cette valeur recuperation.

Je n'ai pas vérifié à fond, mais je pense que vous allez mal à cette ligne ...

_right_child = new_root->_left_child;

et que le problème est que vous avez peut - être déjà écrasé new_root->_left_childdans la ligne ...

_parent->_left_child = new_root;

Ce que je pense que vous devriez faire est, au début, ont un bloc de définitions locales comme ...

avl_tree_node *orig_parent      = _parent;
avl_tree_node *orig_this        = this;
avl_tree_node *orig_left_child  = _left_child;
avl_tree_node *orig_right_child = _right_child;

Ensuite , utilisez les orig_variables locales comme les sources pour des missions ultérieures. Cela permet d' économiser une certaine quantité de se soucier des flux de données à travers les différents pointeurs lors de la rotation. Le Optimiseur doit se débarrasser de tout travail superflu vaut se soucier de cela, et il n'y a pas beaucoup de toute façon.

Quelques points supplémentaires ...

Tout d'abord, le C ++ (et C) identificateurs de réserves de normes avec les traits de soulignement, et avec double-underscores. On prétend que vous pouvez obtenir des interactions surprise avec les bibliothèques standard et fourni par le compilateur si vous ne respectez pas - je suppose que faudrait être macro-connexes pour les identifiants membres, cependant. underscores fin de chaîne sont OK - je tendance à les utiliser pour inclure les gardes.

Une convention commune pour les variables membres est d'ajouter un chef de file mou m_. Encore plus commun, probablement, est de ne pas avoir tout préfixe ou un suffixe spécial du tout.

En second lieu, vous pouvez (ou non) il est plus facile à mettre en œuvre AVL qui ne sont pas des liens parents stockés dans les noeuds. Je ne l'ai pas mis en œuvre moi-même encore AVL, mais je l'ai mise en œuvre des arbres rouge-noir une fois. Un certain nombre d'algorithmes doivent inclure une recherche récursive comme la première étape - vous ne pouvez pas faire juste une recherche standard qui se souvient du noeud trouvé, mais d'éliminer la route vers ce nœud. Cependant, la mise en œuvre récursive est pas trop mal, et il y a moins de pointeurs à jongler.

Enfin, un conseil général - en essayant de « essai » un algorithme comme celui - ci peut facilement vous désarçonner à moins que vous strictement travailler à travers elle , étape par étape, et de vérifier toutes les sources d'information pertinentes (ai - je déjà modifié ce?) À chaque étape. Il est très facile de prendre l'habitude de sauter quelques - uns des détails pour la vitesse. Une marche à sec de la machine assistée utile consiste à exécuter l'étape par étape code dans un débogueur, et voir si les résultats à chaque étape d' accord avec votre papier sec terme.

EDIT - Une note plus - Je ne vais pas appeler cela un pourboire parce que je ne suis pas sûr dans ce contexte. Je mets en œuvre habituellement des noeuds de structure de données avec de simples struct - sans se cacher des données, peu ou pas de fonctions membres. La plupart du code est conservé séparé de la structure de données, souvent dans une classe « outil ». Je sais que cela brise le vieux « la forme se dessine » principe de la POO, mais l' OMI , il fonctionne mieux dans la pratique.

Créé 02/08/2011 à 20:34
source utilisateur

voix
3

Merci au 2 commentaire de AGJ85 Je trouve la question. Dans mes méthodes rotate j'ai oublié que j'ai fait mettre à jour le pointeur de la racine de l'arbre à la nouvelle racine à chaque fois que la racine a été déplacée.

Fondamentalement, la racine de l'arbre a toujours été dirigée vers le premier noeud inséré et sans mettre à jour le pointeur en cas de besoin, mes méthodes rotate seraient fuir la racine du nouvel arbre qui a été configuré en fait à droite. :)

Créé 03/08/2011 à 10:03
source utilisateur

voix
1

Je vois que vous avez trouvé le bug que vous recherchez dans votre code. (Comme vous le dites, vous ne mettez à jour le pointeur de la racine de l'arbre à la nouvelle racine quand la racine a changé. Il est un paradigme commun pour la liste et l'arbre Insérer / Supprimer des méthodes pour renvoyer un pointeur vers la tête de la liste ou la racine de l'arbre, et si vous souvenez-vous de ce paradigme ne referai pas l'erreur.)

Au niveau de vue plus élevé, la technique que je l' ai utilisé pour éviter les problèmes avec AVL Arbre ou arbre rouge-noir Code est d'utiliser à la place un arbre AA , qui a des performances semblables à eux, en utilisant O (n) l' espace et O (log n) pour insérer, supprimer et recherche. Cependant, les arbres AA sont nettement plus simples à code.

Créé 04/08/2011 à 16:55
source utilisateur

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more