COMPOSITION D'INFORMATIQUE
Arbre des suffixes
Jean Berstel*
14 février 2001
Avertissement
On attachera une grande importance à la clarté, à la
précision, à la concision de la rédaction.
La manipulation des grandes bases de données textuelles que
constituent les dictionnaires, les génomes, ou les textes circulant
sur le World Wide Web fait appel à des structures de données
élaborées et à des algorithmes efficaces. L'arbre des suffixes est
utilisé dans ces contextes pour la constitution d'index, car il permet
une recherche rapide.
Soit A un ensemble fini appelé alphabet dont les éléments sont
appelés des lettres. Un mot sur A est une suite finie
d'éléments de A, que l'on note par simple juxtaposition. La longueur
d'un mot u est le nombre de lettres figurant dans u. Ainsi,
ababb est un mot de longueur 5 sur A = {a,b}. L'unique mot de
longueur 0 est le mot vide, noté . Si u et v sont deux mots,
leur produit, noté uv, est le mot obtenu en mettant u et v bout
à bout. Ainsi, si u=ban et v=anes, uv = bananes. Un mot u est
préfixe (resp. suffixe) d'un mot v s'il existe un mot
x tel que ux=v (resp. xu=v). Un mot de longueur n possède
n+1 préfixes (y compris le mot vide et lui-même). Un mot u est
facteur d'un mot v s'il existe deux mots x, y tels que
xuy=v. Ainsi, le mot nan est facteur de bananes. Un préfixe
(resp. suffixe) est toujours un facteur.
1 Arbres littéraux
Un arbre littéral est un arbre dont les arcs sont étiquetés par
des lettres. Deux arcs issus d'un même sommet portent des étiquettes
différentes. L'étiquette d'un chemin dans l'arbre est le mot formé
des étiquettes des arcs qui le composent. Les mots représentés
par un arbre littéral sont les étiquettes des chemins issus de la
racine.
On dira qu'un sommet p et un mot x se correspondent si x
est l'étiquette du chemin de la racine à p. Les mots maximaux
d'un arbre littéral sont les mots qui correspondent aux feuilles.
Dans l'exemple ci-dessus, les mots représentés sont (le mot vide)
et les mots a, aa, ab, aab, abc, aabc, aaba. Les mots
maximaux de l'arbre sont aabc, aaba et abc.
Question 1Tracer l'arbre littéral (sans numéroter les sommets) dont les
mots maximaux sont aba, abb, ba, bb, bca.
Un arbre littéral est représenté comme un graphe. Les sommets sont des
entiers, numérotés à partir de 0. Le numéro de la racine est 0.
Les arcs issus d'un sommet sont représentés par une liste dont la
définition est
class Liste {
char etiq; // étiquette
int sommet; // sommet d'arrivée de l'arc
Liste suivant; // suivant dans la liste
Liste(char e, int s, Liste x) { etiq = e; sommet = s; suivant = x; }
}
La représentation d'un arbre littéral est composée d'un tableau
succ de listes de successeurs et de son nombre de sommets
n :
class Arbre {
static final int nMax = ...;
Liste[ ] succ; // listes de successeurs
int n; // taille
Arbre () { n = 1; succ = new Liste[nMax]; }
}
La constante nMax est supposée assez grande pour qu'il n'y
ait jamais de débordement dans le tableau succ. Un arbre
littéral a toujours au moins un sommet (sa racine, numérotée 0).
Pour l'arbre a correspondant à l'exemple ci-dessus, on a,
après construction, a.n=8, et la liste a.succ[3] a
deux élements, dont les champs etiq et sommet valent
'c', 4 et 'a', 7 respectivement. On a aussi
a.succ[4] = null par exemple.
Un mot w est représenté par un objet w de type
String. Les seules opérations à utiliser sur le type
String sont w.length() qui retourne sa longueur, et
w.charAt(i) qui retourne la lettre en position i
dans w. Les positions sont numérotées à partir de 0.
Question 2Ecrire une fonction static int filsDe(int p, char c,
Arbre a) qui retourne le sommet qui est l'extrémité de l'arc
d'étiquette c issu de p (0 £ p <
a.n), si cet arc existe, et -1 sinon.
Question 3Ecrire une fonction static int descendantDe(int p, String w,
Arbre a) qui retourne le sommet qui est l'extrémité du chemin
d'étiquette w issu de p (0 £ p <
a.n), si un tel chemin existe dans l'arbre, et -1 sinon.
Insérer un mot w dans un arbre littéral revient à créer les
sommets et les arcs nécessaires pour que l'arbre contienne aussi le
chemin issu de la racine dont l'étiquette est w. Ainsi, après
insertion de abb, l'arbre de l'exemple initial devient
Question 4Ecrire une fonction static void inserer(String w, Arbre
a) qui insère le mot w dans l'arbre a. (Rappel: on
suppose que l'arbre contient au moins un sommet).
Question 5Ecrire une fonction static Arbre nouvelArbre(String[ ]
x) qui retourne un arbre littéral obtenu par les insertions
successives des mots du tableau x.
2 Arbre des suffixes
Etant donné un mot w, l'arbre des suffixes de w est l'arbre
littéral obtenu par insertion de tous les suffixes du mot w. Il est
facile de voir qu'il représente exactement l'ensemble des facteurs de
w. Par exemple, l'arbre des suffixes du mot w=acbaa est
On rappelle qu'un sommet p et un mot x se correspondent si
x est l'étiquette du chemin de la racine à p. Un sommet est terminal s'il correspond à un suffixe de w. Dans la figure
ci-dessus, on a doublement encerclé les sommets terminaux de l'arbre
des suffixes du mot w=acbaa.
Question 6Tracer l'arbre des suffixes du mot ababbb.
On augmente la définition de la classe Arbre en ajoutant un
tableau booléen terminal de taille nMax avec la
propriété que a.terminal[p] vaut true si et
seulement si p est un sommet terminal dans l'arbre
a.
Question 7Indiquer comment modifier les fonctions des questions 4 et 5 pour
obtenir:
a) une fonction static void insererSuffixe(int i, String w,
Arbre a) qui insère le suffixe du mot w commençant en
position i dans l'arbre a (le suffixe commençant en
position i de w=a0a1··· aN-1, où a0, a1, ...,
aN-1 sont des lettres, est le mot ai ai+1··· aN-1).
b) une fonction static Arbre nouvelArbreSuffixe(String w) qui
retourne l'arbre des suffixes du mot w.
Etant donné un mot w de longueur N, on note Cw(k) le nombre de
facteurs distincts de longueur k de w. On a Cw(0)=Cw(N)=1, et
Cw(1) est le nombre de lettres distinctes apparaissant au moins une
fois dans w. Par exemple, si w=aaaabcabc, on a Cw(3) = 5. On
note F(w) le nombre total de facteurs distincts du mot w.
Question 8On suppose que l'arbre des suffixes a du mot w de
longueur N est calculé. Ecrire une fonction static int
[ ] nombreDeFacteurs(int N, Arbre a) qui retourne un tableau
cw tel que cw[i] = Cw(i) pour tout i (0 £
i £ N). Votre fonction devra être en temps
proportionnel à F(w).
Question 9a) Donner une borne inférieure et une borne supérieure pour
F(w) en fonction de la longueur de w, et des exemples où ces
bornes sont atteintes.
b) Calculer F(anbn) pour n³0, où a,b sont des lettres et
an est défini par a0= et an+1=ana.
Le degré d'un sommet p est le nombre de ses successeurs. Si le
facteur x de w correspond à p, le degré de x est le degré de
p. C'est aussi le nombre de lettres a telles que xa est facteur
de w. Par exemple, le mot aa est de degré 2 dans aaab.
On note d(x) le degré d'un facteur x de w.
Question 10a) Montrer que pour tout k (0 £ k £ N), il existe au plus un
facteur de w de longueur k et de degré 0.
b) Montrer que si y est suffixe de x, et x est un facteur de
w, alors d(y) ³ d(x).
c) Démontrer que si Cw(k)>Cw(k+1) pour un k<N, alors
Cw(k)=1+Cw(k+1).
d) Démontrer que si Cw(k) = Cw(k+1) pour un k < N-1, alors
Cw(k+1) ³ Cw(k+2).
3 Compacter l'arbre des suffixes
L'inconvénient majeur de l'arbre des suffixes d'un mot est sa
taille. On va le compacter en supprimant les sommets internes
inutiles.
L'arbre compact des suffixes d'un mot w est obtenu à partir de
l'arbre des suffixes en supprimant les sommets de degré 1 qui ne
sont pas terminaux. Plus précisément, si (s,s1,...,sn,t) est
un chemin d'étiquette x dans l'arbre des suffixes, avec s1,
s2,...sn de degré 1 non terminaux, et s,t de degré 1
ou terminaux, alors ce chemin est remplacé, dans l'arbre compact, par
l'arc (s,t), dont l'étiquette est le mot x. Par exemple, l'arbre
compact des suffixes du mot w=acbaa est donné dans la partie gauche
de la figure ci-dessous (on a conservé les numéros des sommets dans
l'arbre du début de la section précédente).
Soit w=a0a1··· aN-1 un mot, avec a0, a1, ...,
aN-1 des lettres. On représente l'arbre compact des suffixes de
w de façon économique en remplaçant l'étiquette x d'un arc par
un couple (i,j) tel que x=aiai+1··· aj-1 comme dans
l'arbre ci-dessus à droite.
Question 11Calculer l'arbre compact des suffixes du mot w=ababbb.
Question 12Démontrer que l'arbre compact des suffixes d'un mot de longueur
N ³ 2 a au plus 2N-1 sommets.
Pour représenter un arbre des suffixes compact, à chaque arc est
maintenant associé un intervalle semi-ouvert décrivant l'étiquette.
Les nouvelles classes pour les listes et arbres sont donc
class ListeA {
int debut, afin; // intervalle semi-ouvert [début, après-la-fin[
int sommet; // sommet d'arrivée de l'arc
ListeA suivant; // suivant dans la liste
ListeA(int d, int a, int s, ListeA x) {
debut = d; afin = a; sommet = s; suivant = x; }
}
class ArbreA {
static final int nMax = ...;
ListeA[ ] succ; // listes de successeurs
boolean[ ] terminal; // marques des sommets terminaux
int n; // taille
ArbreA () { n = 1; succ = new ListeA[nMax]; terminal = new boolean[nMax]; }
}
Question 13Ecrire une fonction static int longueurMot(Arbre a) qui
retourne la longueur du mot dont a est l'arbre des suffixes.
Question 14Ecrire une fonction static ArbreA transformer(Arbre a)
qui retourne l'arbre compact correspondant à l'arbre des suffixes
a. (On conservera les mêmes numéros pour les sommets des deux
arbres et on ne se servira que de la longueur du mot dont a
est l'arbre des suffixes).
Question 15Ecrire une fonction static ArbreA epurer(ArbreA a) qui
retourne un arbre compact équivalent à l'arbre compact a avec
des numéros de sommets consécutifs (comme dans la figure suivante).
4 Construction directe de l'arbre compact des suffixes
Dans cette partie, on construit l'arbre compact des suffixes sans
passer par l'arbre littéral. On gagne en place, mais le temps est
encore quadratique.
L'algorithme procède par insertions des suffixes par longueur
décroissante dans un arbre initialement réduit à un sommet. Durant
l'insertion d'un suffixe, on détecte le plus long préfixe figurant
déjà dans l'arbre, et on ajoute, si nécessaire, un nouvel arc étiqueté
par la totalité du mot restant. L'exploitation du préfixe présent dans
l'arbre nécessite parfois de briser un arc en deux.
Figure 1: A gauche, après insertion de aaabab, et à droite après
insertion de aabab
Voici un exemple. Soit w=aaabab. Après insertion du mot, on obtient
l'arbre à gauche dans la figure 1. Pour le suffixe =aabab, le plus
long préfixe dans l'arbre est aa. On brise l'arc (0, aaabab,
1) en deux arcs (0, aa, 2) et (2, abab, 1), et on ajoute un
nouvel arc (2, bab, 3). On obtient alors l'arbre à droite dans la
figure 1.
Figure 2: Après abab et bab.
Pour l'insertion du suffixe abab, on brise l'arc (0,aa,2) en
(0,a,4) et (4,a,2) et on ajoute (4,bab,5). L'insertion de bab
ne provoque pas de bris, et se solde par l'ajout de l'arc
(0,bab,6). On obtient donc l'arbre de la figure 2.
Figure 3: A gauche après ab, et à droite l'arbre final
L'insertion de ab provoque le bris de l'arc (4,bab,5) sans
création d'une feuille supplémentaire (figure 3 à gauche). L'arbre
final est dessiné dans la figure 3, à droite.
On se donne un mot w=a0 a1 ··· aN-1 avec N>0 et
a0, a1, ... aN-1 des lettres.
Question 16Ecrire une fonction static int lpc(int i, int j, int k,
int l, String w) qui retourne la longueur du plus long mot qui est
préfixe à la fois du mot aiai+1··· aj-1 et du mot
akak+1··· a-1.
On utilise les structures introduites dans la partie précédente,
à savoir les classes ArbreA et ListeA.
Question 17Ecrire une fonction static void insererSuffixe(int i,
String w, int p, ArbreA a) qui insère le suffixe de w
commençant en position i dans l'arbre a à partir du
sommet p.
Question 18Ecrire une fonction static ArbreA nouvelArbre(String w)
qui retourne l'arbre des suffixes compact du mot w en
appliquant l'algorithme décrit plus haut.
5 Vers la construction de l'arbre compact en temps linéaire
Dans cette partie, on amorce la construction de l'arbre compact en
temps et en place linéaires.
Question 19Soit w un mot, et soit x un facteur de w. Montrer que si
x correspond à un sommet p dans l'arbre des suffixes de w,
on peut calculer le sommet p en temps O(E × k) où E
est le nombre d'arcs du chemin de la racine à p et k est le nombre
de lettres de l'alphabet considéré.
Question 20Soit p un sommet de l'arbre des suffixes compact de w autre
que la racine, et soit x le facteur correspondant à p. Posons
x=ay, où a est la première lettre de x. Montrer que y
correspond à un sommet q de l'arbre.
On appelle lien suffixe de p le sommet q défini dans la
question précédente.
Question 21Ecrire une fonction static int[ ] lienSuffixe(String w,
ArbreA a) qui retourne, dans un vecteur, les liens suffixes des
sommets de l'arbre a des suffixes de w.
Weiner (1973), McCreight (1976) et Ukkonen (1992) ont trouvé un
algorithme pour construire l'arbre des suffixes en un temps linéaire.
L'algorithme de Weiner fut considéré comme l'algorithme de l'année
1973 par Knuth.
- *
-
avec les participations de Georges Gonthier et de Jean-Jacques Lévy
This document was translated from LATEX by HEVEA.