web-dev-qa-db-fra.com

La recherche de nœuds dans la pile de débordements d'arbres binaires

J'utilise la méthode suivante pour parcourir * un arbre binaire de 300 000 niveaux:

Node* find(int v){
   if(value==v)
      return this;
   else if(right && value<v)
      return right->find(v); 
   else if(left && value>v)
      return left->find(v);
}

Cependant, je reçois une erreur de segmentation en raison d'un débordement de pile . Des idées sur la façon de parcourir l'arbre profond sans la surcharge d'appels de fonction récursifs?

* Par "traverse", je veux dire "rechercher un nœud avec une valeur donnée", pas une traversée complète.

18
PerelMan

Oui! Pour un arbre de niveau 300 000 éviter la récursivité . Parcourez votre arbre et trouvez la valeur de manière itérative en utilisant une boucle.

Représentation de l'arbre de recherche binaire

           25             // Level 1
        20    36          // Level 2
      10 22  30 40        // Level 3
  .. .. .. .. .. .. .. 
.. .. .. .. .. .. .. ..   // Level n

Juste pour clarifier davantage le problème. Votre arbre a une profondeur de n = 300.000 niveaux . Ainsi, dans le pire des cas, un arbre de recherche binaire (BST) devra visiterTOUSdes noeuds de l'arbre. C'est une mauvaise nouvelle car/ le pire des cas a une complexité algorithmique O(n) temporelle . Un tel arbre peut avoir:

2ˆ300.000 noeuds = 9.9701e + 90308 noeuds (environ).


9.9701e + 90308 nœuds est un exponentiellement massif nombre de nœuds à visiter. Avec ces chiffres, il devient tellement clair pourquoi la pile d'appels déborde.


Solution (manière itérative):

Je suppose que votre déclaration de noeud class/struct est un entier standard classiqueBSTone. Ensuite, vous pourrez l’adapter et cela fonctionnera:

struct Node {
    int data;
    Node* right;
    Node* left;
};

Node* find(int v) {
    Node* temp = root;  // temp Node* value copy to not mess up tree structure by changing the root
    while (temp != nullptr) {
        if (temp->data == v) {
            return temp;
        }
        if (v > temp->data) {
            temp = temp->right;
        }
        else {
            temp = temp->left;
        }
    }
    return nullptr;
}

Cette approche itérative évite la récursivité , vous évitant ainsi d'avoir à chercher récursivement la valeur dans un arbre aussi volumineux avec votre pile d'appels au programme. 

27
Santiago Varela

Une boucle simple dans laquelle vous avez une variable de type Node * que vous définissez sur le nœud suivant, puis qui se boucle à nouveau ...
N'oubliez pas que la valeur que vous recherchez n'existe pas!

9
Rene

Vous pouvez implémenter la récursivité en n'utilisant pas la pile d'appels mais une pile définie par l'utilisateur ou quelque chose de similaire. Cela pourrait être fait via le template stack existant. L'approche serait d'avoir une boucle while qui itère jusqu'à ce que la pile soit vide; Comme l'implémentaion existante utilise la recherche en profondeur d'abord, l'élimination des appels récursifs peut être trouvée ici .

7
Codor

Lorsque l'arborescence que vous avez est un Arborescence de la recherche binaire et que vous souhaitez simplement y rechercher un nœud ayant une valeur spécifique, la procédure est simple: aucune récursion n'est nécessaire, vous pouvez le faire à l'aide de une simple boucle comme d'autres l'ont fait remarquer.

Dans le cas plus général d’avoir un arbre qui n’est pas nécessairement un Binary Search Tree et de vouloir effectuer un parcours complet , le moyen le plus simple est d’utiliser la récursion, mais comme vous le savez déjà, l'arbre est très profond, la récursivité ne fonctionnera pas.

Pour éviter la récursivité, vous devez donc implémenter une pile sur le tas C++. Vous devez déclarer une nouvelle classe StackElement qui contiendra un membre pour chaque variable locale qu'avait votre fonction récursive d'origine et un membre pour chaque paramètre accepté par votre fonction récursive d'origine. (Vous pourrez peut-être vous en sortir avec moins de variables membres, vous pourrez vous en préoccuper une fois que votre code fonctionnera.)

Vous pouvez stocker des instances de StackElement dans une collection de piles ou simplement demander à chacune d’elles de contenir un pointeur sur son parent, implémentant ainsi la pile par vous-même. 

Ainsi, au lieu que votre fonction s’appelle de manière récursive, elle se compose simplement d’une boucle. Votre fonction entre dans la boucle avec le current StackElement en cours d'initialisation avec des informations sur le noeud racine de votre arborescence. Son pointeur parent sera null, ce qui est une autre façon de dire que la pile sera vide.

À chaque endroit où la version récursive de votre fonction s’appelait elle-même, votre nouvelle fonction allouera une nouvelle instance de StackElement, l’initialisera et répétera la boucle en utilisant cette nouvelle instance comme élément current .

À chaque endroit où la version récursive de votre fonction retournait, votre nouvelle fonction publiera l'élément current StackElement, en remplaçant celui qui était assis en haut de la pile, ce qui en fait le nouvel élément current / et en répétant la boucle.

Lorsque vous trouvez le noeud que vous recherchez, il vous suffit de rompre avec la boucle.

Alternativement, si le nœud de votre arborescence existante prend en charge a) un lien vers son nœud "parent" et b) des données utilisateur (où vous pouvez stocker un indicateur "visité"), vous n'avez pas besoin d'implémenter votre propre pile. parcourez simplement l’arbre sur place: à chaque itération de votre boucle, vous vérifiez d’abord si le nœud actuel est le nœud que vous recherchiez; sinon, vous énumérez les enfants jusqu'à ce que vous en trouviez un qui n'a pas encore été visité, puis vous le visitez; lorsque vous atteignez une feuille ou un nœud dont tous les enfants ont été visités, vous suivez en arrière en suivant le lien vers le parent. De plus, si vous avez la liberté de détruire l’arbre au fur et à mesure que vous le parcourez, vous n’avez même pas besoin du concept de "données utilisateur": une fois que vous avez terminé avec un noeud enfant, vous le libérez et le rendez nul.

6
Mike Nakis

Eh bien, cela peut être récursif au prix d’une seule variable locale supplémentaire et de quelques comparaisons:

Node* find(int v){
  if(value==v)
    return this;
  else if(!right && value<v)
    return NULL;
  else if(!left && value>v)
    return NULL;
  else {
    Node *tmp = NULL;
    if(value<v)
      tmp = right;
    else if(value>v)
      tmp = left;
    return tmp->find(v);
  }
}
3
user2793784

Parcourir un arbre binaire est un processus récursif, dans lequel vous continuerez à marcher jusqu'à ce que vous trouviez que le nœud où vous vous trouvez actuellement ne pointe nulle part.

C'est que vous avez besoin d'une condition de base appropriée. Quelque chose qui ressemble à:

if (treeNode == NULL)
   return NULL;

En général, la traversée d’un arbre s’effectue de la manière suivante (en C):

void traverse(treeNode *pTree){
  if (pTree==0)
    return;
  printf("%d\n",pTree->nodeData);
  traverse(pTree->leftChild);
  traverse(pTree->rightChild);
}
0
user4063679