Dans cette section, pour implanter les arbres binaires, nous étudions
les notions Scheme de Sexpressions et de vecteurs.
Ainsi, nous pourrons implanter les arbres binaires de deux façons
différentes. Noter que nous ne comparerons pas les performances de ces
implantations, une telle étude étant hors du programme de ce cours.
Considérons l'expression Scheme,
(- (+ (- 95) 5) (+ 10 3)). Cela ressemble à une liste (de trois éléments) de type
LISTE[a], le premier élément étant
-, le second
élément étant
(+ (- 95) 5) et le dernier élément étant
(+
10 3); à nouveau,
(+ (- 95) 5) ressemble à une liste de type
LISTE[a] (de trois éléments), le premier élément étant
+, le second élément étant
(- 95) et le dernier élément
étant
5; à nouveau,
(- 95) ressemble à une liste (de
deux éléments)... Mais ce ne sont pas des listes de type
LISTE[a] puisque les éléments ne sont pas de même type.
Ce sont des
Sexpressions.
Formellement, on peut définir les
Sexpressions par
<Sexpression> |
-> |
<atome>
|
|
|
LISTE[<Sexpression>]
|
|
|
|
<atome> |
-> |
<Nombre>
|
|
|
<bool>
|
|
|
<string>
|
|
|
<Symbole>
|
|
|
|
(
<Nombre>,
<bool>,
<string> et
<Symbole>
sont définis dans la carte de référence)
Tout d'abord, nous devons pouvoir savoir si une Sexpression est un
atome ou une liste de Sexpressions. Pour ce faire, il existe en
Scheme la fonction
list?:
;;;
;;;
Les autres fonctions de base sont les fonctions que nous avons déjà vues
(
car,
cdr,
pair?...).
La notion de Sexpression permet d'implanter efficacement -- en
Scheme -- les arbres binaires (ainsi que tous les types d'arbres). En effet:
-
lorsque l'arbre est vide, on peut le représenter par la liste
vide (qui est également la Sexpression vide);
- lorsque l'arbre n'est pas vide, on peut le représenter par une
liste contenant l'étiquette de la racine et les deux Sexpressions
qui représentent ses sous-arbres immédiats. Notez que l'on a six
ordres possibles (d'abord l'étiquette puis le sous arbre gauche et
enfin le sous-arbre droit ou le sous-arbre gauche puis l'étiquette
et enfin le sous-arbre droit...). Ces six possibilités sont aussi
valables les unes que les autres. Il faut en choisir une et s'en
souvenir pour toutes les fonctions qui opèrent sur les arbres non
vides. Ci-dessous, nous donnons une implantation écrite en mettant
systématiquement l'étiquette de la racine en premier et le
sous-arbre gauche avant le sous-arbre droit. La compréhension de
cette implantation est facile, aussi nous la donnons telle quelle,
sans explication.
;;;;
;;;;
;;;;
;;;;;;;;;;;;;;;
;;;
;;;
;;;
(define (ab-noeud e B1 B2)
(list e B1 B2))
;;;
;;;
(define (ab-vide)
'())
;;;;;;;;;;;;;;
;;;
;;;
(define (ab-noeud? B)
(pair? B))
;;;
;;;
(define (ab-vide? B)
(not (ab-noeud? B)))
;;;;;;;;;;;;;;;;;
;;;
;;;
;;;
(define (ab-etiquette B)
(car B))
;;;
;;;
;;;
(define (ab-gauche B)
(cadr B))
;;;
;;;
;;;
(define (ab-droit B)
(caddr B))
Les listes, que nous avons vues dans les cours précédents, permettent
de rassembler plusieurs valeurs avec la possibilité d'extraire
immédiatement la première valeur (en utilisant
car); mais si
l'on veut extraire le
ieme élément de la liste (
i étant
variable), on doit écrire une fonction qui parcourt cette dernière (en
utilisant la fonction
cdr).
Les vecteurs
2 sont
des structures de données Scheme qui permettent d'agréger plusieurs valeurs
-- les composants du vecteur -- avec la possibilité d'extraire
immédiatement l'une de ces valeurs.
Plus précisément, un vecteur est caractérisé par des
indices,
une
longueur et des
composants:
-
la longueur du vecteur est le nombre de ses composants,
- ses composants sont indicés par les entiers naturels compris entre
0 (inclus) et sa longueur (exclue);
- ainsi, le premier composant du vecteur est celui d'indice 0, le
deuxième composant est celui d'indice 1... et le dernier composant
est celui ayant comme indice sa longueur moins un.
Notons qu'il existe un vecteur particulier, le vecteur vide, dont la
longueur est nulle et qui ne contient aucun composant.
Remarque: sous Drscheme, l'affichage de la valeur d'un vecteur
commence par le caractère
#, continue par la longueur du vecteur
et se termine par, entre parenthèses, la liste des valeurs des composants du
vecteur.
Constructeur
La fonction
vector, qui peut avoir un nombre quelconque
d'arguments, rend un vecteur dont les composants sont ses arguments,
dans l'ordre où ils sont donnés:
;;;
;;;
;;;
;;;
Exemple
(vector 'a 'b 'c) ->
#3(a b c)
Accesseurs
;;;
;;;
Exemple
(vector-length (vector 'a 'b 'c)) ->
3
Rappelons que le dernier indice d'un vecteur
V est égal à
(- (vector-length V) 1).
Nous avons dit que la caractéristique d'un vecteur était de pouvoir
accéder immédiatement à un composant d'un indice donné. Ceci se fait
grâce à la fonction
vector-ref:
;;;
;;;
;;;
Exemples
(vector-ref (vector 'a 'b 'c) 0) ->
a
(vector-ref (vector 'a 'b 'c) 2) ->
c
(vector-ref (vector 'a 'b 'c) 3) rend une erreur
(
vector-ref: index 3 out of range [0, 2] for vector: #3(a b c))
Fonctions de conversion
Comme nous l'avons dit, les listes et les vecteurs représentent des
suites d'éléments (mais les fonctions de base sont très différentes).
Aussi existe-t-il deux fonctions,
vector->list et
list->vector, qui permettent de passer d'une structure de
données à l'autre:
;;;
;;;
Exemple
(vector->list (vector 'a 'b 'c)) ->
(a b c)
;;;
;;;
;;;
Exemple
(list->vector '(a b c)) ->
#3(a b c)
On peut implanter les arbres binaires en utilisant des vecteurs:
-
l'arbre vide est représenté par le vecteur vide,
- un arbre non-vide est représenté par un vecteur de longueur
trois ayant comme composants l'étiquette de la racine de l'arbre
-- par exemple en indice 0 --, la représentation du sous-arbre
gauche -- par exemple en indice 1 -- et la représentation du
sous-arbre droit -- par exemple en indice 2.
Constructeurs
La fonction
ab-noeud est implantée en utilisant la fonction
vector:
;;;
;;;
;;;
(define (ab-noeud e B1 B2)
(vector e B1 B2))
La fonction
ab-vide est implantée par le vecteur vide en
utilisant aussi la fonction
vector (mais sans argument):
;;;
;;;
(define (ab-vide)
(vector))
Reconnaisseurs
Pour savoir si un arbre est, ou n'est pas, vide il suffit de comparer
la longueur du vecteur à 0:
;;;
;;;
(define (ab-vide? B)
(= (vector-length B) 0))
;;;
;;;
(define (ab-noeud? B)
(> (vector-length B) 0))
Accesseurs
Pour les accesseurs, on utilise la fonction
vector-ref,
l'étiquette étant en indice 0, le sous-arbre gauche en indice 1 et le
sous-arbre droit en indice 2:
;;;
;;;
;;;
(define (ab-etiquette B)
(vector-ref B 0))
;;;
;;;
;;;
(define (ab-gauche B)
(vector-ref B 1))
;;;
;;;
;;;
(define (ab-droit B)
(vector-ref B 2))