Arbres binaires de recherche

Définition

Un arbre binaire de recherche (ABR) (binary search treeBST) est un arbre binaire tel que :

  • les clefs des nœuds doivent être ordonnables (il doit exister entre elles une relation d’ordre)
  • pour chacun de ses nœuds:
    • chaque nœud du sous-arbre gauche a une clé inférieure (ou égale) à celle du nœud considéré,
    • chaque nœud du sous-arbre droit possède une clé supérieure (ou égale) à celle-ci

Remarque : selon la mise en œuvre de l’ABR, on pourra interdire ou non des clés de valeurs égales.

Lorsqu’on ajoute un nœud à un ABR, il devient une feuille de l’arbre.

Activité : validité d'un ABR

Soit l'arbre binaire suivant :

S'agit-il d'un ABR (justifier) ?

 

Définitions spécifiques

Arbre binaire parfait : arbre dont tous les niveaux sont remplis et dont toutes les feuilles sont à la même profondeur (le dernier niveau est complètement occupé).

Arbre binaire complet : tous les niveaux de l’arbre sont remplis, sauf éventuellement le dernier, sur lequel les nœuds (feuilles) sont à gauche.

Arbre binaire équilibré : tous les chemins de la racine aux feuilles ont la même longueur.

Arbre binaire dégénéré : chacun de ses nœuds a au plus un fils.

 

Interface

Soit l’ABR suivant, qui servira comme support pour illustrer la suite :

Télécharger la version PDF

 

 

Plus petite (grande) valeur

Pour accéder à la clé la plus petite dans un ABR, il suffit de descendre sur le fils gauche autant que possible. Le dernier nœud visité qui n’a pas de fils gauche porte la valeur la plus petite de l’arbre.

Activité : valeurs extrêmes d'un ABR
Sur l'arbre donné en exemple, tracer les chemins permettant d'accéder à la plus petite valeur de ses clés, puis à la plus grande valeur.

 

Tri des clés

Pour obtenir la suite ordonnée de toutes les clés d’un ABR, il suffit de le parcourir l’arbre dans l’ordre infixe.

Exemple :

 

Activité : tri des clés d'un ABR
Vérifier que l'arbre donné en exemple est bien un ABR en parcourant ses clés dans l'ordre infixe.

 

Recherche

Comme son nom l’indique, un ABR est spécifiquement adapté à la recherche d’enregistrements dont les clés sont données par les nœuds de l’arbre.

Exemple : recherche de la clé de valeur 6

Un ABR permet de rechercher rapidement (faible coût temporel).

Activité : coût de la recherche

Arbre dégénéré (ou filiforme)

Supprimer des nœuds à l'arbre donné en exemple pour former un arbre dégénéré.

 

En déduire l'ordre de complexité de l'algorithme de recherche d'une valeur parmi ses clés.
CORRECTION

Dans le pire des cas, la valeur recherchée est sur un nœud de profondeur égale à la hauteur de l’arbre, égale à sa taille +1:

Par conséquent la complexité est : \(\large{O(n)}\)

Arbre équilibré

Transformer l'arbre donné en exemple en arbre équilibré en supprimant le moins de nœuds possible.

 

À l'aide de l'encadrement de la hauteur d'un arbre binaire, estimer l'ordre de complexité de l'algorithme de recherche d'une valeur parmi ses nœuds.
CORRECTION

Dans le pire des cas, la valeur recherchée est sur un nœud de profondeur égale à la hauteur de l’arbre :

Dans l’article sur les arbres binaires, on a montré que : \(\lfloor\log_2(n)\rfloor\leq h\leq n−1\) (\(n\) étant la taille de l’arbre)

Par conséquent la complexité est : \(\large{O(\log_2(n))}\)

 

 

Insertion

Pour insérer un nœud dans un arbre :

  • on recherche le nœud dont la clé est la plus proche de celle du nœud à insérer
    (comme pour l’algorithme de recherche, sauf que le nœud recherché n’est pas sensé être trouvé),
  • dès qu’une place est libre, on ajoute le nœud :
    • à gauche si sa clé est inférieure à celle de la feuille,
    • ou à droite dans le cas contraire.

Exemple : ajout d’un nœud dont la clé vaut 5 :

Dans le cas d’un ABR qui n’autorise pas les clés multiples, il faudra empêcher l’insertion d’un nœud dont la clé est déjà dans l’arbre.

Activité : insertions
Sur l'arbre fourni en exemple, ajouter des nœuds de clé égales à 19, 33, puis 95.

 

 

Suppression

L’opération dépend du nombre de fils du nœud à supprimer.

Cas n°1 : le nœud à supprimer est une feuille

  • On cherche le père du nœud à supprimer,
  • On supprimer le lien père-fils.

Exemple : suppression du nœud 5

Cas n°2 : le nœud à supprimer a un unique fils

  • On recherche le père du nœud à supprimer,
  • On redirige le lien père-fils vers le fils (unique) du nœud à supprimer.

Exemple : suppression du nœud 6

Cas n°3 : le nœud à supprimer a deux fils

  • On recherche le nœud de sa branche de gauche ayant la plus grande valeur,
    comme c’est le plus grand de cette branche, il n’a pas de fils droit (voir Plus grande valeur)
  • On rompt le lien père-fils, (comme dans le cas n°2 ! )
  • Et on remplace la valeur du nœud à supprimer par celle du nœud décroché.

Exemple : suppression du nœud 11

Exemple : suppression du nœud 7

Remarque : alternativement, on peut « décrocher » le nœud ayant la plus petite valeur de sa branche de droite.

Activité : valeurs extrêmes d'un ABR
Sur l'arbre donné en exemple, supprimer les nœuds 29, 71 et 55.

 

 

Implémentation

On se propose d’implémenter l’interface présentée plus haut en langage Python.

Pour cela, on utilisera, comme des modules (sans les modifier !), les programmes suivant :

 

Implémenter une classe ABR(Arbre Binaire de Recherche), héritant de la classe Arbre_b(Arbre Binaire).
CORRECTION
class ABR(Arbre_b): 
    def __init__(self, racine = None): 
        super().__init__(racine)

 

Implémenter, sous forme de méthodes de la classe ABR, les opérations de l’interface évoquée plus haut :
  • min()Noeud_b
  • max()Noeud_b
CORRECTION

Solution récursive :

La classe Arbre_br n’étant en elle même pas structurellement récursive, on imbrique une fonction récursive m (portant sur les nœuds de l’arbre) à l’intérieur des méthodes min et max :

def min(self):
    """ Renvoie la plus petite valeur de l'arbre """
    def m(n):
        if n.g is not None:
            return m(n.g)
        return n
    return m(self.racine)


def max(self):
    """ Renvoie la plus grande valeur de l'arbre """
    def m(n):
        if n.d is not None:
            return m(n.d)
        return n
    return m(self.racine)

Solution itérative :

def min(self):
    n = self.racine
    p = None
    while n is not None:
        p = n
        n = n.g
    return p

def max(self):
    n = self.racine
    p = None
    while n is not None:
        p = n
        n = n.d
    return p

 

 

  • chercher(v)Noeud_b
CORRECTION
def chercher(self, v):
    """ Renvoie le Noeud de valeur v
        None si aucun noeud n'a cette valeur
    """
    n = self.racine
    while n is not None:
        if v > n.v:
            n = n.d
        elif v < n.v:
            n = n.g
        else:
            return n
  • inserer(v)
CORRECTION

On peut décomposer en implémentant une méthode chercher_plu-proche, très proche de chercher, mais qui renvoie None si le nœud de valeur v existe déjà (on ne doit pas avoir deux nœuds de même valeur dans l’ABR) :

def chercher_plus_proche(self, v):
    n = self.racine
    p = None
    while n is not None:
        if v < n.v:
            p = n
            n = n.g
        elif v > n.v:
            p = n
            n = n.d
        else:
            return
    return p

 

Ou tout intégrer dans une unique méthode :

def inserer(self, v):
    """ Insère un nouveau Noeud de valeur v dans l'arbre
        (version itérative)
    """
    if self.racine is None:
        self.racine = Noeud_b(v)

    else:
        n = self.racine
        while n is not None:
            if v < n.v:
                if n.g is None:
                    n.g = Noeud_b(v)
                    return
                n = n.g

            elif v > n.v:
                if n.d is None:
                    n.d = Noeud_b(v)
                    return
                n = n.d

            else:
                return # clé déjà dans l'arbre
  • supprimer(v)
CORRECTION
def supprimer(self, v):
    """ Supprime le Noeud de valeur v dans l'arbre
        (s'il existe)
    """
    n = self.racine
    p = None  # Père de n
    continuer = True
    while n is not None and n.v != v:
        p = n
        if v > n.v:
            n = n.d
        else: # Remarque : le cas n.v == v ne peut pas se produire ici
            n = n.g

    if p is not None and n is not None:
        if n.d is None and n.g is None:  # Feuille
            if n.v < p.v:
                p.g = None
            else:
                p.d = None
                
        elif n.d is None:                # Fils unique à gauche
            if p.v < n.v:
                p.g = n.d
            else:
                p.d = n.d
        elif n.g is None:                # Fils unique à droite
            if p.v < n.v: 
                p.d = n.d 
            else: 
                p.d = n.g        
        
        else:                            # 2 fils
            # on cherche le plus grand noeud à gauche (noeud à déplacer m) et son père p
            m = n.g
            p = n
            while m.d is not None:
                p = m
                m = m.d
            n.v = m.v # On remplace la valeur du noeud supprimer par celle du noeud à déplacer
            # On supprime la référence du noeud à déplacer (lien père-fils)
            if m.v < p.v: # il est à gauche de son père p
                p.g = m.g
            else:         # il est à droite de son père p
                p.d = m.g
  • verifier()bool (méthode récursive, d’après la définition d’un ABR)
Aide
  • La classe ABRn’étant pas récursive (contrairement à la classe Noeud_b), pour coder une méthode de l’objet ABR, il faudra passer par une fonction auxiliaire (récursive) définie comme sous-fonction de la méthode verifier().
  • Afin de bien identifier la validité d’une branche de racine n, il faut identifier les 5 conditions qui doivent être vérifiées :
    • 1 portant sur la valeur du nœud  n
    • 2 portant sur la valeur des fils de n
    • 2 portant sur la validité des branches partant des fils de n

 

Sources :
https://fr.wikipedia.org/wiki/Arbre_binaire_de_recherche
http://pageperso.lif.univ-mrs.fr/~francois.denis/algoMPCI/chap1.pdf

 

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *