web-dev-qa-db-fra.com

Comptage de nœuds dans une arborescence en Java

Tout d’abord, je jure que ce n’est pas un devoir, c’est une question qui m’a été posée lors d’une interview. Je pense que j'en ai fait un gâchis (même si j'ai réalisé que la solution nécessite une récursion). Voici la question:

Implémentez la méthode count () qui renvoie le nombre de nœuds dans une arborescence. Si un nœud n'a pas d'enfant gauche ou droit, la méthode getXXChild() appropriée renverra null

class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

  Tree getLeftChild() {
    // Assume this is already implemented
  }

  int count() {
    // Implement me
  }
}

Ma raison de poser la question est simplement curieuse de voir la bonne solution et de mesurer ainsi à quel point le mien était mauvais.

A bientôt, Tony

20
Tony
int count() {
  Tree right = getRightChild();
  Tree left = getLeftChild();
  int c = 1;                                      // count yourself!
  if ( right != null ) c += right.count();        // count sub trees
  if ( left != null ) c += left.count();          // ..
  return c;
}
32
Blorgbeard

Une solution récursive triviale:

int count() {
   Tree l = getLeftTree();
   Tree r = getRightTree();
   return 1 + (l != null ? l.count() : 0) + (r != null ? r.count() : 0);
}

Un non trivial non-récursif:

int count() {
    Stack<Tree> s = new Stack<Tree>();
    s.Push(this);
    int cnt = 0;
    while (!s.empty()) {
        Tree t = s.pop();
        cnt++;
        Tree ch = getLeftTree();
        if (ch != null) s.Push(ch); 
        ch = getRightTree();
        if (ch != null) s.Push(ch); 
    }
    return cnt;
}

Ce dernier est probablement légèrement plus efficace en termes de mémoire car il remplace la récursion par une pile et une itération. C'est aussi probablement plus rapide, mais c'est difficile à dire sans mesures. Une différence clé est que la solution récursive utilise la pile, tandis que la solution non récursive utilise le tas pour stocker les nœuds.

Edit: Voici une variante de la solution itérative, qui utilise moins la pile:

int count() {
    Tree t = this;
    Stack<Tree> s = new Stack<Tree>();
    int cnt = 0;
    do {
        cnt++;
        Tree l = t.getLeftTree();
        Tree r = t.getRightTree();
        if (l != null) {
            t = l;
            if (r != null) s.Push(r);
        } else if (r != null) {
            t = r;
        } else {
            t = s.empty() ? null : s.pop();
        }
    } while (t != null);
    return cnt;
}

Que vous ayez besoin d'une solution plus efficace ou plus élégante dépend naturellement de la taille de vos arbres et de la fréquence à laquelle vous comptez utiliser cette routine. Rembemer a déclaré Hoare: "l'optimisation prématurée est la racine de tout le mal".

19
David Hanak

J'aime mieux cela parce qu'il se lit:

retour compte pour gauche + compte pour droit + 1 

  int count() {
      return  countFor( getLeftChild() ) + countFor( getRightChild() ) + 1;
  }
  private int countFor( Tree tree )  { 
       return tree == null ? 0 : tree.count();
  }

Un peu plus vers une programmation alphabète.

En passant, je n'aime pas la convention getter/setter qui est si communément utilisée en Java, je pense qu'une utilisation de leftChild () serait préférable:

  return countFor( leftChild() ) + countFor( rightChild() ) + 1;

Comme Hoshua Bloch l'explique ici http://www.youtube.com/watch?v=aAb7hSCtvGw à min. 32:03

Si vous l'obtenez, votre code se lit ...

MAIS, je dois admettre que la convention get/set fait maintenant presque partie du langage. :) 

Pour de nombreuses autres parties, suivre cette stratégie crée un code auto-documentant, ce qui est une bonne chose.

Tony: Je me demande quelle était votre réponse dans l'interview.

11
OscarRyz

Quelque chose comme ça devrait marcher:

int count()
{
    int left = getLeftChild() == null ? 0 : getLeftChild().count();
    int right = getRightChild() == null ? 0 : getRightCHild().count();

    return left + right + 1;
}
4
Outlaw Programmer
return (getRightChild() == null ? 0 : getRightChild.count()) + (getLeftChild() == null ? 0 : getLeftChild.count()) + 1;

Ou quelque chose comme ça.

4
Steven Robbins
class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

  Tree getLeftChild() {
    // Assume this is already implemented
  }

  int count() {
   return 1 
      + getRightChild() == null? 0 : getRightChild().count()
      + getLeftChild() == null? 0 : getLeftChild().count();
  }
}
4
user66322

Implémenter la méthode:

public static int countOneChild(Node root)
{
    ...
}

qui compte le nombre de nœuds internes dans une arborescence binaire ayant un enfant. Ajoutez la fonction au programme tree.Java.

2
morad nanz

Vous pouvez compter l’arbre en le parcourant de nombreuses manières . Précommandez simplement la traversée, le code serait (basé sur les fonctions que vous avez définies):

int count() {
    count = 1;
    if (this.getLeftChild() != null)
        count += this.getLeftChild().count();
    if (this.getRightChild() != null)
        count += this.getRightChild().count();
    return count;
}
2
achinda99

Je l'ai fait par récursion préalable. Bien que cela ne suive pas exactement le format de l'entretien en utilisant la racine locale, je pense que vous avez compris.

private int countNodes(Node<E> localRoot, int count) {
    if (localRoot == null) 
        return count;     
    count++; // Visit root
    count = countNodes(localRoot.left, count); // Preorder-traverse (left)
    count = countNodes(localRoot.right, count); // Preorder-traverse (right)
    return count;
}

public int countNodes() {
   return countNodes(root, 0);
}
1
Tiago Redaelli

C'est un problème de récursivité standard:

count():
    cnt = 1 // this node
    if (haveRight) cnt += right.count
    if (haveLeft)  cnt += left.count
return cnt;

Très inefficace, et un tueur si l'arbre est très profond, mais c'est la récursion pour toi ...

0
Jeff Kotula

Bien sûr, si vous souhaitez éviter de visiter chaque nœud de votre arborescence lorsque vous comptez et que le temps de traitement vaut plus que votre mémoire, vous pouvez tricher en créant vos comptes au fur et à mesure que vous construisez votre arborescence.

  1. Avoir un compte int dans chaque noeud, Initialisé à un, qui Représente le nombre de noeuds dans Le sous-arbre enraciné dans ce noeud.

  2. Lorsque vous insérez un nœud, avant de revenir de votre routine d'insertion récursive , Incrémentez le nombre au nœud Courant.

c'est à dire. 

public void insert(Node root, Node newNode) {
  if (newNode.compareTo(root) > 1) {
    if (root.right != null) 
      insert(root.right, newNode);
    else
      root.right = newNode;
  } else {
    if (root.left != null)
      insert(root.left, newNode);
    else
      root.left = newNode;
  }
  root.count++;
}

Puis obtenir le nombre à partir de n'importe quel point implique simplement une recherche de node.count

0
Ben Hardy
int count()

{
   int retval = 1;
    if(null != getRightChild()) retval+=getRightChild().count();
    if(null != getLeftChild()) retval+=getLeftChild().count();
    return retval;

}

J'espère que je n'ai pas commis d'erreur.

EDIT: Je l'ai fait en fait.

0
mannicken

Les questions relatives à l'arbre binaire doivent être attendues lors d'une interview. Je dirais de prendre le temps avant toute prochaine interview et de passer par this link. Il y a environ 14 problèmes résolus. Vous pouvez regarder et comment la solution est faite. Cela vous donnerait une idée de la façon de traiter un problème d'arborescence binaire à l'avenir.

Je sais que votre question est spécifique à la méthode de comptage .C'est également implémenté dans le lien que j'ai fourni

0
Barry

Ma première tentative n'avait rien de nouveau à ajouter, mais j'ai ensuite commencé à me poser des questions sur la profondeur de la récursivité et sur la possibilité de réorganiser le code pour tirer parti de la fonctionnalité d'optimisation des appels en aval du dernier compilateur Java. Le problème principal était le test null - qui peut être résolu en utilisant un NullObject. Je ne sais pas si TCO peut gérer les deux appels récursifs, mais il devrait au moins optimiser le dernier.

static class NullNode extends Tree {

    private static final Tree s_instance = new NullNode();

    static Tree instance() {
        return s_instance;
    }

    @Override
    Tree getRightChild() {  
        return null;
    }  

    @Override
    Tree getLeftChild() {  
        return null;
    }  

    int count() {  
        return 0;
    }
}

int count() {      
    Tree right = getRightChild();      
    Tree left  = getLeftChild();      

    if ( right == null ) { right = NullNode.instance(); }
    if ( left  == null ) { left  = NullNode.instance(); }

    return 1 + right.count() + left.count();
}   

L'implémentation précise de NullNode dépend des implémentations utilisées dans Tree. Si Tree utilise NullNode au lieu de null, les méthodes d'accès aux enfants devraient peut-être renvoyer NullPointerException au lieu de renvoyer null. Quoi qu’il en soit, l’idée principale est d’utiliser un NullObject afin d’obtenir des avantages du coût total de possession.

0
richj
class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

Tree getLeftChild() {
    // Assume this is already implemented
 }

 int count() {
    if(this.getLeftChild() !=null && this.getRightChild()!=null) 
        return 1 + this.getLeftChild().count() + this.getRightChild().count();
    elseif(this.getLeftChild() !=null && this.getRightChild()==null)
        return 1 + this.getLeftChild().count();
    elseif(this.getLeftChild() ==null && this.getRightChild()!=null)
        return 1 + this.getRightChild().count();
    else return 1;//left & right sub trees are null ==> count the root node
  }
}
0
amu