Cliquer ici pour imprimer

Dernière modification : 18/10/2022 à 17:55

TP4 - BST et AVL
TP4 - BST et AVL

Comme vu en cours, les arbres binaires de recherche (ABR en francais ou BST en anglais) sont des arbres binaires (2 fils au plus par noeud) qui vérifient une propriété d’ordre : le fils gauche est toujours plus petit que son père, et le fils droit plus grand.

Fonctions de bases

arbre AJOUTER(element x, arbre A)
    si A est vide
        arbre B = creer un noeud avec l'element x
        A = B;
        renvoyer A
    sinon si x < racine de A
            fils gauche de A = AJOUTER(x,fils gauche de A)
            retourner A
    sinon si x > racine de A
            fils droit de A = AJOUTER(x,fils droit de A)
            retourner A
    sinon retourner A (pas d'ajout)
arbre AJOUTER(element x, arbre A)
    si A est vide
        arbre B = creer un noeud avec l'element x
        A = B;
        renvoyer A
    sinon si x < racine de A
            fils gauche de A = AJOUTER(x,fils gauche de A)
            retourner A
    sinon si x > racine de A
            fils droit de A = AJOUTER(x,fils droit de A)
            retourner A
    sinon retourner A (pas d'ajout)

Ou dit autrement, on cherche l’élément dans l’abre, si on le trouve on ne fait rien, si on arrive à une feuille on insère l’élément comme fils de cette feuille.
Écrivez en C la procédure void add(Elmt_t*,BST* t, int (*compare_elmt)(Elmt_t*, Elmt_t*));
Notez la difficulté ici : dans la procédure donnée, le passage de l’arbre se fait par copie, dans la fonction demandée, il se fait par adresse.

Parcourir l’arbre

int main()
{
    int n1 = 1;
    int n2 = 2;
    int n3 = 3;
    int n4 = 4;
    int n5 = 5;
    
    BST t = create_empty_BST();

    add(&n1,&t,&compare_int);
    add(&n2,&t,&compare_int);
    add(&n3,&t,&compare_int);
    add(&n4,&t,&compare_int);
    add(&n5,&t,&compare_int);
        
    printf("Affichage prefixé : \n");	
    print_prefix(t);/* R G D */
    printf("\n");

    printf("Affichage infixé : \n");	
    print_infix(t); /* G R D */
    printf("\n");
    
    printf("Affichage postfixé : \n");	
    print_postfix(t); /* G D R */
    printf("\n");
    
/* à ajouter plus tard
    printf("Affichage largeur : \n");	
    print_bf(t); 
    printf("\n");
*/
/* NOTE : la mémoire n'est pas libérée ! à suivre */

    return 0;
}
int main()
{
    int n1 = 1;
    int n2 = 2;
    int n3 = 3;
    int n4 = 4;
    int n5 = 5;
    
    BST t = create_empty_BST();

    add(&n1,&t,&compare_int);
    add(&n2,&t,&compare_int);
    add(&n3,&t,&compare_int);
    add(&n4,&t,&compare_int);
    add(&n5,&t,&compare_int);
        
    printf("Affichage prefixé : \n");	
    print_prefix(t);/* R G D */
    printf("\n");

    printf("Affichage infixé : \n");	
    print_infix(t); /* G R D */
    printf("\n");
    
    printf("Affichage postfixé : \n");	
    print_postfix(t); /* G D R */
    printf("\n");
    
/* à ajouter plus tard
    printf("Affichage largeur : \n");	
    print_bf(t); 
    printf("\n");
*/
/* NOTE : la mémoire n'est pas libérée ! à suivre */

    return 0;
}
PARCOURS_LARGEUR(Arbre A)
    FIFO f
    enfiler A dans f
    TANT QUE f non vide :
        Arbre B = défiler f
        enfiler le fils gauche de B dans f 
        enfiler le fils droit de B dans f
        action sur B
PARCOURS_LARGEUR(Arbre A)
    FIFO f
    enfiler A dans f
    TANT QUE f non vide :
        Arbre B = défiler f
        enfiler le fils gauche de B dans f 
        enfiler le fils droit de B dans f
        action sur B

Comme vous le voyez, cette fonction necessite que nous disposions d’une librairie de fifo.
Voici le .h, à vous d’écrire le .c :

#ifndef _FIFO_H
#define _FIFO_H

typedef struct _cell{

    void * data;
    struct _cell * next;

}Cell;

typedef struct{
    Cell* first;
    Cell* last;
}Fifo;

int is_empty(Fifo);
Cell* create_cell(void *); /* contains a malloc */
void free_cell(Cell*); 
Fifo create_fifo();

/* return NULL if memory allocation issue */
Cell* fifo_add(Fifo*, void*);
void* fifo_get(Fifo*); /* call free_cell to free memory */

#endif
#ifndef _FIFO_H
#define _FIFO_H

typedef struct _cell{

    void * data;
    struct _cell * next;

}Cell;

typedef struct{
    Cell* first;
    Cell* last;
}Fifo;

int is_empty(Fifo);
Cell* create_cell(void *); /* contains a malloc */
void free_cell(Cell*); 
Fifo create_fifo();

/* return NULL if memory allocation issue */
Cell* fifo_add(Fifo*, void*);
void* fifo_get(Fifo*); /* call free_cell to free memory */

#endif
dm@dellbookpro:~/sync/ens/4R-IN1/arbres_1$ ./a.out 
                                     001                                      
                  ____________________|___________________                   
                  ---                                    002                  
         __________|__________                  __________|__________         
        ---                 ---                 ---                 003        
    _____|______        _____|______        _____|______        _____|_____    
   ---       ---       ---       ---       ---       ---       ---       004   
 ___|___   ___|___   ___|___   ___|___   ___|___   ___|___   ___|___   ___|__  
---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  005 

Affichage prefixé : 
 1  2  3  4  5 
Affichage infixé : 
 1  2  3  4  5 
Affichage postfixé : 
 5  4  3  2  1 
Affichage largeur : 
 1  2  3  4  5 
dm@dellbookpro:~/sync/ens/4R-IN1/arbres_1$ ./a.out 
                                     001                                      
                  ____________________|___________________                   
                  ---                                    002                  
         __________|__________                  __________|__________         
        ---                 ---                 ---                 003        
    _____|______        _____|______        _____|______        _____|_____    
   ---       ---       ---       ---       ---       ---       ---       004   
 ___|___   ___|___   ___|___   ___|___   ___|___   ___|___   ___|___   ___|__  
---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  005 

Affichage prefixé : 
 1  2  3  4  5 
Affichage infixé : 
 1  2  3  4  5 
Affichage postfixé : 
 5  4  3  2  1 
Affichage largeur : 
 1  2  3  4  5 

Est-ce que cela correspond à ce que vous aviez dessiné ?

arbre ROTATION_GAUCHE(arbre A) {
    arbre B
    B = fils droit de A
    le fils gauche de B devient le fils droit de A (A->d = B->g)
    A devient le fils gauche de B (B->g=A)
    retourne B
}
arbre ROTATION_GAUCHE(arbre A) {
    arbre B
    B = fils droit de A
    le fils gauche de B devient le fils droit de A (A->d = B->g)
    A devient le fils gauche de B (B->g=A)
    retourne B
}
arbre ROTATION_DROITE(arbre A) {
    arbre B
    B = fils gauche de A
    le fils droit de B devient le fils gauche de A (A->g = B->d)
    A devient le fils droit de B (B->d=A)
    retourne B
}
arbre ROTATION_DROITE(arbre A) {
    arbre B
    B = fils gauche de A
    le fils droit de B devient le fils gauche de A (A->g = B->d)
    A devient le fils droit de B (B->d=A)
    retourne B
}

Attention, même difficulté qu’au début du TP, ici on vous demande de faire des procédures en C qui prennent l’arbre par adresse, et non par copie (c’est pouquoi elles ne renvoient rien)

void print_root(BST* t)
{
            printf(" %d ",*((*t)->data));
}
void print_prefix(BST t)
{ visit_prefix(t,&print_root); }

void print_infix(BST t)
{ visit_infix(t,&print_root); }

void print_postfix(BST t)
{ visit_postfix(t,&print_root); }

void print_bf(BST t)
{ visit_bf(t,&print_root); }
void print_root(BST* t)
{
            printf(" %d ",*((*t)->data));
}
void print_prefix(BST t)
{ visit_prefix(t,&print_root); }

void print_infix(BST t)
{ visit_infix(t,&print_root); }

void print_postfix(BST t)
{ visit_postfix(t,&print_root); }

void print_bf(BST t)
{ visit_bf(t,&print_root); }

Remarquez que nous passons par adresse l’arbre a la fonction action, pour permettre à celle ci de le modifier.

void free_BST(BST * t)
{
    visit_postfix(*t,&free_node);
    *t=NULL;
}
void free_BST(BST * t)
{
    visit_postfix(*t,&free_node);
    *t=NULL;
}

il ne vous reste plus qu’à écrire la fonction void free_node(BST * t);
qui libère la mémoire allouée dans create_node et qui remplace la valeur de t par NULL (c’est pour ca qu’on le passe par adresse).

Arbres AVL

Les arbres AVL sont des BST particulier où les opérations d’insertions/délétions garantissent que la différence de hauteur entre les deux sous arbres droit et gauche est au plus 1. Ceci permet d’assurer d’être toujours dans le cas optimal lorsqu’on cherche un élément dans notre abre.

Pour se faire sans payer un cout mémoire ni CPU trop élevé, il faut stocker dans chaque noeud, en plus de la donnée, un entier “facteur d’équilibrage” ou en anglais “balance factor” égale à la hauteur du sous artbre droit moins la hauteur du sous arbre gauche.
Cette valeur dans un arbre AVL ne peut valoir que -1 0 ou 1, et transitoirement -2 ou 2 lors de l’ajout ou la suppression d’un noeud. Elle peut donc être stockée sur 2bits.
Nous utiliserons malgrès tout un int dans la suite pour simplifier.

arbre ROTATION_GAUCHE(arbre A) {
    arbre B
    int a,b
    
    B = fils droit de A
    a = fe de A
    b = fe de B
    
    le fils gauche de B devient le fils droit de A (A->d = B->g)
    A devient le fils gauche de B (B->g=A)
    
    fe de A devient a-max(b,0)-1
    fe de B devient min(a-2,a+b-2,b-1)
    
    retourne B
}
arbre ROTATION_GAUCHE(arbre A) {
    arbre B
    int a,b
    
    B = fils droit de A
    a = fe de A
    b = fe de B
    
    le fils gauche de B devient le fils droit de A (A->d = B->g)
    A devient le fils gauche de B (B->g=A)
    
    fe de A devient a-max(b,0)-1
    fe de B devient min(a-2,a+b-2,b-1)
    
    retourne B
}
arbre ROTATION_DROITE(arbre A) {
    arbre B
    int a,b
    
    B = fils gauche de A
    a = fe de A
    b = fe de B
    
    le fils droit de B devient le fils gauche de A (A->g = B->d)
    A devient le fils droit de B (B->d=A)
    
    fe de A = a-min(b,0)+1
    fe de B = max(a+2,a+b+2,b+1)
    
    retourne B
}
arbre ROTATION_DROITE(arbre A) {
    arbre B
    int a,b
    
    B = fils gauche de A
    a = fe de A
    b = fe de B
    
    le fils droit de B devient le fils gauche de A (A->g = B->d)
    A devient le fils droit de B (B->d=A)
    
    fe de A = a-min(b,0)+1
    fe de B = max(a+2,a+b+2,b+1)
    
    retourne B
}

Important : à l’aide d’un brouillon, vérifiez les calculs des facteurs d’équilibrage

arbre EQUILIBRER(arbre A) {
    si fe de A == 2
        si fe du fils droit de A < 0
            remplacer fils droit de A par résultat de ROTATION_DROITE(fils droit de A)
        retourner le résultat de ROTATION_GAUCHE(A)

    si fe de A == -2
        si fe du fils gaucge de A > 0
            remplacer fils gauche de A par résultat de ROTATION_GAUCHE(fils gauche de A)
        retourner le résultat de ROTATION_DROITE(A)
arbre EQUILIBRER(arbre A) {
    si fe de A == 2
        si fe du fils droit de A < 0
            remplacer fils droit de A par résultat de ROTATION_DROITE(fils droit de A)
        retourner le résultat de ROTATION_GAUCHE(A)

    si fe de A == -2
        si fe du fils gaucge de A > 0
            remplacer fils gauche de A par résultat de ROTATION_GAUCHE(fils gauche de A)
        retourner le résultat de ROTATION_DROITE(A)

La fonction ajouter renvoie maintenant la différence de hauteur produite par l’ajout, afin de pouvoir mettre à jour les facteurs d’équilibrage.

(arbre,entier) AJOUTER(element x, arbre A)
  entier h = 0
    
    si A est vide
        arbre B = creer un noeud avec l'element x
        A = B;
        renvoyer (A,1)
    
    sinon si x < racine de A
            (fils gauche de A,h) = AJOUTER(x,fils gauche de A)
            h = -h
    sinon si x > racine de A
            (fils droit de A,h) = AJOUTER(x,fils droit de A)
        
    si(h == 0) 
        renvoyer(A,0)
        
    fe de A = fe de A plus h
    A = EQUILIBRER(A)
    
    si fe de A == 0
        retourner (A,0)
    retourner (A,1)
(arbre,entier) AJOUTER(element x, arbre A)
  entier h = 0
    
    si A est vide
        arbre B = creer un noeud avec l'element x
        A = B;
        renvoyer (A,1)
    
    sinon si x < racine de A
            (fils gauche de A,h) = AJOUTER(x,fils gauche de A)
            h = -h
    sinon si x > racine de A
            (fils droit de A,h) = AJOUTER(x,fils droit de A)
        
    si(h == 0) 
        renvoyer(A,0)
        
    fe de A = fe de A plus h
    A = EQUILIBRER(A)
    
    si fe de A == 0
        retourner (A,0)
    retourner (A,1)

Comme en C on ne peut renvoyer qu’une valeur, vous savez maintenant pourquoi depuis le début du TP nous passons les abres par leur adresse.
Le prototype de la fonction add devient :
int add(Elmt_t * data, AVL * t, int (*compare_elmt)(Elmt_t*, Elmt_t*));