Cours-ASD-2014

publicité
Carole Blanc
Année 2014-2015



Carole Blanc : [email protected]
Site WEB :
http://dept-info.labri.u-bordeaux.fr/~blanc/ENS/ASD/
4 groupes de TD :
◦ 1-G Mélançon , 2-C. Blanc, 3- C Gavoille, 4- S. Gueorguieva



3 DS en séance de TD (coef 0,4).
1 examen en fin de semestre (coef 0,6).
◦ Note= 0.6*EX +0.4*max(CC, EX)
2 sessions (janvier et juin).





Complexité
Récursivité
Type abstrait
Containeur
Implémentation
Définition 1.1 L'efficacité d'un algorithme est mesurée
par son coût (complexité) en temps et en mémoire.
La complexité d'un algorithme se mesure en
calculant :
◦ le nombre d'opérations élémentaires,
◦ la taille de la mémoire nécessaire,
pour traiter une donnée de taille n.
On considèrera dans ce cours que la complexité des
instructions élémentaires les plus courantes sur un
ordinateur a un temps d'exécution constant égal à 1.
Centre d’intérêt pour l'algorithmique c'est l'ordre de
grandeur au voisinage de l'infini de la fonction qui
exprime le nombre d'instructions ou la taille de la
mémoire.
Question : L’infini de quoi ?
Définition 1.2 On définit les trois complexités
suivantes :
◦ Complexité dans le pire des cas :
C>A(n)=max{CA(d),d donnée de taille n}
◦ Complexité dans le meilleur des cas :
C<A(n)=min{CA(d),d donnée de taille n}
◦ Complexité en moyenne :
C A ( n) 
 Pr (d ) C
A
(d )
d instance de A
où Pr(d) est la probabilité d'avoir en entrée une
instance d parmi toutes les données de taille n.
Cas Particulier : Problème NP-complet
C’est un problème pour lequel on ne connait pas
d'algorithme correct efficace : réalisable en temps et en
mémoire.
L'ensemble des problèmes NP-complets ont les propriétés
suivantes :



Si on trouve un algorithme efficace pour un problème NP
complet alors il existe des algorithmes efficaces pour tous,
Personne n'a jamais trouvé un algorithme efficace pour un
problème NP-complet,
Personne n'a jamais prouvé qu'il ne peut pas exister
d'algorithme efficace pour un problème NP-complet particulier.
Le plus célèbre est le problème du voyageur de commerce.
Définition 1.3:
Lorsqu'un algorithme contient un appel à lui-même,
on dit qu'il est récursif.
Lorsque deux algorithmes s'appellent l'un l'autre on
dit que la récursivité est croisée
Complexité
Un algorithme récursif nécessite de conserver les
contextes récursifs des appels. La récursivité peut
donc conduire à une complexité mémoire plus grande
qu'un algorithme itératif.
Exemple : calcul de la fonction factorielle
:
fonction facRecur(val :entier):entier;
debut
si n==0 alors
retourner(1)
sinon
retourner(n*facRec(n-1))
finsi
fin
finfonction
Exemple : calcul de la fonction factorielle
fonction facIter(val n:entier):entier;
debut
var i,p:entier;
p=1:
pour i=2 à n faire
p=p*i;
finpour
retourner(p)
fin
Finfonction
La fonction factIter est meilleure en temps et mémoire (voir).
Définition 1.4 : Un algorithme récursif présente une
récursivité terminale si et seulement si la valeur
retournée par cet algorithme est une valeur fixe, ou
une valeur calculée par cet algorithme.
L'algorithme facRecur ne présente pas de récursivité
terminale.
L'algorithme facRecur ne présente pas de
récursivité terminale.
fonction facRecur(val n:entier):entier;
debut
si n==0 alors
retourner(1)
sinon
retourner(n*facRec(n-1))
finsi
fin
finfonction
Exemple
fonction facRecurTerm(val n:entier;
val res:entier):entier;
debut
si n==0 alors
retourner(res)
sinon
retourner(facRecurTerm(n-1,n*res))
finsi
fin
finfonction
L'algorithme facRecurTerm présente une récursivité
terminale.
La factorielle se calcule par l'appel facRecurTerm(n,1)
Intérêt : les compilateurs détectent cette propriété et
optimisent le stockage de l'environnement de la
fonction.
Ainsi facRecurTerm aura une complexité identique à
facIter.
ATTENTION.
Dans le cas d'un algorithme présentant deux appels
récursifs, rendre la récursivité terminale ne permet
pas obligatoirement au compilateur d'obtenir une
complexité inférieure.
Définition 1.5 : Un type abstrait est un triplet
composé :
◦ d'un nom,
◦ d'un ensemble de valeurs,
◦ d'un ensemble d'opérations (souvent appelé
primitives) définies sur ces valeurs.
D'un point de vue complexité, on considère que les
primitives (à part celle d'initialisation si elle existe) ont une
complexité en temps et en mémoire en O(1).
Pour désigner un type abstrait on utilise une chaine de
caractères.
Exemple
Les nombres complexes ne sont pas des types de
bases. On peut les définir comme un type abstrait :
◦ nom : nombreComplexe
◦ ensemble de valeur : réel×réel
◦ primitives :
 multiplication :
(nombreComplexe × nombreComplexe) → nombreComplexe
 addition :
(nombreComplexe × nombreComplexe) → nombreComplexe
 module :
nombreComplexe → réel

Fin 1er cours
Définition 1.6 : Un containeur est un type abstrait
permettant de représenter des collections d'objets ainsi
que les opérations sur ces objets.
Les collections que l'on veut représenter peuvent être
ordonnées ou non, numériques ou non.
L'ordre est parfois fourni par un évènement extérieur.
Les collections d'objets peuvent parfois contenir des
éléments identiques.
Primitives :
 Accès
valeur : containeur → objet

Modification
creerContaineur: containeur → vide
ajouter : containeur X objet → vide
supprimer : containeur X objet → vide
detruireContaineur : containeur → vide
Exemple
Un ensemble de nombres complexes peut être défini par
un containeur dont les objets sont des nombreComplexe.
Définition 1.7 : L'implémentation consiste à choisir une
structure de données et les algorithmes associés pour
réaliser un type abstrait
La structure de données utilisée pour l'implémentation
peut elle-même être un type abstrait.
L'implémentation doit respecter la complexité des
primitives à part celle d'initialisation (celle-ci ne
s'exécutera qu'une fois).
Exemple
Le type abstrait nombreComplexe peut être implémenté de la
manière suivante :
nombreComplexe=structure
nombre Complexe
r:réel;
Nom
Type
i:réel;
r
réel
Finstructure
i
var c : nombreComplexe;
Nom variable
c
Type
nombreComplexe
c.r
réel
c.i
réel
réel
Exemple
Le type abstrait Etudiant peut être implémenté de la manière
suivante :
Etudiant=structure
Nom variable Type
nom:chainedecar;
prenom:chainedecar;
E.note
reel
numero:entier
E.numero
entier
note:reel
E.nom
chaine
Finstructure
var E1 : Etudiant;
Var E2 :Etudiant; E1.numero>E2.numero
Exemple
Le type abstrait nombreComplexe peut être implémenté de la
manière suivante :
nombreComplexe=structure
r:réel;
i:réel;
Finstructure
fonction op::*(val a,b:nombreComplexe):nombreComplexe;
var c:nombreComplexe;
debut
c.r=a.r*b.r-a.i*b.i;
c.i=a.r*b.i+a.i*b.r;
retourner(c)
fin
Exemple
fonction op::+(val a,b:nombreComplexe):nombreComplexe;
var c:nombreComplexe;
debut
c.r=a.r+b.r;
c.i=a.i+b.i;
retourner(c)
fin
fonction module(val a:nombreComplexe):réel;
debut
retourner(sqrt(a.r*a.r+a.i*a.i))
fin
Exemple
Un containeur de nombreComplexe peut être
implémenté par un tableau de nombreComplexe.
containeur d'objet=tableau[1..N]de structure
v:objet;
b:booleen;
Finstructure
T
1
2
3
4
5
6
N
v
objet
b
booléen
Modification
fonction creerContaineur(ref C:containeur de
nombreComplexe):vide;
var i:entier;
debut
pour i allant de 1 à N faire
C[i].b=faux;
finPour;
C
fin
1 2 3 4 5
v
…
N
nombreComplexe
b F F F F F F…F F F booléen
Complexité : ?
Modification
fonction ajouter(ref C: containeur de
nombreComplexe;val x:nombreComplexe):vide
var i:entier;
debut
i=1;
tant que i<=N et C[i].b faire
i=i+1;
fintantque
si i<=N alors
C[i].v=x;
C
C[i].b=vrai
1
2
3
4 5
…
N
finsi
fin
v c1 c21 c5 c2
nombreComplexe
b V
V
V
V F F…F F F booléen
Complexité : ?
Modification
fonction supprimer(ref C: containeur de
nombreComplexe;val x:nombreComplexe):vide
var i:entier;
debut
i=1;
tant que i<=N et C[i].v!=x faire
i=i+1;
fintantque
C
si i<=N alors
1
2
3 4 5…
N
C[i].b=faux
v c1 c21 c5 c2
nombreComplexe
finsi
b V
V
F V F…F F F booléen
fin
Complexité : ?
Modification
fonction supprimer(ref C: containeur de
nombreComplexe;val x:nombreComplexe):vide
var i:entier;
debut
i=1;
tant que i<=N faire
si C[i].v == x et C[i].b alors
C[i].b=Faux
break
finsi
i=i+1;
fintantque
fin
Complexité : ?
Modification
fonction detruireContaineur(ref C : containeur
de nombreComplexe):vide
debut
pour i allant de 1 à N faire
C[i].b=faux;
finPour;
fin
Complexité : ?
Accès
fonction valeur(ref C : containeur de
nombreComplexe):nombreComplexe;
/* retourne le 1er nombre complexe présent/*
var i:entier;
2 conditions d’arrêt
debut
i=1;
tant que i<=n et !C[i].b faire
i=i+1;
fintantque
Test en sortie de boucle
si i<=n alors
retourner(C[i].v)
sinon
retourner(NULL)
finsi
fin
Complexité : ?





Définition
Liste simplement chainée
Liste doublement chainée
Implémentation par un tableau du type listeSC
Implémentation par allocation dynamique
du type listeSC
Définition 2.1
 Une liste est un containeur tel que le nombre d'objets
(dimension ou taille) est variable,
 L'accès aux objets se fait indirectement par le
contenu d'une clé qui le localise de type curseur.
Un curseur est un type abstrait dont l'ensemble des valeurs sont des
positions permettant de localiser un objet dans le containeur.
Dans le cas où il n'y a pas de position, la valeur par défaut est NIL.
Si c est un curseur, les primitives considérées dans ce cours sont les
suivantes :
 accès à l'élément désigné par le curseur :
contenu(c): curseur → objet
 accès à la valeur du curseur :
getCurseur(c) : curseur → valeur_de_curseur
 positionnement d'un curseur :
setCurseur(c,valeur) : curseur X valeur_de_curseur → vide
 existence d'un élément désigné par le curseur :
estVide(c) : curseur → {vrai,faux}
La manipulation des éléments de la liste dépend des
primitives définies comme s'exécutant en temps O(1).
Définition 2.2 Une liste est dite simplement chainée si les opérations
suivantes s'effectuent en O(1).
accès
fonction valeur(val L:liste d'objet):objet;
/* si la clé==NIL alors le résultat est NULL */
fonction debutListe(ref L:liste d'objet);
/* positionne la clé sur le premier objet de la liste */
fonction suivant(ref L:liste d'objet);
/* avance la clé d'une position dans la liste */
fonction listeVide(val L:liste d'objet): booleen;
/* est vrai si la liste ne contient pas d'élément */
fonction getCléListe(val L: liste d'objet):curseur;
/* permet de récupérer la clé de la liste */
Modification
fonction supprimerEnTete(ref L:liste d'objet):vide;
/* supprime un objet en debut de liste, la clé est
positionnée*/ /*sur la tête de liste */
fonction setCléListe(ref L: liste d'objet, val
c:curseur):vide;
/* permet de positionner la clé de la liste*/
fonction detruireListe(ref L:liste d'objet):vide;
Modification
fonction creerListe(ref L:liste d'objet):vide;
fonction insererApres(ref L:liste d'objet, val
x:objet;):vide;
/* insère un objet après
la clé, la clé ne change pas */
fonction insererEnTete(ref L:liste d'objet,
val x:objet):vide;
/* insère un objet en debut de liste, la clé est positionnée
sur la tête de liste */
fonction supprimerApres(ref L:liste
d'objet):vide;
/* supprime
l'objet après
la clé, la clé ne change pas */
Détection fin de liste
fonction estFinListe(val L:liste d'objet):booléen;
debut
retourner(valeur(L)==NULL)
fin
Chercher un élément dans une liste
fonction chercher(ref L:liste d'objet; ref x:objet): booleen;
debut
debutListe(L);
tant que !estFinListe(L) et valeur(L)!=x faire
suivant(L);
fintantque
retourner (!estFinListe(L))
/* la clé vaut NIL ou est positionné sur l'objet */
fin
Finfonction
Complexité:
minimum : O(1)
maximum : O(n)
Supprimer un élément dans la liste s'il existe
fonction supprimer(ref L:liste d'objet; ref x:objet): vide;
var tmp:curseur;
/* on suppose que l'objet se trouve dans la liste */
debut
debutListe(L);
tmp=NIL;
/*on cherche l’objet dans la liste */
tant que !estFinListe(L) et contenu(getCléListe(L))!=x faire
tmp= getCléListe(L);
suivant(L);
fintantque
.
.
.
Supprimer un élément dans la liste s'il existe
fonction supprimer(ref L:liste d'objet; ref x:objet): vide;
.
.
.
/* 2 cas en sortie de la boucle tantque */
si tmp==NIL alors /*la clé est sur la tête de liste */
supprimerEnTete(L)
sinon
setCléListe(L,tmp);
/*la clé est sur l'objet précédent l'objet à supprimer*/
supprimerAprès(L);
finsi
fin
finfonction
Complexité:
minimum : O(1)
maximum : O(n)
fonction supprimer(ref L:liste d'objet; ref x:objet): vide;
var tmp:curseur;
debut
debutListe(L);
tmp=NIL;
tant que !estFinListe(L) et contenu(getCléListe(L))!=x faire
tmp= getCléListe(L);
suivant(L);
fintantque
si tmp==NIL alors
supprimerEnTete(L)
sinon
setCléListe(L,tmp);
supprimerAprès(L)
finsi
fin
finfonction
Exercice:
Réfléchir aux problèmes que soulèvent l'introduction de
getCléListe et surtout setCléListe?
Que faut-il en déduire?
Doit-on vraiment les garder?
Définition 2.2: Une liste doublement chainée est une liste
pour laquelle les opérations en temps O(1) sont celles des
listes simplement chainées auxquelles on ajoute les
fonctions d'accès
fonction finListe(ref L:liste d'objet):vide;
/* positionne la clé sur le dernier objet de la liste */
fonction precedent(ref L::liste d'objet): vide;
/* recule la clé d'une position dans la liste */
Supprimer un élément
fonction supprimer(ref L:liste d'objet; ref x:objet): booleen;
debut
si chercher(L,x) alors
précédent(L);
si valeur(L)!=NULL alors
supprimerAprès(L);
sinon
supprimerEnTete(L)
fin
retourner(vrai)
sinon
retourner(faux)
finsi
fin
Complexité :
finfonction
minimum : O(1)
maximum : O(n)
Chaque élément du tableau est une structure
◦ objet
◦ indexSuivant
Le champ indexSuivant désigne une entrée du tableau.
Ainsi l'accès au suivant est en complexité O(1).
Pour une liste de caractère la zone de stockage peut
donc être décrite par :
stockListe = tableau[1..tailleStock] d'elementListe ;
elementListe=structure
valeur : car;
indexSuivant : entier ;
finstructure;
Dans ce contexte, le type curseur est un entier compris
entre 1 et tailleStock.
Il faut coder la valeur NIL : on peut par exemple choisir 0
La valeur du champ indexSuivant est donc un entier
compris entre 0 et tailleStock.
Le premier élément doit être accessible en O(1), il faut
donc conserver son index.
On peut donc représenter une liste par la structure
suivante :
listeSC_Car=structure
tailleStock:entier;
vListe:stockListe;
premier:curseur;
cle:curseur;
finstructure;
Le tableau de stockage étant grand mais pas illimité, il
faudra prévoir que l'espace de stockage puisse être
saturé.
Primitives d'accès
Ces fonctions sont immédiates.
fonction debutListe(ref L:listeSC_Car):vide;
debut
L.cle=L.premier;
fin
finfonction
fonction suivant(ref L:listeSC_Car):vide;
debut
L.cle=L.vListe[L.cle].indexSuivant;
fin
finfonction
Primitives d'accès
fonction listeVide(ref:listeSC_Car): booléen;
debut
retourner(L.premier==0);
fin
finfonction
Gestion de l'espace de stockage
Pour ajouter un élément, il faut pouvoir trouver un élément "libre"
dans le tableau.
Une solution compatible avec la complexité des primitives consiste à
gérer cet espace de stockage en constituant la liste des cellules
libres (voir un exemple) On modifie donc en conséquence la
description de listeSC_Car :
listeSC_Car=structure
tailleStock:entier;
vListe:stockListe;
premier:curseur;
premierLibre:curseur;
cle:curseur;
finstructure;
Gestion de l'espace de stockage
Par convention, l'espace de stockage sera saturé lorsque l'index
premierLibre vaut 0 (la liste des cellules libres est vide).
On définit donc la fonction de test :
fonction listeLibreVide(ref L:listeSC_Car):booléen;
debut
retourner(L.premierLibre==0);
fin
finfonction
Gestion de l'espace de stockage
On définit deux primitives liées à la gestion de la liste des libres :
1.
mettreCellule : insère une cellule libre en tête de la liste des
cellules libres
L’opération correspondante est de type insererEnTete
fonction mettreCellule (ref L:listeSC_Car,val
P:curseur):vide;
debut
L.vListe[P].indexSuivant=L.premierLibre;
L.premierLibre=P;
fin
finfonction
Gestion de l'espace de stockage
On définit deux primitives liées à la gestion de la liste des libres :
2. prendreCellule : prend la cellule libre en tête de la liste des
cellules libres.
L’opération correspondante est de type supprimerEnTete.
fonction prendreCellule(ref L:listeSC_Car):curseur;
var nouv:curseur;
debut
nouv=L.premierLibre;
L.premierLibre=L.vListe[nouv].indexSuivant;
retourner nouv;
fin
finfonction
Deux primitives de modifications
fonction creer_liste(ref L:listeSC_Car):vide;
var i:curseur;
debut
L.tailleStock=tailleMax;
L.premier=0;
L.premierLibre=1;
pour i allant de 1 à L.tailleStock-1 faire
L.vListe[i].indexSuivant=i+1;
finpour
L.vListe[tailleStock].indexSuivant=0;
L.cle=0;
fin
finfonction
Deux primitives de modifications
fonction insérerAprès(ref L:listeSC_Car;val x:car):booléen;
var tmp,nouv:curseur;
debut
si L.cle==0 ou L.premierLibre==0 alors retourner faux;
sinon
tmp=L.cle;
nouv=prendreCellule(L);
L.vListe[nouv].valeur=x;
L.cle=L.vListe[L.cle].indexSuivant; /*suivant(L)*/
L.vListe[nouv].indexSuivant=L.cle;
L.vListe[tmp].indexSuivant=nouv;
L.cle=tmp;
retourner vrai;
finsi; fin ;
finfonction
Réfléchir aux problèmes que soulèvent l'introduction de
getCléListe et surtout setCléListe? Que déduire? Faut-il
vraiment les garder?
Dans ce choix d’implémentation on a curseur=entier.
Supposons tailleStock=1000. La séquence suivante mènera à une
incohérence.
setCléListe(L,10000);
suivant(L);
Le fait d'avoir introduit ces primitives permet lors de l'utilisation du
type abstrait de modifier la clé de type curseur et donc par la même
de pouvoir rendre la structure de donnée incohérente en cas de
mauvaise utilisation.
Pointeur : Définition et syntaxe
Définition :
Un pointeur est une variable qui contient une adresse mémoire.
Pour déclarer un pointeur on écrit :
nom_pointeur=curseur;
Par convention un pointeur qui ne donne accès à aucune adresse
contient la valeur NIL.
Pour accéder à l'emplacement mémoire désigné par le pointeur on
écrit :
nom_pointeur^
Pointeur : Définition et syntaxe
La primitive new permet d'allouer dynamiquement de la mémoire au
cours d'une exécution. On écrira:
new(nom_pointeur);
Lorsque la mémoire n'est plus utilisée par le pointeur il faut
impérativement la libérer. La primitive delete permet de libérer la
mémoire allouée par l'intermédiaire d'un pointeur, on écrira :
delete(nom_pointeur);
Chaque élément de la liste est une structure :
(valeurElement,pointeurSuivant)
Le champ pointeurSuivant est une adresse en mémoire, par suite,
l'accès au suivant est en complexité O(1).
Dans ce contexte le type curseur est un pointeur vers un élément.
La zone de stockage peut donc être décrite par :
cellule=structure
valeurElement:car;
pointeurSuivant:^cellule;
finstructure;
curseur=^cellule;
La zone de stockage peut donc être décrite par :
cellule=structure
valeurElement:car;
pointeurSuivant:^cellule;
finstructure;
curseur=^cellule;
listeSC_car=structure
premier:curseur;
cle:curseur;
finstructure
NIL correspond donc à l'absence d'élément suivant.

Primitives d'accès
fonction listeVide(val L:listeSC_car):booléen;
debut
retourner L.premier==NIL;
fin
finfonction
listeVide est utilisée si nécessaire avant les autres.
fonction valeur(val L:listeSC_car):car;
debut
retourner L.cle^.valeurElement;
fin
finfonction

Primitives d'accès
fonction premier(val L:listeSC_car):vide;
debut
L.cle=L.premier;
fin;
finfonction
fonction suivant(val L:listeSC_car):vide;
debut
L.cle=L.cle^.pointeurSuivant;
fin
finfonction

Trois primitives de modifications
fonction creer_liste(ref L:listeSC_Car):vide;
debut
L.premier=NIL;
L.cle =NIL;
fin
finfonction

Trois primitives de modifications
On suppose que la clé est au debut de la liste
fonction supprimerEnTete(ref L:listeSC_Car):vide;
var P:curseur;
debut
P=L.premier;
suivant(L);
L.premier=L.cle;
delete(P);
fin
finfonction

Trois primitives de modifications
fonction insererApres(val x:car; ref :listeSC_Car):vide;
var nouv:curseur;
debut
new(nouv);
nouv^.valeurElement=x;
nouv^.pointeurSuivant=L.cle^.pointeurSuivant;
L.cle^.pointeurSuivant=nouv;
fin
finfonction



L'implémentation dans un tableau permet d'avoir un bloc contigu
de mémoire ce qui va minimiser les accès disques. Ceci n'est pas
le cas pour l'implémentation par pointeurs.
L'implémentation dans un tableau nécessite de fixer au préalable
le nombre maximum de cellules qui va contraindre fortement les
applications : la structure de donnée peut avoir beaucoup trop de
cellules ou au contraire trop peu.
L'implémentation par pointeur va être très dépendante de
l'implémentation des modules d'allocation dynamique du langage
choisi





Définitions
Primitives de piles, exemples
Primitives de files, exemples
Implémentation des piles
Implémentation des files
Les piles et les files sont des containeurs dans lesquels on ne peut
accéder qu'à un objet particulier.
Définition 3.1. :
Dans une pile, l’objet accessible est le dernier inséré
(LIFO, Last-In, First-Out).
Définition 3.2:
Dans une file, l‘objet accessible est le plus ancien dans la file
(FIFO, First-In, First-Out).
On écrira pour déclarer des variables :
type_pile=pile de objet;
type_file=file de objet;
Une pile est définie par les opérations suivantes :
accès
fonction valeur(val P:pile de objet):objet;
fonction pileVide(val P:pile de objet):booléen;
modification
fonction creerPile(ref P:pile de objet):vide;
fonction empiler(ref P:pile de objet;
val:objet):vide;
fonction depiler (ref P:pile de objet):vide;
fonction detruirePile(ref P:pile de objet):vide;
fonction listeInverse(ref L:listeSC de objet):listeSC de objet;
var P:pile de objet;
var LR:liste de objet;
debut
creerListe(LR);
creerPile(P);
debutListe(L);
tant que !finListe(L) faire
empiler(P,valeur(L));
suivant(L);
fintantque
insererEnTete(LR,valeur(P))
depiler(P);
tant que non(pileVide(P)) faire
insererApres(LR,valeur(P));
suivant(LR);
depiler(P);
fintantque;
detruirePile(P)
retourner(LR);
fin
finfonction
Une file est définie par les opérations suivantes :
Accès :
fonction valeur(val F:file de objet):objet;
fonction fileVide(val F:file de objet):booléen;
Modification :
fonction creerFile(ref F:file de objet):vide;
fonction enfiler(ref F:file de objet;
val v:objet):vide;
fonction defiler(ref F:file de objet):vide;
fonction detruireFile(ref F:file de objet):vide;
fonction compteFile(ref F:file de entier): entier;
var v,compt:entier;
debut
compt=0;
enfiler(F,0);
tant que valeur(F)!=0 faire
compt=compt+1;
v=valeur(F);
defiler(F);
enfiler(F,v);
fintantque;
defiler(F);
retourner(compt);
fin
finfonction
fonction inverserFile(ref F:file de entier):file d'entier;
var P:pile d'entier;
var FS:file d'entier;
debut
creerPile(P);
tant que non(pileVide(P))
creerFile(FS);
faire
enfiler(F,0);
v=valeur(P);
tant que valeur(F)!=0
enfiler(FS,v);
faire
depiler(P);
v=valeur(F);
fintantque;
defiler(F);
detruirePile(P);
enfiler(F,v);
retourner(FS);
empiler(P,v);
fin
fintantque
finfonction
defiler(F);
Chaque objet de la pile est un élément du tableau.
On doit de plus avoir un champs qui permet d'accéder
au sommet de pile.
pile d'objet=structure
taille:entier;
sommet:entier;
pile:tableau[1..taille] d'objets;
finstructure;
accès
fonction valeur(ref P:pile de objet):objet;
debut
retourner(P.pile[P.sommet]);
fin
finfonction
fonction pileVide(ref P:pile de objet):booléen;
debut
retourner(P.sommet==0);
fin
finfonction
modification
fonction empiler(ref P:pile de objet; x:objet):booleen;
/* l'espace de stockage peut être saturé */
debut
si P.sommet==P.taille alors
retourner(FAUX)
sinon
P.sommet=P.sommet+1;
P.pile[P.sommet]=x;
retourner(VRAI)
finsi
fin
finfonction
modification
fonction depiler(ref P:pile de objet):vide;
debut
P.sommet=P.sommet-1;
fin
finfonction
fonction creerPile(ref P:pile de objet):pile de
objet;
debut
P.sommet=0;
fin
finfonction
Chaque objet de la pile est un objet de la listeSC.
pile d'objet=listeSC de objet;
accès :
fonction valeurPile(ref P:pile de objet):objet;
debut
debutListe(P);
retourner(valeurListe(P));
fin
finfonction
fonction pileVide(ref P:pile de objet):booléen;
debut
retourner(listeVide(P));
fin
finfonction
modification
fonction empiler(ref P:pile de objet; x:objet):vide;
debut
insérerEnTete(P,x)
fin
finfonction
fonction depiler(ref P:pile de objet):vide;
debut
supprimerEnTete(P);
fin
finfonction
modification
fonction creerPile(ref P:pile de objet):vide;
debut
creerListe(P);
fin
finfonction
Chaque objet de la file est un élément du tableau. On
utilise le tableau de manière circulaire avec un
pointeur donnant le premier et un autre donnant le
dernier.
file d'objet=structure
taille : entier;
premier : entier;
dernier : entier;
plein : booléen;
file : tableau[0..taille-1] d'objets;
finstructure;
accès
fonction valeur(ref F:file de objet):objet;
debut
retourner(F.file[F.premier]);
fin
finfonction
fonction fileVide(ref F:file de objet):booléen;
debut
retourner(F.premier==F.dernier & non(F.plein));
fin
finfonction
Modification
fonction enfiler(ref F:file de objet; x:objet):booleen;
debut
si F.plein alors
retourner(FAUX)
sinon
F.file[F.dernier]=x;
F.dernier=(F.dernier+1) mod F.taille;
F.plein=F.dernier==F.premier;
retourner(VRAI)
finsi
fin
finfonction
Modification
fonction creerFile(ref F:file de objet):file de
objet;
debut
F.premier= 0;
F.dernier= 0;
F.plein=FAUX;
fin
finfonction
Modification
fonction defiler(ref F:file de objet):vide;
debut
F.premier=(F.premier+1) mod F.taille;
F.plein=Faux
fin
finfonction
Chaque objet de la file est un objet de la liste_DC car il
faut un accès au dernier.
file d'objet=liste_DC de objet;
Accès :
fonction fileVide(ref F:file de objet):booléen;
debut
retourner(listeVide(F));
fin
finfonction
modification
fonction enfiler(ref F:file de objet; x:objet):vide;
debut
dernier(F);
insererApres(F,x);
fin
finfonction
fonction defiler(ref F:file de objet):vide;
debut
supprimerEnTete(F);
fin
finfonction
modification
fonction creerFile(ref F:file de objet):vide;
debut
creerListe(F);
fin
finfonction
cellule=structure
valeurElement:objet;
pointeurSuivant:^cellule;
finstructure;
curseur=^cellule;
file d'objet=structure
premier:curseur;
dernier:curseur
finstructure
accès
fonction valeurFile(ref F:file de objet):objet;
debut
retourner(F.premier^.valeurElement);
fin
finfonction
fonction fileVide(ref F:file de objet):booléen;
debut
retourner(F.premier==NIL);
fin
finfonction
modification
fonction enfiler(ref F:file de objet; x:objet):vide;
var c:curseur;
debut
new(c);
c^.valeurElement=x;
c^.pointeurSuivant=NIL;
si F.premier==NIL alors
F.premier=c
finsi
F.dernier^.pointeurSuivant=c;
F.dernier=c;
fin
finfonction
modification
fonction defiler(ref F:file de objet):vide;
var c:curseur;
debut
c=F.premier;
si F.premier=F.dernier alors
F.dernier=NIL
finsi
F.premier=c^.pointeurSuivant;
delete(c)
fin
finfonction
modification
fonction creerFile(ref F:file de objet):vide;
debut
F.premier=NIL;
F.dernier=NIL;
fin
finfonction
modification
fonction detruireFile(ref F:file de objet):vide;
debut
tantque !fileVide(F) faire
defiler(F)
fintantque
fin
finfonction







Arbre et arborescence
Arbres binaires
Parcours d'un arbre binaire
Implémentation d'un arbre binaire
Retour sur les arborescences
Parcours d'un arbre planaire
Implémentation d'un arbre planaire
Définition 4.1: Un arbre est un graphe connexe sans cycle.
Un sous arbre est un sous graphe d'un arbre.
Propriété 4.1: Si un arbre a n sommets alors il a n-1 arêtes.
Idée de la démonstration: ceci s'appuie sur les deux propriétés
suivantes des graphes connexes.
 Tout graphe connexe ayant n sommets a au moins n-1 arêtes.
 Tout graphe connexe ayant n sommet et au moins un cycle a
au minimum n arêtes.
La taille d'un arbre est le nombre de sommets de l'arbre.
Propriété 4.2: Entre deux sommets quelconques d'un
arbre, il existe une unique chaîne les reliant.
Idée de la démonstration : Pour deux sommets
quelconques
 Il ne peut exister deux chaines différentes les reliant
sinon il y aurait un cycle dans l'arbre.
 Il existe au moins une chaine puisque un arbre est un
graphe connexe.
Définition 4.2: Une arborescence est définie à partir
d'un arbre en choisissant un sommet appelé racine et
en orientant les arêtes de sorte qu'il existe un chemin
de la racine vers tous les autres sommets.
Arbre
Arborescence
Définitions 4.3 :




On appelle fils d'un sommet s tout sommet s' tel que (s,s')
est une arête de l'arbre.
On notera qu'une arborescence est un exemple d'ensemble
partiellement ordonné (relation d'ordre "fils de").
On appelle feuille de l'arbre un sommet qui n'a pas de
successeur . Tout autre sommet est appelé sommet interne.
On appelle hauteur d'un sommet de l'arbre la longueur du
chemin de la racine à ce sommet.
Définition 4.4: Un arbre planaire est défini en ordonnant
les arêtes sortantes de chaque sommet. On notera qu'un
arbre planaire est un exemple d'ensemble totalement
ordonné (relation "est fils ou est frère à droite").
Définition 4.5: Un arbre binaire est un arbre planaire dont
chaque sommet a au plus deux fils.
Définition 4.6: Un arbre binaire complet est un arbre
binaire dont chaque sommet interne a exactement deux
fils. .
Arbre planaire
Racine
Premier Fils
Frère droit
Arbre Binaire
Arbre Binaire complet
Propriété 4.3: Tout sommet x d'un arbre binaire vérifie
l'une des deux propriétés suivantes:
 x est une feuille,
 x a un sous arbre binaire dit gauche de racine G(x) et
un sous arbre binaire droit de racine D(x).
X
feuille
G(X)
D(X)
Définition 4.7: Un arbre binaire parfait est un arbre
binaire complet dans lequel toutes les feuilles sont à la
même hauteur dans l'arbre.
Théorème 4.1: Un arbre binaire de taille n a une hauteur
moyenne de log2(n).
Théorème 4.2: Il existe une bijection qui transforme un
arbre planaire ayant n sommet en un arbre binaire
complet ayant 2n+1 sommets.
Du fait de ce théorème, on ne considère dans un premier
temps que le type arbre binaire que l'on nommera
arbreBinaire.
Chaque sommet permet d'accéder à deux sommets :
 le fils gauche .
 le fils droit.
Ce type sera nommé sommet.
Chaque sommet permet également d'accéder à l'objet
qu'il stocke.
Un arbre binaire peut être vu comme un curseur
indiquant le sommet racine. De la même manière un
sommet est un curseur. On a donc :
arbreBinaire=curseur;
sommet=curseur;
Le type sommet présente les primitives suivantes :
Accès :
fonction getValeur(val S:sommet):objet;
/* vaut NULL si le sommet n'existe pas /*
fonction filsGauche(val S:sommet):sommet;
/* vaut NIL si S n'a pas de fils gauche */
fonction filsDroit(val S:sommet):sommet;
/* vaut NIL si S n'a pas de fils droit */
fonction pere(val S:sommet):sommet;
/* vaut NIL si S est la racine de l'arbre */
Modification :
fonction setValeur(ref S:sommet;val x:objet):vide;
/* affecte au sommet S la valeur x */
fonction ajouterFilsGauche(ref S:sommet,val x:objet):vide;
/* filsGauche(S)==NIL doit être vérifié */
fonction ajouterFilsDroit(ref S:sommet,x:objet):vide;
/* filsDroit(S)==NIL doit être vérifié */
fonction supprimerFilsGauche(ref S:sommet):vide;
/* filsGauche(S) est une feuille */
fonction supprimerFilsDroit(ref S:sommet):vide;
/* filsDroit(S) est une feuille */
fonction detruireSommet(ref S:sommet):vide;
/* S est une feuille */
Modification :
fonction creerArbreBinaire(val Racine:objet):sommet;
fonction detruireArbreBinaire(ref A:arbreBinaire d'objet):vide;
Détection de feuille
fonction estFeuille(val S:sommet):booléen;
debut
retourner(filsGauche(S)==NIL et filsDroit(S)==NIL)
fin
Le parcours d'un arbre binaire consiste à donner une liste de sommets dans
l'arbre.
Le prototype d'algorithme suivant permet d'effectuer les parcours selon les
algorithmes associés aux traitements à partir d'un sommet de l'arbre.
1e 3
1 r3
1a 3
2
2
1 i 3
1z 3
2
2
2
1g 3
2
1m 3
2
fonction parcoursArbreBinaire(val A:arbreBinaire d'objet):vide;
//
Déclarations locales
debut
//
traitement1;
si estFeuille(A)alors
//
traitement2;
sinon
//
traitement3;
si filsGauche(A)!=NIL alors
//
traitement4;
parcoursArbreBinaire(filsGauche(A));
//
traitement5;
finsi
//
traitement6;
si filsDroit(A)!=NIL alors
//
traitement7;
parcoursArbreBinaire(filsDroit(A));
//
traitement8;
finsi
//
traitement9;
finsi
//
traitement1O;
fin
Complexité:
Si le traitementi a pour complexité ci(n), soit c(n) =
La complexité intrinsèque de l'algorithme est :
10
𝑖=1 𝑐𝑖 (𝑛)
O(n c(n)).
On distingue quatre parcours qui conditionnent les algorithmes sur
les arbres binaires.
 Le parcours hiérarchique ou en largeur
 Parcours préfixe : on affiche la racine, les sommets du sous arbre
gauche, puis les sommets du sous arbre droit.
 Parcours infixe: on affiche les sommets du sous arbre gauche,
puis la racine puis les sommets du sous arbre droit.
 Parcours suffixe : on affiche les sommets du sous arbre gauche,
puis les sommets du sous arbre droit puis la racine.
1e 3
1r 3
2
1g 3
2
2
1a 3 1 i 3
1m 3
2
2
2
1z 3
2
Parcours hiérarchique
Parcours préfixe
Parcours infixe
Parcours postfixe
:
:
:
:
e,r,g,a,i,m,z
e,r,a,i,z,g,m
a,r,z,i,e,g,m
a,z,i,r,m,g,e
Affichage des valeurs des sommets pour un parcours donné
Soit un arbre étiqueté par des caractères. On considère que l'on
dispose de la fonction qui affiche un caractère :
fonction afficher(val n:entier):vide;
Affichage dans le parcours préfixe
pas de déclarations locales.
traitement 2, 3 : afficher(valeur(A));
Affichage dans le parcours infixe :
pas de déclarations locales.
traitement 2, 6: afficher(valeur(A));
Affichage dans le parcours postfixe
pas de déclarations locales.
traitement 2, 9 : afficher(valeur(A));
Lister les étiquettes d'un arbre dans un tableau
fonction arbre2Tableau(val A:arbreBinaire d'entier;
ref T:tableau[1..N] d'entier; ref i:entier):vide;
debut
i=i+1;
T[i]=valeur(A);
si !estFeuille(A)alors
si filsGauche(A)!=NIL alors
arbre2Tableau(filsGauche(A),T,i);
finsi
si filsDroit(A)!=NIL alors
arbre2Tableau(filsDroit(A),T,i);
finsi
finsi
fin
Hauteur d'un arbre binaire
fonction hauteurArbreBinaire(val s:sommet):entier
debut
si estFeuille(s)alors
retourner(0)
sinon
var tmp1,tmp2:entier;
tmp1=0;
tmp2=0;
si filsGauche(s)!=NIL alors
tmp1= hauteurArbreBinaire(filsGauche(s));
finsi
si filsDroit(s)!=NIL alors
tmp2=hauteurArbreBinaire(filsDroit(s));
finsi
retourner(1+max(tmp1,tmp2));
finsi
fin
Hauteur d'un arbre binaire : Autre version
fonction hauteurArbreBinaireSimp(val s:sommet):entier
debut
si s==NIL alors
retourner(-1)
sinon
retourner(1+
max(hauteurArbreBinaireSimp(filsGauche(s)),
hauteurArbreBinaireSimp(filsDroit(s))));
finsi
fin
Remarque : La fonction hauteurArbreSimp (même si elle est plus
courte) a une complexité plus grande que la fonction
hauteurArbreBinaire notamment en nombre d'appel récursif. Elle
est moins performante.
Taille d'un sous-arbre d'un arbre binaire complet.
fonction tailleArbreBinaire(val A: arbreBinaire):entier;
debut
si estFeuille(A) alors
retourner(1)
sinon
retourner(1+tailleArbreBinaire(filsGauche(A))
+tailleArbreBinaire(filsDroit(A))
finsi
fin
L’implémentation se fait par allocation dynamique. On
définit
cellule=structure
info:objet;
gauche:sommet;
droit:sommet;
pere:sommet;
finstructure
sommet=^cellule;
gauche info
droit
Pere
accès
fonction getValeur(val S:sommet):objet;
debut
retourner(S^.info);
fin
fonction filsGauche(val S:sommet):sommet;
debut
retourner(S^.gauche)
fin
modification
fonction creerArbreBinaire(val racine:objet):sommet;
var tmp:sommet;
debut
new(tmp);
tmp^.info=racine;
tmp^.gauche=NIL;
tmp^.droit=NIL;
tmp^.pere=NIL;
retourner(tmp)
fin
modification
fonction ajouterFilsGauche(ref S:sommet,val x:objet):vide;
var tmp:sommet;
debut
new(tmp);
tmp^.info=x;
tmp^.gauche=NIL;
tmp^.droit=NIL;
tmp^.pere=S;
S^.gauche=tmp;
fin
modification
fonction supprimerFilsGauche(ref S:sommet):vide;
var tmp:sommet;
debut
tmp=S^.gauche;
S^.gauche=NIL;
delete(tmp);
fin
modification
fonction detruireArbreBinaire(ref A:arbreBinaire d'objet):vide;
debut
si estFeuille(A)alors
delete(A)
sinon
si filsGauche(A)!=NIL alors
detruiresArbreBinaire(filsGauche(A));
finsi
si filsDroit(A)!=NIL alors
detruireArbreBinaire(filsDroit(A));
finsi
delete(A);
finsi
fin
On peut définir un type abstrait sommetArbrePlanaire par les
primitives suivantes:
accès
fonction getValeur(val S:sommetArbrePlanaire):objet;
fonction premierFils(val S:sommetArbrePlanaire):sommetArbrePlanaire;
fonction frere(val S:sommetArbrePlanaire):sommetArbrePlanaire;
fonction pere(val S:sommetArbrePlanaire):sommetArbrePlanaire;
modification
fonction creerArbrePlanaire(val racine:objet):
sommetArbrePlanaire;
fonction ajouterFils(ref S:sommetArbrePlanaire,
val x:objet):vide;
/* ajoute un fils comme cadet */
fonction supprimerSommet(ref S: sommetArbrePlanaire):vide;
/* le sommet doit être une feuille */
fonction detruireArbrePlanaire(ref S:
sommetArbrePlanaire):vide;
Un arbre planaire est de type sommetArbrePlanaire.
C'est un curseur.
Le parcours d'un arbre planaire consiste à donner une liste de tous
les sommets.
fonction parcoursArbrePlanaire(val A:sommetArbrePlanaire):vide;
// Déclarations locales
var f: sommetArbrePlanaire;
debut
// traitement1;
f= premierFils(A);
tant que f!=NIL faire
// traitement2;
parcoursArbrePlanaire(f);
// traitement3;
f=frere(f);
// traitement4
fintantque
// traitement5
fin
On distingue trois parcours qui conditionnent les algorithmes sur les
arbres planaires :



Le parcours hiérarchique qui s'effectue grâce à une file.
Le Parcours préfixe : on liste la racine, les sommets de chaque
sous arbre dans l'ordre où les sous arbres apparaissent.
Le Parcours suffixe : on liste les sommets des sous arbres en
ordre inverse puis la racine.
6
7
3
1
12
2
0
4
10
9
8
5
Implémentation dans le type arbreBinaire
L'implémentation dans le type arbreBinaire découle du théorème
4.2
On a alors
arbrePlanaire=arbreBinaire.
Pour un noeud donné :
 la primitive filsGauche donne accès au premier fils du noeud.
 la primitive filsDroit donne accès au frère du noeud.
 la primitive pere donne accès soit au père du noeud soit à son
frère précédent.
Il faut donc redéfinir la primitive pere pour les arbres planaires.
fonction pereArbrePlanaire(val S:
sommetArbrePlanaire):sommetArbrePlanaire;
var T: sommetArbrePlanaire;
debut
T=filsGauche(pere(S));
tant que T!=S faire
S=pere(S)
T=filsGauche(pere(S));
fintantque
retourner(pere(S))
fin
En utilisant le théorème 4.1, cette dernière primitive à une complexité
moyenne O(log2n).
Implémentation par allocation dynamique
Cette implémentation permet de diminuer le temps d'accès au père.
cellule=structure
info:objet;
premierFils:sommet;
frere:sommet;
pere:sommet;
Finstructure
Dans cette implémentation, on initialise parfois le champ frere du
dernier frère à l'adresse du premier fils.
On peut vérifier aisément que les primitives sont toutes réalisables en
O(1).
L'espace mémoire est le même que celui occupé par une
implémentation dans le type arbreBinaire
Accès :
fonction getValeur(val s:sommetArbrePlanaire):objet;
début
retourner(s^.info)
fin
fonction premierFils(val s:sommetArbrePlanaire):
sommetArbrePlanaire;
début
retourner(s^.premierFils)
fin
Accès :
fonction frere(val s : sommetArbrePlanaire) :
sommetArbrePlanaire;
début
retourner(s^.frere)
fin
fonction pere(val s : sommetArbrePlanaire):
sommetArbrePlanaire;
début
retourner(s^.pere)
fin
Modification :
fonction creerArbrePlanaire(val racine:objet):sommetArbrePlanaire;
var tmp:^cellule;
début
new(tmp);
tmp^.info=racine;
tmp^.premierFils=NIL;
tmp^.frere=NIL;
tmp^.pere=NIL;
retourner(tmp)
fin
Modification :
fonction detruireArbrePlanaire(ref s:sommetArbrePlanaire):vide;
var tmp,f: sommetArbrePlanaire;
début
f= premierFils(s);
tant que f!=NIL faire
detruireArbrePlanaire(f);
f=frere(f);
fintantque
supprimerSommet(s)
fin
fonction ajouterFils(ref s:sommetArbrePlanaire,val x:objet):vide;
/* ajoute un fils comme cadet
cette fonction n'est pas en O(1) */
var tmp:^celluleAP;
début
new(tmp);
tmp^.info=x;
tmp^.frere=NIL;
tmp^.pere=s;
tmp^.premierFils=NIL
si s^.premierFils==NIL alors
s^.premierFils=tmp;
sinon
r=premierFils(s);
tantque frere(r)!=NIL faire
r=frere(r)
fintantque
r^.frere=tmp
fin
fonction supprimerSommet(ref s:sommetArbrePlanaire):vide;
/* le sommet doit être une feuille */
var p,r,tmp:^celluleAP;
début
r=premierFils(pere(s));
si r==s alors
p=pere(s);
p^.premierFils=s^.frere;
sinon
tantque frere(r)!=s faire
r=frere(r);
fintantque
r^.frere=s^.frere;
finsi
delete(s)
fin



Arbre binaire de recherche
Modification d'un arbre binaire de recherche
Equilibrage
Définition 5.1 : Dans un arbre binaire de recherche, quel que
soit x un sommet interne d'étiquette val(x), soit LG(x) (resp.
LD(x)), l'ensemble des étiquettes du sous arbre gauche (resp.
droit) de x. On a :
∀𝑦 ∈ 𝐿𝐺 𝑥 , ∀𝑧 ∈ 𝐿𝐷 𝑥 , 𝑦 ≤ 𝑣𝑎𝑙(𝑥) < 𝑧
Propriété 5.1: Si on parcourt un arbre binaire de recherche en
ordre infixe, on obtient une séquence d'étiquettes triées en
ordre croissant.
Corollaire 5.1: Si n est le nombre de sommets d'un arbre
binaire de recherche, on obtient la liste triée en O(n).
On utilise les primitives des arbres binaires.
fonction recherche(val x:sommet, val e:objet):sommet;
var tmp:objet;
debut
si x==NIL alors
retourner(NIL)
sinon
tmp= getValeur(x);
si tmp==e alors
retourner(x);
sinon
si e <=tmp alors
retourner(recherche(filsGauche(x),e));
sinon
retourner(recherche(filsDroit(x),e));
finsi
finsi
Complexité
finsi
minimum :
fin
maximum :
fonction recherche(val x:sommet, val e:objet):sommet;
var tmp:objet;
debut
si x==NIL alors
retourner(NIL)
sinon
tmp= getValeur(x);
si tmp==e alors
retourner(x);
sinon
si e <=tmp alors
retourner(recherche(filsGauche(x),e));
sinon
retourner(recherche(filsDroit(x),e));
finsi
finsi
Complexité
finsi
minimum :
fin
maximum :
fonction cherchePlusPetit(val x:sommet):sommet;
debut
tantque filsGauche(x)!=NIL faire
x=filsGauche(x);
fintantque
retourner(x);
fin
Complexité
minimum :
maximum :
Dans l’ordre croissant, où se trouve le suivant d’un élément x
dans un ABR ?
8
14
6
10
2
0
4
3
27
18
5
32
fonction chercheSuivant(val x: sommet,
val e : objet):sommet;
sinon
var p:sommet;
p=pere(x);
debut
tantque p!=NIL faire
x=cherche(x,e);
si filsGauche(p)==x alors
si x==NIL alors
retourner(p)
retourner(NIL);
sinon
sinon
x=p;
si filsDroit(x)!=NIL alors
p=pere(p);
return(cherchePlusPetit(filsDroit(x))
Complexité
minimum :
maximum :
finsi
fintantque
retourner(NIL);
finsi
finsi
fin
Les primitives ajouter et supprimer des objets permettent de faire évoluer un
ABR.
fonction ajouter(ref x:sommet,
sinon
val e:objet):vide;
s=filsDroit(x);
var s:sommet;
si s==NIL alors
debut
ajouterFilsDroit(x,e);
si e <= valeurSommet(x) alors
sinon
s=filsGauche(x);
ajouter(s,e);
finsi
si s==NIL alors
finsi
ajouterFilsGauche(x,e);
fin
sinon
ajouter(s,e);
finsi
Complexité
minimum :
maximum :
fonction supprimer(ref x:sommet):vide;
/*Tous les elts sont différents */
sinon
var p,f,y:sommet;
f=filsDroit(x);
debut
si f!=NIL
si estFeuille(x) alors
y=cherchePlusPetit(f);
p=pere(x);
sinon
/*traiter le cas racine*/
f=filsGauche(x);
si filsGauche(p)==x alors
y=cherchePlusGrand(f);
finsi
supprimerFilsGauche(p)
var v : valElement;
sinon
v=getValeur(y);
supprimerFilsDroit(p)
supprimer(y);
finsi
setValeur(x,v);
finsi
fin
Complexité
minimum :
maximum :
Dans le cas général la fonction supprimer applique
l’algorithme suivant :
Si le sommet à supprimer :
 est une feuille on l'enlève
 a 1 fils on le remplace par son fils
 a 2 fils on remplace sa valeur par la valeur
précédente dans l’ordre croissant et on
supprime le sommet qui portait cette valeur
dans l’ABR.
La complexité des opérations sur un ABR dépendant de la hauteur
de l'arbre, il est important qu'un ABR reste aussi proche que possible
d'un arbre binaire parfait de manière à ce que la hauteur soit
minimum.
L'équilibrage d'un ABR peut-être obtenu par un algorithme de type
"diviser pour régner".
On récupère la liste des éléments triés dans un tableau T[1..N] où N
est la taille de l'arbre de départ et on reconstruit l'arbre.
fonction lister(val x:sommet, ref T:tableau[1..N] d'objet,
ref i:entier):vide
debut
si estFeuille(x)alors
i=i+1;
T[i]= getValeur(x);
sinon
si filsGauche(x)!=NIL alors
lister(filsGauche(x),T,i);
finsi
i=i+1;
T[i]= getValeur(x);
si filsDroit(x)!=NIL alors
lister(filsDroit(x),T,i);
finsi
finsi
fin
L'appel : liste(A,T,0) fournit, en utilisant l'ordre infixe, dans le tableau T
la liste des valeurs dans l'ordre croissant de l'arbre A.
fonction detruireArbreBinaire(ref A:arbreBinaire
d'objet):vide;
debut
si s!=NIL alors
detruireArbreBinaire(filsGauche(s));
supprimerFilsGauche(s);
detruireArbreBinaire(filsDroit(s));
supprimerFilsDroit(s);
finsi
fin
fonction equilibre(ref A:arbreBinaire de objet):vide;
var N:entier;
var T:tableau[1..N]d'objet;
debut
N=tailleArbre(A);
lister(A,T,0);
detruireArbreBinaire(A);
delete(A);
A=construire(T,1,N))
fin
fonction construire(ref T:tableau[1..N]d'objet,ref
d,f:entier):sommet;
var m:entier;
var c,s:sommet;
si s!=NIL alors
debut
s^.pere=c;
si d≤f alors
finsi
m=(d+f)//2;
s=construire(m+1,f);
new(c);
c^.droit=s
setValeur(c,T[m]);
si s!=NIL alors
si d==f alors
s^.pere=c;
c^.gauche=NIL;
finsi
c^.droit=NIL;
finsi
retourner(c);
retourner(c);
sinon
sinon
s=construire(d,m-1);
retourner(NIL)
c^.gauche=s;
finsi
fin




Définition
Ajouter et supprimer dans un tas Max
Implémentation
Files de priorité
Définition 6.1 : Un tas max (resp.tas min) T est un arbre binaire
quasi-parfait étiqueté par des objets comparables (ie : il existe un
ordre total) tel que tout nœud a une étiquette plus grande ou égale
(resp. plus petite) que ses fils.
Propriété 6.1 : La hauteur d'un tas est O(log(n)).
L’ajout d’un élément se fait en conservant la structure ABQP
Un tas est un containeur et un arbre binaire, il dispose donc des
primitives des arbres binaires ainsi que ceux d'un containeur :
fonction valeur(ref T:tas d'objet): objet;
// renvoie l'objet stocké à la racine de l'arbre
fonction ajouter(ref T:tas de objet, val v:objet):vide;
// ajoute l'objet dans le tas
fonction supprimer(val T:tas de objet):vide;
// suppression de la racine et tassement de l'arbre
fonction creerTas(ref T:tas,val:v:objet):vide;
fonction detruireTas(ref T:tas):vide;
Pour ajouter une valeur v dans un tas, on crée une nouvelle
feuille dans l'arbre quasi-parfait en lui affectant la valeur v.


Soit (r=s0,...,sk) le chemin de la racine à cette nouvelle
feuille.
Pour i allant de k à 1 si la valeur stockée dans si est plus
grande que celle stockée dans si-1 alors on échange ces
valeurs. On fait une réorganisation montante : un
exemple est ici
Dans un tas supprimer un élément consiste toujours à
supprimer la racine. Pour supprimer la valeur dans un tas,
on remplace la valeur de la racine par la valeur v de la
dernière feuille de l'arbre.


On supprime cette feuille.
On fait descendre la valeur v dans l'arbre par échange
avec la valeur la plus grande d'un des fils si celle ci est
plus grande. On fait une réorganisation descendante : un
exemple est ici
On utilise la numérotation des nœuds dans le parcours
hiérarchique d’un AB.
La racine est numérotée 1.
Le fils gauche de la cellule numéro i a pour numéro 2*i
Le fils droit de la cellule numéro i a pour numéro 2*i +1
1
3
2
4
5
6
7
8
9
6
27
2
0
10
7
32
5
1
2
3
4
8
6
9
2
5
6
7
8
9
27
0
10
10
11
12
13
14
15
7
32
16
17
18
19
5
20
21
On utilise cette propriété pour représenter un tas dans un tableau.
De plus dans les opérations d'ajout et suppression des valeurs, on
devra pouvoir parcourir l'arbre. Un curseur sera donc utile.
tas=structure
arbre:tableau[1..tailleStock] d'objet;
tailleTas:entier;
finstructure;
curseur=entier;
sommet=entier;
De ce fait, les primitives arbre binaire prennent comme
paramètre un tas et non un sommet.
Les fonction ajouter et supprimer sont spécifiques au tas.
 accès
fonction getValeur(ref T:tas d'objet; Val s:sommet):objet;
debut
retourner(T.arbre[s]);
fin;
fonction valeur(ref T:tas d'objet):objet;
debut
retourner(T.arbre[1]);
fin
fonction filsGauche(val s:sommet):sommet;
debut
retourner(2*s);
fin
Les fonction ajouter et supprimer sont spécifiques au tas.
 accès
fonction filsDroit(val s:sommet):sommet;
debut
retourner(2*s+1);
fin
fonction pere(val s:sommet):sommet;
debut
retourner(partieEntiere(s/2));
fin
fonction tasPlein(ref T:tas d'objet):booleen;
debut
retourner(T.tailleTas==tailleStock)
fin

modification
fonction setValeur(ref T:tas d'objet; val s:sommet;
val x:objet):vide;
debut
T.arbre[s]=x;
fin
fonction creerTas(ref T:tas d'objet; val x:objet):vide;
debut
T.arbre[1]=x;
T.tailleTas=1;
fin

Gestion du tas
fonction ajouter(ref T:tas d'objet, val
v:entier):vide
debut
T.tailleTas=T.tailleTas+1;
T.arbre[T.tailleTas]=v;
reorganiseTasMontant(T,tailleTas);
fin

Gestion du tas
fonction reorganiseTasMontant(ref T: tas d'objet;
val x:sommet):vide;
var p:sommet;
var signal:booléen;
debut
p=pere(x);
signal=vrai;
tantque x!=1 et signal faire
si getValeur(T,x)>getValeur(T,p) alors
échanger(T.arbre[p],T.arbre[x])
x=p;
p=pere(x);
sinon
signal=faux
finsi
fintantque
fin

Gestion du tas
fonction supprimer(ref T:tas d'objet):vide;
var r:objet;
debut
T.arbre[1]=T.arbre[T.tailleTas];
T.tailleTas=T.tailleTas-1;
reorganiseTasDesc(T,1);
fin

Gestion du tas
fonction reorganiseTasDesc(ref T:tas d'objet,
val x:sommet):vide;
var g,d:sommet;
debut
g= filsGauche(x);
d= filsDroit(x);
si g!=NIL alors
si d!=NIL alors
si getValeur(T,d)>getValeur(T,g) alors
g=d;
finsi;
finsi
si getValeur(T,x)<getValeur(T,g) alors
échanger(T.arbre[x],T.arbre[g]);
reorganiseTasDesc(T,g)
finsi
finsi
Définition 6.2 : Une file de priorité est un tas dans lequel on a la possibilité de
modifier les valeurs des sommets.
On dispose donc d'une primitive supplémentaire :
fonction changeValeur(ref T:tas d'objet, val s:sommet,
val v:objet):vide;
debut
setValeur(T,s,v);
si v > getValeur(T,pere(s)) alors
reorganiseTasMontant(T,s)
sinon
si v < getValeur(T,filsDroit(s))
ou v < getValeur(T,filsGauche(s)) alors
reorganiseTasDesc(T,s)
finsi
Complexité :
finsi
fin





Définitions
Fonction de hachage
Adressage chainé
Adressage ouvert
Réorganisation d'une table de hachage
Soient a et b deux entiers.
Soient q et r respectivement leur quotient et
reste dans la division Euclidienne, on a :
a=b*q + r.
On dit que a est égal à r modulo b et on écrira
a=r[b].
Définition 7.1 :
Soit K un ensemble de valeurs et m un entier
naturel, une fonction de hachage hm est une
fonction définie de K dans {0,...,m-1}.
hm : K
{0,...,m-1}


Les éléments de K sont appelées clés.
Si k est une clé, h(k) est dite valeur de
hachage de k.
Définition 7.2 :
Soit m un entier naturel et hm une fonction de
hachage, une table de hachage est un
containeur tel que :
 le nombre d'éléments de la table (dimension
ou taille) est fixe,
 l'accès aux éléments s'effectue indirectement
par la valeur de hachage de la clé.
Table de hachage

Exemple
0
K
1
2
k0
ki
…
hm(ki)
kj
ki
m-1
Remarques
 Les tables de hachage sont très utiles dans le cas où
l'amplitude des clés est grande (structure de tableau non
utilisable) et les éléments à gérer sont "assez figés" ce qui
permet d'espérer un temps d'accès à l'information proche
de O(1) (structure de liste trop pénalisante).


Pour une séquence d'élément, il est clair qu'il peut exister
deux clés k1 et k2 de K telles que hm(k1)=hm(k2), on dit
alors qu'il y a collision des clés k1 et k2.
Soit p dans [0..m-1], il est possible qu'il n'existe pas de
clé k dans K telle que hm(k)=p.



La structure de données pour représenter une table
de hachage est un tableau de dimension [0..m-1].
Pour une clé k de K, T[hm(k)] donne l'accès à
l'élément de clé k.
Plus qu'à la clé elle-même le plus souvent on
souhaite accéder aux informations associées à la clé.
La clé est donc stockée ainsi que l'adresse
permettant d'accéder aux informations.
On précisera ces moyens d'accès aux paragraphes
concernant la gestion des collisions : adressage
chainé , adressage ouvert.



On nommera le type abstrait tableHash.
La valeur de hachage d'une clé est un curseur. Les
primitives d'accès à une table de hachage sont :
accès
fonction chercher(ref T:tableHash_de_cle, val v:cle):curseur;

modification
fonction creerTablehachage(ref T: tableHash_de_cle,
ref h:fonction):vide;
fonction ajouter(ref T:tableHash de cle, val x:cle):booleen;
fonction supprimer(ref T:tableHash de cle, val :cle):vide;
fonction detruireTablehachage(ref T:tableHash de cle): vide;
Elles sont définies en fonction de hm et du mode de gestion des
collisions.
Les fonctions de hachage doivent pouvoir s'appliquer sur des objets de
n’importe quel type de base (entier, car, réel, etc.)
Les types numériques peuvent aisément être ramené à un entier.
Pour les chaines de caractères, il est toujours possible de leur associer
de manière bijective un entier.
Considérons la fonction asc qui a un caractère associe son code
ASCII (codé sur 7 bits). Soit c0...cp une suite de p caractères alors la
valeur entière associée bijectivement est :
𝑝
𝑎𝑠𝑐 𝑐𝑖 ∗ 128𝑖
𝑖=0
Dans la suite on ne s'intéressera donc qu'à des clés entières.
Méthode de division
Soit m un entier premier pas trop proche d'une
puissance de 2 :

∀ 𝑘 ∈ ℕ, ℎ𝑚 𝑘 = 𝑘[𝑚]

Méthode de multiplication
Soit m=2p et A dans ]0..1[,
∀ 𝑘 ∈ ℕ, ℎ𝑚,𝐴 𝑘 = (𝑚 𝑘𝐴 − 𝑘𝐴 )
Cette méthode fonctionne mieux pour certaines valeurs de A
que pour d’autres. D.E. Knuth a étudié le problème et suggère
5−1
𝐴~
2
Famille de hachage universel
Définition 7.3 : Soit H un ensemble de fonction de
hachage de U dans [0..m[. On dit que la famille H est
universelle si pour toutes clés k1, k2 dans U, le nombre de
fonctions h de H telles que h(k1)=h(k2) est égal à
card(H)/m.
Probabilité de collision : 1/m
Soit m un entier naturel. Soit p un nombre premier grand
tel que :
∀𝑘 ∈ 𝐾, 𝑘 ∈ 0 … 𝑝 − 1
On définit la famille de fonction : Hp,m
∀𝑘 ∈ 𝐾, ℎ𝑎,𝑏 𝑘 = ( 𝑎𝑘 + 𝑏 𝑝 ) 𝑚
Théorème 7.1 : La famille de fonction Hp,m est universelle
De ce fait, on peut montrer que l'exécution de n
primitives a une complexité moyenne en O(n).
Définition 7.4 : L'adressage d'une table de hachage est dit chainé si
l'élément i du tableau donne accès à une structure de données
permettant de stocker les clés k telles que hm(k)=i.
Les valeurs sont donc stockées à l'extérieur de la table qui est un
tableau de pointeurs.
Par suite, si il n'existe pas de clé k telle que hm (k)=i alors T[i]=NIL.
L'espace de stockage des clés peut être une liste doublement
chainée.
On a dans ce cas
tableHash de clé=structure
table:tableau[0..m-1] de listeDC de clé;
h:fonction(val v:clé):entier
finstructure
Primitives d’accès :
fonction chercher(ref T:tableHash_de_cle,
val e:entier):curseur;
debut
retourner(chercherListe(T.table[T.h(e)],e))
fin
Primitives de modification :
fonction creerTableHach(ref T: tableHash_de_cle,
ref h:fonction):vide;
var i:entier;
debut
pour i allant de 0 à m-1 faire
creerListe(T.table[i])
finpour
T.h=h;
fin
fonction ajouter(ref T:tableHash_de_cle,val e:entier):booleen;
debut
insererEnTete(T.table[T.h(e)],e);
retourner(vrai):
fin
Primitives de modification :
fonction supprimer(ref T:tableHash_de_cle;val e:entier):vide;
debut
supprimer(T.table[T.h(e)],e))
fin
Théorème 7.2 : Dans une table de hachage à adressage chainé, une
recherche fructueuse ou infructueuse prend en moyenne O(1+n/m) si
chaque élément à les mêmes chances d'être haché vers l'une
quelconque des cases indépendamment des autres éléments (hachage
uniforme).
Définition 7.5 : L'adressage d'une table de hachage est
dit ouvert si les éléments du tableau sont les clés elles
mêmes.
Les éléments de la table sont donc initialisés à NULL.
La gestion de la collision se fait en trouvant une place
libre dans la table. Pour cela on utilise une seconde
fonction s(x,i) à valeur dans [0..m] que l'on compose
avec hm(k).
Cette seconde fonction dite de sondage doit vérifier les
propriétés suivantes :


s(hm(k),0)=hm (k)
s(hm (k),i))i=0…m-1 est une permutation de la séquence (i)
i=0…m-1
La méthode d'insertion consiste, en cas de collision pour une
clé k, à sonder les places disponibles s(hm(k),i) à partir de i=0
jusqu'à
m-1.
Si au bout de m sondages, aucune place disponible n'a
été détectée, la table est dite saturée.
Quelques méthodes de sondage :



Sondage linéaire :
s(k,i)=(h(k)+i)[m]
Sondage quadratique :
s(k,i)=(h(k)+c1 i+c2 i2)[m]
Double hachage :
s(k,i)=(h(k)+i h'(k))[m]
où h' est une seconde fonction de hachcode telle que
∀𝑘 ∈ 𝐾, ℎ′ 𝑘 𝑒𝑠𝑡 𝑝𝑟𝑒𝑚𝑖𝑒𝑟 𝑎𝑣𝑒𝑐 𝑚
L'espace de stockage des clés est alors un tableau.
On a dans ce cas :
tableHash_de_cle=structure
table:tableau[0..m-1] de cle;
h : fonction(val v : cle) : entier
s : fonction(val v : cle; val i : entier) : entier
finstructure
Primitives d’accès :
fonction chercher(ref T:tableHash_de_cle,
val e:entier):curseur;
var i:entier;
debut
i=0;
tant que T.table[T.s(T.h(e),i)]!=e et i<m faire
i=i+1
fintantque
si i==m alors
retourner(NULL)
sinon
retourner(i)
finsi
fin
Primitives de modification
fonction creerTablehachage(ref T: tableHash_de_cle,
ref h : fonction(var x:entier):entier),
ref s : fonction(var x:entier):entier):vide;
var i:entier;
debut
pour i allant de 0 à m-1 faire
T.table[i]=NULL;
finpour
T.h=h;
T.s=s;
fin
Primitives de modification
fonction ajouter(ref T:tableHash_de_cle,
val e :entier):booleen;
var i:entier;
debut
i=0;
tant que T.table[T.s(T.h(e),i)]!=NULL et i<m faire
i=i+1
fintantque
si i==m alors
retourner(faux)
sinon
T.table[T.s(T.h(e),i)]=e;
retourner(vrai)
finsi
fin
Primitives de modification
fonction supprimer(ref T:tableHash_de_cle,
val e:entier):vide;
var p:curseur;
debut
p=chercher(T,e);
T.table[p]=NULL
fin
Théorème 7.3 : Etant donné une table de hachage de facteur de
remplissage r, une recherche infructueuse ou l'insertion d'une
clé se fait en moyenne en 1/(1-r), si chaque élément a les
mêmes chances d'être haché vers l'une quelconque des cases
indépendamment des autres éléments (hachage uniforme ).
Théorème 7.4 : Etant donné une table de hachage de facteur de
remplissage r, une recherche fructueuse se fait en moyenne en
(1/r)*ln(1/(1-r)), si chaque élément a les mêmes chances d'être
haché vers l'une quelconque des cases indépendamment des
autres éléments (hachage uniforme ).
Le taux d'occupation de la table doit rester relativement
faible pour minimiser le risque de collision sinon le nombre de
collision augmente et la performance se dégrade.
Ceci veut dire qu'on ajoute à chaque type abstrait un
champ "taux de remplissage" égal au rapport du nombre
d'éléments divisé par la taille de la table.
Dès que ce taux dépasse un seuil fixé, la fonction
"ajouter" effectue automatiquement l'appel à la fonction de
redimensionnement de la table.



Définition
Implémentation
Opérations d'ajout et suppression


Un dictionnaire est une structure de donnée
permettant de stocker des mots.
Dans ce cours, on considère que le mot est stocké
dans une variable de type "mot" dont on peut
connaitre la longueur par la fonction "longueur".
fonction longueur(ref M:mot):entier;

Si M est un mot, M[i] est le ième caractère de M.
Un dictionnaire est donc un containeur dont les
primitives sont les suivantes.
accès :

fonction appartient((ref d:dictionnaire,val M::mot):booléen;
modification :
fonction
fonction
fonction
fonction
creerDictionnaire(ref d: dictionnaire):vide
ajouter(ref d:dictionnaire,val M::mot):vide;
supprimer(ref d:dictionnaire,val M:mot):vide;
detruireDictionnaire(ref d:dictionnaire):vide;
On considère que l'implémentation se fait dans le type
arbreBinaire. On munit le dictionnaire d'un curseur de façon à
ne pas passer ou déclarer un pointeur à chaque appel.
type dico=structure
a:sommet; /* l'arbre*/
p:sommet; :* le curseur */
finstructure
Le principe est le suivant :
 Descendre sur le fils gauche correspond à passer à la lettre
suivante dans un mot,
 Descendre sur le fils droit correspond à passer à une autre
lettre en même position.
 Tous les mots se terminent par un même caractère que nous
noterons "\0" et qui est stocké dans l'arbre.
c
a
i
s
r
m
\0
r
r
e
e
e
e
\0
e
\0
i
r
\0
\0
e
\0
Pour ajouter un mot M dans un dictionnaire :
 on recherche le plus long préfixe du mot M contenu dans
l'arbre à partir de la racine, on détermine ainsi un sommet S
de l'arbre correspondant au i premières lettres du mot.
 On parcourt à partir de S la branche droite de l'arbre de
manière à trouver deux sommets tels que :
◦ S2==filsDroit(S1),
◦ valeur(S1)<M[i+1]<valeur(S2)



On crée un arbre "baguette" (tous les fils droits sont vide) B
étiqueté par toutes lettres de M à partir du (i+1)ème caractère.
on insère B entre les sommets S1 et S2 dans l'arbre
Un exemple est ici
Pour supprimer un mot M dans un dictionnaire :
 on recherche la feuille correspondante au mot, elle
se trouve au bout d'un arbre "baguette" B,
 on détruit l'arbre B.
 Un exemple est ici
fonction chercher_branche_droite(ref d : dico; ref c:car): booleen;
var v: car;
debut
v=valeur(d.p)
tant que filsDroit(d.p)<>NIL et v<c faire
d.p=filsDroit(d.p)
v= valeur(d.p)
fintantque
si v==c alors /*d.p est positionné sur la lettre c*/
retourner VRAI
sinon
si filsGauche(pere(d.p))!=d.p et filsDroit(d.p)<>NIL alors
d.p=pere(d.p)
finsi
/* dans le cas ou d.p est le premier de la branche droite le
sommet contient une valeur >C */
/* sinon d.p est sur l'elt précédant la place possible pour un
ajout à droite */
retourner FAUX
finsi
fin
fonction prefixe(ref d: dico; ref M:Mot;ref i:entier) :booleen;
/*->VRAI M est dans le dico*/
/*->FAUX M[i] est la dernière lettre du prefixe dans le dico
debut
cas où
cas M[i]=="\0" et valeur(d.p)=="\0" :
/*le mot existe dans le dico*/
retourner VRAI
cas M[i]==valeur(d.p)
/* filsGauche(d.p)!=NIL nécessairement*/
d.p=filsGauche(d.p)
i=i+1
/* On continue à chercher le préfixe /*
retourner(prefixe(d,M,i))
cas chercher_branche_droite(d,M[i]) alors
/*d.p est sur le caractère correspondant à M[i] dans dico on*/
/*repart de là prefixe se charge de descendre à gauche*/
retourner(prefixe(d,M,i))
autre cas :
/* on a le prefixe max au i-1eme caractère. d.p est sur l'elt
/*précédant la place possible*/
i=i-1
retourner FAUX
fincasou
fin
fonction creer_arbre_baguette(ref M:mot;val
i:entier):sommet;
var s,tmp,c:sommet
var j:entier;
debut
s=creerArbreBinaire(M[i]);
tmp=s;
pour j=i+1 a longueur(M) faire
ajouterFilsGauche(tmp,M[j]);
tmp=filsGauche(tmp)
finpour
retourner(s)
fin
fonction ajouter(ref d:dico,ref M:mot):vide
debut
d.p=d.a
i=1
si !prefixe(d,M,i) alors
s=creer_arbre_baguette(M, i+1)
cas où :
cas i=0 et pere(d.p)==NIL : /* cas de la racine*/
s^.droit=d.a
s^.pere=nil
d.a^.pere=s
d.a=s
cas M[i]>valeur(d.p) : /* insertion en milieu de branche droite */
s^.droit=d.p^.droit
s^.pere=d.p
si filsDroit(d.p)!=NIL alors /* !fin de branche droite */
s^.droit^.pere=s
finsi
d.p^.droit=s
autre cas
s^.droit=d.p;
s^.pere=d.p^.pere;
d.p^.pere=s;
s^.pere^.gauche==s
fincasou
fin



Arbre AVL
Facteur d'équilibrage et rotation
Implémentation
Définition :
Un arbre AVL est un arbre binaire éventuellement vide tel que la
différence de hauteur entre le sous arbre gauche et le sous arbre
droit d'un nœud diffère d'au plus 1.
 les arbres gauches et droits d'un sommet sont des arbres AVL
Le nom AVL vient du nom des auteurs G.M. Adelson-Velsky et E.M.Landis (1982).

Propriété :
Si n est le nombre de nœuds d'un arbre AVL alors sa hauteur est
log2(n)
Chaque nœud contient un entier appelé facteur d'équilibrage qui
permet de déterminer si il est nécessaire de rééquilibrer l'arbre.
Définition :
Soit s un sommet ayant pour sous arbre gauche (resp. droit) Gs (resp.
Ds). Le facteur d'équilibrage eq(s) du sommet s est défini par :
eq(s)= h(Ds)-h(Gs)
avec h(NIL)=0 et si A est un arbre h(A)=hauteur(A)+1.
Le facteur d'équilibrage d'un nœud d'un arbre AVL vaut 0,1 ou -1.
Lors d'insertion ou suppression, l'arbre peut se déséquilibrer (valeur 2
ou -2), on utilise alors des rotations pour rééquilibrer l'arbre.
Définition :
Une rotation droite autour du sommet y d'un arbre binaire de
recherche consiste à faire descendre le sommet y et à faire remonter
son fils gauche x sans invalider l'ordre des éléments. L'opération
inverse s'appelle rotation gauche autour du sommet x.
Dans la rotation seuls les facteur d'équilibrage de X et Y sont modifiés.
Notons eq'(X) et eq'(Y) les facteurs d'équilibrage après rotation.
Propriété : Après une rotation droite autour du sommet Y, on a :
eq'(X)=eq(X)+1+max(eq'(Y),0)
eq'(Y)=eq(Y)+1-min(eq(X),0)
Propriété : Après une rotation gauche autour du sommet X, on a :
eq'(X)=eq(X)-1-max(eq(Y),0)
eq'(Y)=eq(Y)-1+min(eq'(X),0)
Les opérations d'insertion et suppression sont celles d'un arbre binaire de
recherche dans lesquelles on ajoute la gestion du facteur d'équilibrage.
Insertion : L'insertion aboutit à la création d'une feuille f.
Considérons le premier ancêtre y de cette feuille qui viole la condition AVL,
on a eq(y)=-2 ou eq(y)=2 et ces deux cas sont symétriques.
Supposons que eq(y)=-2 et soit x le fils gauche de y. On a deux cas.

soit eq(x)=-1 ou 0, on effectue une rotation droite,

soit eq(x)=1, alors x a un sous arbre droit de racine z, qui a deux sous arbres.
On effectue une rotation gauche autour de x puis une rotation droite autour de y.
La suppression aboutit à la suppression d'une feuille .

Considérons le premier ancêtre y de cette feuille qui viole la condition AVL, on a :
eq(y)=-2 ou eq(y)=2
et ces deux cas sont symétriques.
Supposons que eq(y)=-2 Soit x le fils gauche de y et t le fils droit de y. La feuille a été
supprimée dans le sous arbre de racine t. On a trois cas :
soit eq(x)=-1 , on effectue une rotation droite,


soit eq(x)=0 , on effectue aussi une rotation droite,

soit eq(x)=1, alors x a un sous arbre droit de racine z, qui a deux sous arbres.

On effectue une rotation gauche autour de x puis une rotation droite autour de y.

La hauteur du nouveau sous arbre a même hauteur que le sous arbre de
départ si le facteur d'équilibrage de la racine est 0.
Dans ce cas, il suffit donc d'une opération d'équilibrage pour que l'arbre soit
AVL.
Dans les autres cas il faut recommencer la même opération en remontant
dans l'arbre.
Le cas eq(y)=2 est obtenue par symétrie.

Figures complètes ici



L'intérêt du facteur d'équilibrage est que cette information nécessite 2 bits.
Néanmoins les algorithmes permettant la gestion de eq sont délicats.
Dans la suite, une cellule pour un arbre AVL doit contenir un champ supplémentaire
qui est la hauteur.
type celluleAVL=structure
info:objet;
hauteur:entier;
gauche:sommet;
droit:sommet;
père:sommet;
finstructure
sommetAVL=^celluleAVL;
On accède à ce nouveau champ par les deux primitives suivantes :
fonction getHauteur(ref S:sommet):entier;
/* 1 pour une feuille; 0 si NIL/*
fonction setHauteur(ref S:sommet; val h:entier):entier;
/* 1 pour une feuille; 0 si NIL/*
Les fonctions implémentant les rotations sont immédiates.
fonction rotationDroite(ref y:sommet):vide;
var x,p:sommet;
debut
x=filsGauche(y);
p=pere(y);
si p!=NIL alors
si y=filsGauche(p) alors
p^.gauche=x;
sinon
p^.droit=x;
finsi
finsi
x^.pere=p;
y^.pere=x;
y^.gauche=x^.droit;
x^.droit=y;
setHauteur(y,max(getHauteur(filGauche(y)),getHauteur(filsDroit(y)))+1);
setHauteur(x,max(getHauteur(filGauche(x)),getHauteur(filsDroit(x)))+1);
fin
Les fonctions implémentant les rotations sont immédiates.
fonction rotationGauche(ref x:sommet):vide;
var y,p:sommet;
début
y=filsDroit(x);
p=pere(x);
si p!=NIL alors
si x=filsGauche(p) alors
p^.gauche=y;
sinon
p^.droit=y;
finsi
finsi
y^.pere=p;
x^.pere=y;
x^.droit=y^.gauche;
y^.gauche=x;
setHauteur(x,max(getHauteur(filGauche(x)),getHauteur(filsDroit(x)))+1);
setHauteur(y,max(getHauteur(filGauche(y)),getHauteur(filsDroit(y)))+1);
fin
Les fonctions d'insertion et suppression doivent prendre en compte le calcul du facteur
d'équilibrage ainsi que le maintien de cet équilibre.
fonction ajouter(ref x:sommet, val e:objet):vide;
var s:sommet;
début
si e ≤ getValeur(x) alors
s=filsGauche(x);
si s==NIL alors
ajouterFilsGauche(x,e);
setHauteur(filsGauche(x),1)
equilibreAprèsInsertion(filsGauche(x))
sinon
ajouter(s,e);
finsi
sinon
s=filsDroit(x);
si s==NIL alors
ajouterFilsDroit(x,e);
setHauteur(filsDroit(x),1);
equilibreAprèsInsertion(filsDroit(x))
sinon
ajouter(s,e);
finsi
finsi
fin
fonction supprimer(ref x:sommet):booléen;
var p,f,y:sommet;
début
si estFeuille(x) alors
p=pere(x);
si filsGauche(p)==x alors
supprimerFilsGauche(p)
setHauteur(p,getHauteur(filsDroit(p)+1))
sinon
supprimerFilsDroit(p)
setHauteur(p,getHauteur(filsGauche(p)+1))
finsi
equilibreAprèsSuppression(p)
sinon
f=filsDroit(x);
si f!=NIL
y=cherchePlusPetit(f);
sinon
f=filsGauche(x);
y=cherchePlusGrand(f);
finsi
setValeur(x,getValeur(y));
supprimer(y);
finsi
fin
fonction equilibreAprèsInsertion(ref x:sommet):vide;
var eq:entier;
var s,p:sommet;
debut
eq=0;
p=x;
tantque pere(p)!=NIL et eq!=2 et eq!=2 faire
s=p;
p=pere(s);
setHauteur(p,max(getHauteur(filGauche(p)),getHauteur(filsDroit(p)))+1);
eq=getHauteur(filsDroit(p))-getHauteur(filsGauche(p));
fintantque
si eq==2 ou eq==-2 alors
equilibreUnSommet(p,s);
finsi
fin
fonction equilibreUnSommet(ref p,s:sommet):vide;
début
si s==filsGauche(p) alors
si getEquilibre(p)==-2 alors
si getEquilibre(s)<1 alors
rotationDroite(p);
sinon
rotationGauche(s);
rotationDroite(p);
finsi
finsi
sinon
si getEquilibre(p)==2 alors
si getEquilibre(s)>-1 alors
rotationGauche(p)
sinon
rotationDroite(s);
rotationGauche(p);
finsi
finsi
finsi
fin
Dans le cas de suppression, on remonte à partir de la feuille supprimée dans l'arbre et de la même
manière on détecte les nœuds déséquilibrés.
Il est possible que plusieurs rotations soient nécessaires. En effet un déséquilibre peut en entrainer
un autre.
fonction equilibreAprèsSuppression(ref x:sommet):vide;
var eq:entier;
var s,p:sommet;
début
eq=1;
p=x;
p=pere(s);
tantque p!=NIL et eq!=0 faire
s=p;
p=pere(p);
setHauteur(p,max(getHauteur(filGauche(p)),getHauteur(filsDroit(p)))+1);
eq=getHauteur(filsDroit(p))-getHauteur(filsGauche(p));
equilibreUnSommet(p,s);
fintantque
fin
Définition
 Liste simplement chainée
 Quelques exemples d'algorithmes
 Liste doublement chainée

Définition
 Une liste est un containeur tel que le nombre d'objets
(dimension ou taille) est variable,
 L'accès aux objets se fait indirectement par le
contenu d'une clé qui le localise de type curseur.
Par exemple :
curseur=^type_predefini;
liste de type_predefini = curseur
L
NIL
Définition :
Une liste est dite simplement chainée si les opérations
suivantes s'effectuent en O(1).
Accès
fonction premier(ref L: Liste):curseur;
fonction suivant(ref L: Liste; val P:curseur):curseur;
fonction listeVide(ref L:Liste):booléen;
Modification
fonction
fonction
fonction
fonction
fonction
creerliste(ref L:Liste):vide;
insererApres(ref L:Liste; val x:objet; ref P:curseur):vide;
insererEnTete(ref L:Liste; val x:objet):vide;
supprimerApres(ref L:Liste; val P:curseur):vide;
supprimerEnTete(ref L:Liste):vide;
Test de fin de liste
fonction estDernier(ref L:Liste; ref P:curseur):booléen;
début
retourner(suivant(L,P)=NIL)
fin
Complexité: O(1).
Chercher un élément dans une liste
fonction chercher(ref L: Liste; ref E:objet):curseur;
var p: curseur;
début
si listeVide(L) alors
retourner(NIL)
sinon
p=premier(L);
tant que non(estDernier(L,p)) et (contenu(p)!=e) faire
p=suivant(L,p);
fintantque
si (contenu(p)!=e) alors
retourner(NIL)
sinon
retourner(p)
finsi
finsi
fin
Complexité: O(n).
Chercher un élément dans une liste
fonction chercher(ref L: Liste; ref E:objet):curseur;
var p: curseur;
début
si listeVide(L) alors
retourner(NIL)
si contenu(L)==E alors
retourner(L)
chercher(suivant(L,premier(L)),E)
Complexité: O(n).
Trouver le dernier élément
fonction trouverDernier(ref L:liste):curseur;
var p: curseur;
debut
si listeVide(L) alors
retourner(NIL)
sinon
p=premier(L);
tant que non(estDernier(L,p)) faire
p=suivant(L,p);
fintantque
retourner(p)
finsi
fin
Complexité: O(n).
Trouver le dernier élément
fonction trouverDernier(ref L:liste):curseur;
var p: curseur;
debut
si listeVide(L) alors
retourner(NIL)
finsi
p=premier(L);
si(estDernier(L,p))
retourner(p)
finsi
trouverDernier(suivant(L,p))
fin
Complexité: O(n).
Calculer la taille d’une liste
fonction taille(val L:Liste):entier;
var p;curseur;
var t:entier;
debut
si listeVide(L) alors
retourner(0)
sinon
retourner(1+taille(suivant(L,premier(L))))
finsi
fin
finfonction
Complexité: O(n).
Insérer dans une liste triée
On suppose la liste triée dans l'ordre croissant
fonction insertionTrie(ref L:liste; val e: objet):vide;
var p:curseur;
début
si listeVide(L) alors
insererEnTete(L,e)
sinon
si contenu(premier(L))>e alors
insererEnTete(L,e)
sinon
insererTrie(suivant(L,premier(L)),e)
finsi
finsi
fin
Complexité: O(n).
Décrire l’algorithme pour supprimer un objet e dans une liste L.
Définition :
Une liste doublement chainée est une liste pour laquelle les
opérations en temps O(1) sont celles des listes simplement
chainées auxquelles on ajoute les fonctions d'accès
fonction dernier(val L:Liste_DC):curseur;
fonction precedent(val L:ListeDC; val P:curseur):curseur;
Téléchargement