Académique Documents
Professionnel Documents
Culture Documents
Plutôt que d’indiquer les répétitions en recopiant les caractères, on peut utiliser un symbole
de répétition. Ainsi a* signifie zéro, une ou plusieurs occurrences successives de a
(attention au « zéro ») :
1
tralere = OK
On peut utiliser egrep en traitant le texte contenu dans un fichier ex1. Les lignes contenant
le motif sont recopiées, et on peut aussi afficher leur numéro (option –n) :
Le symbole | (Maj-Alt-L sous Mac) permet d’indiquer un choix entre plusieurs caractères
ou suites de caratères :
Les expressions régulières sous Unix (pour egrep, ou pour la commande lex qu’on verra
plus tard) comportent également un certain nombre d'abréviations :
[abc] pour a|b|c
[a-z] pour a|b|…|z
[a-zA-Z0-9] pour toutes les lettres et chiffres
x+ pour au moins une occurrence de x, c’est-à-dire xx*
x? pour un x optionnel
Exemple : [A-Z][a-z]* pour un nom propre, c’est-à-dire commençant par une majuscule.
2
Les expressions régulières sont un moyen de définir et de traiter des ensembles de suites de
caractères.
Ces ensembles peuvent contenir :
- des suites de caractères définies explicitement,
- des répétitions de suites de caractères,
- des choix entre plusieurs suites de caractères.
Exemple : a(b|c)*d
Une autre manière de représenter une expression régulière est de dessiner un graphe appelé
« automate fini » :
2. La hiérarchie de Chomsky
Un programme informatique est une suite de caractères (aussi appelée « mot ») qu’une
machine doit traiter.
Un « langage » est un ensemble de suites de caractères (ou de mots), une machine au sens
abstrait est un calculateur sur des mots.
3
Le domaine des langages formels a été développé par :
- les informaticiens : élaborer des langages de programmation,
- les linguistes : décrire des langues naturelles avec l’idée de les traiter
automatiquement.
Le thème central est l’étude des modèles qui permettent une spécification ou une
représention finie des langages formels.
Plusieurs approches :
- spécifier les propriétés des mots du langage : définitions en français ou expression
régulières (Kleene) egrep
- spécifier les propriétés des mots du langage : les grammaires formelles (Chomsky)
- reconnaître qu’un mot donné est un mot du langage avec des mécanismes
automatiques réalisés par différents types de machines (Turing, McCulloch et Pitt)
- spécification logique, algébrique, etc.
Les automates finis sont les machines les plus simples. Ils interviennent dans de nombreux
domaines :
- traitement de texte, compilation, codage, modélisation des circuits, vérification,
compression de données, formatage de texte, calcul formel.
- biologie (pour certains algorithmes liés au génome),
- dans l’étude des événements discrets (automatique, musique)
Objectifs du cours :
1) Éléments théoriques pour les deux derniers niveaux : langages rationnels et algébriques
2) Applications à la compilation :
- Automates finis (AF) = base de l’analyse lexicale,
- Grammaires algébriques (voir cours de Patrice Enjalbert) = base de l’analyse
syntaxique.
3. Vocabulaire
4
3.1 Définitions générales
Un alphabet Σ un ensemble fini non vide de symboles appelés aussi lettres ou caractères.
Exemple : le langage machine est basé sur l’alphabet Σ = {0, 1}.
Un mot est une suite finie de symboles.
La longueur d’un mot u notée |u| est le nombre de symboles de ce mot. Exemple : le mot u
= 011 est de longueur |u| = 3.
Le mot vide noté ε est le seul mot de longueur nulle.
Σ* représente l’ensemble des mots sur l’alphabet Σ.
Σ+ représente l’ensemble des mots sur l’alphabet Σ de longueur ≥ 1.
1.2 Concaténation
5
Exemple :
L = {ab, b}, L* est l’ensemble de tous les mots tels que aa n’est pas facteur, et a n’est pas
suffixe.
On note également L+ le langage :
L + = L ∪ L 2 … ∪ Ln ∪ …
Définition. Soit Σ un alphabet. Les expressions rationnelles (ou régulières) sur Σ et les
langages correspondants sont définis récursivement :
1) • ∅ est une expression rationnelle, et représente l’ensemble vide,
• ε est une expression rationnelle, et représente l’ensemble {ε},
• pour toute lettre a de Σ, a est une expression rationnelle, et représente l’ensemble
{a}.
2) Si r et s sont des expressions rationnelles, qui représentent les langages R et S,
alors r + s, rs, et r* sont des expressions rationnelles qui représentent les
langages R ∪ S, RS et R* .
7
CHAPITRE 2 : AUTOMATES FINIS DETERMINISTES (AFD)
Définition. Un automate fini déterministe AFD sur un alphabet Σ est la donnée d’un n-
uplet (Q, δ, i, F) où :
• Q est un ensemble fini d’états,
• δ est une fonction de transition de Q × Σ dans Q,
• i est un état particulier de Q dit initial,
• F est une partie de Q d’états dits finals.
L’automate est dit complet lorsque la fonction δ est partout définie sur Q × Σ.
Exemple :
Q = {1, 2},
i = 1, état noté avec une petite flèche entrante,
F = {2}, état noté avec deux cercles.
δ:Q×Σ→Q
(1, a) → 2
(1, b) → 1
(2, b) → 1
Cet automate n’est pas complet, car δ(2, a) n’est pas défini.
1 2
a 2
b 1 1
1
Le calcul de l’automate consiste à suivre des flèches, en partant d’un état initial et en
s’arrêtant dans un état final. Le mot correspondant à ce calcul est la suite des étiquettes des
flèches.
Propriété d’associativité. Pour tous mots u, v ∈ Σ* , on a : δ(q, uv) = δ(δ(q, u), v).
a correspond au calcul 1 → 2,
aba correspond au calcul 1 → 2 → 1 → 2,
donc on écrira :
δ(1, aba) = 2.
À partir de l’état 2, on ne peut lire un mot commençant par a, mais on peut lire babb par
exemple : δ(2, babb) = 1.
La propriété d’associativité donne :
δ(1, abababb) = δ(δ(1, aba), babb) = δ(2, babb) = 1.
Définition. Le langage reconnu (ou accepté) par un automate AFD est l’ensemble des
mots qui correspondent à un calcul de l’automate partant d’un état initial et s’arrêtant
dans un état final.
2
Exemple : le langage reconnu par l’automate ci-dessus est
b*a(bb*a)*
Les automates finis sont les modèles de machine les plus simples : ils n’ont aucun support
de mémoire externe (comme la pile d’un automate à pile).
Leur mémoire est donc finie (espace constant), et correspond à leur nombre d’états. Par
exemple, dans l’automate ci-dessus, l’état 1 permet de se souvenir qu’il faut lire un a pour
sortir.
Exemples :
- distributeur de café : les pièces introduites sont les symboles de l’alphabet, l’état
terminal est atteint quand le montant est supérieur au montant demandé,
- mécanisme contrôlant le code d’accès d’une porte : les chiffres tapés sont les
symboles, l’état terminal est celui qui déclenche l’ouverture,
- rubiks cube : l’alphabet est l’ensemble des rotations des 6 faces.
Propriété. Si L est un langage reconnu par AFD, alors son complémentaire Σ* \L l’est
aussi.
On complète l’automate :
Construction :
Si A = (Q, δ, i, F) est un AFD complet qui reconnaît L, alors
3
A = (Q, δ, i, Q\F) reconnaît Σ* \L.
4
CHAPITRE 3 : AUTOMATES FINIS NON-DETERMINISTES (AFN)
Définition. Un automate fini non-déterministe AFN sur un alphabet Σ est la donnée d’un
n-uplet (Q, δ, I, F) où :
• Q est un ensemble fini d’états,
• δ est une fonction de transition de Q × Σ dans P(Q), ensemble des parties de Q,
• I est une partie de Q d’états dits initiaux,
• F est une partie de Q d’états dits finals.
1.2 Fonctionnement
Un automate fini non-déterministe est un automate tel que dans un état donné, il peut y avoir
plusieurs transitions avec la même lettre.
Q = {1, 2, 3},
I = {1},
F = {3}.
δ définie par la table de transition :
1
1 2 3
a {1,2} ∅ ∅
b 1 3 ∅
Un mot u est accepté par un AFN s’il existe un chemin d’étiquette u, partant de l’un des
états initiaux, et arrivant à l’un des états finals.
Définition. Le langage reconnu (ou accepté) par un automate AFN est l’ensemble des
mots acceptés par l’AFN, c’est-à-dire qui correspondent à un calcul de l’automate partant
d’un état initial et s’arrêtant dans un état final.
Un AFD est un cas particulier d’AFN, avec Card(δ(q, a)) ≤ 1 pour tous q ∈ Q, a ∈ Σ.
Donc tout langage reconnu par un AFD est reconnu par un AFN.
Plus surprenant, on a la réciproque.
Théorème (Rabin-Scott). Tout langage reconnu par un AFN peut être reconnu par un
AFD.
Le principe de la construction est de prendre comme états de l’AFD les ensembles d’états
de l’AFN. L’unique état initial de l’AFD est l’ensemble I des états initiaux de l’AFN.
Exemple :
2
état initial : I = {1}
transition par a : {1, 2}
transition par b : {1}
état {1, 2}
transition par a : {1, 2}
transition par b : {1, 3}
état {1, 3}
transition par a : {1, 2}
transition par b : {1}
Conclusion. Les langages reconnus par les AFN sont exactement les langages reconnus
par les AFD.
Dans l’exemple ci-dessus, le nombre d’états de l’automate déterminisé est égal à celui de
l’AFN de départ. On verra que c’est le cas dans certaines applications, notamment pour la
recherche de motifs.
Mais il existe des cas où l’AFD équivalent est beaucoup plus gros. En fait, si Card(Q) = n,
la limite théorique est Card(P(Q)) = 2n . Or il existe des automates qui correspondent à ce
cas limite.
Exemple : L = {a, b}* a{a, b}n
L’AFN a n + 1 états, alors que l’AFD équivalent a 2n états.
3.1 Définition
3
Définition. On généralise encore la notion d’automate en introduisant des transitions
avec le mot vide ε. La fonction de transition est alors définie de Q × (Σ ∪ {ε}) dans P(Q),
ensemble des parties de Q. Ces transitions s’effectuent sans lecture de lettre.
Comme pour les AFN, un mot u est accepté par un AFN avec ε-transitions s’il existe un
chemin d’étiquette u, partant de l’un des états initiaux, et arrivant à l’un des états finals.
Exemple :
Proposition. Tout langage reconnu par un AFN avec ε–transitions peut être reconnu par
un AFN (sans ε–transitions) ayant le même nombre d’états.
On vérifie :
- qu’un chemin correspondant à un calcul réussi pour un mot dans l’automate avec
ε–transitions peut facilement être transformé en chemin dans le nouvel AFN, grâce
aux nouvelles transitions introduites,
- et qu’un chemin correspondant à un calcul réussi dans l’AFN peut être transformé
en chemin dans l’automate avec ε–transitions, en remplaçant chaque nouvelle
transition utilisée par les ε–transitions correspondantes.
4
Exemple :
• Fermeture transitive des ε-transitions
L’AFN obtenu reconnaît bien le même langage : L = {acbc, acc, abc, ac}
Propriété. Si L est un langage reconnu par AFN, alors son complémentaire Σ* \L l’est
aussi.
Propriété. Si L1 , L2 sont des langages reconnus par AFN, alors L1 ∩ L2 l’est aussi.
5
Construction par produit d’automates :
Si Ai = (Qi, δi, Ii, Fi) reconnaît Li pour i = 1, 2 sans ε–transition, alors
A = (Q1 × Q2 , δ, I1 × I2 , F1 × F2 ) reconnaît L1 ∩ L2 .
Justification :
• Si un mot correspond à un calcul réussi dans l’automate produit, on obtient une
succession de couples d’états. En projetant sur les premières composantes (resp.
deuxièmes), on obtient un calcul réussi dans le 1er automate (resp. 2ème). Donc le mot
appartient à l’intersection.
• Réciproquement, si un mot appartient aux deux langages, il correspond à deux calculs
réussis dans les automates. S’il n’y a pas de ε–transition, les deux chemins ont même
longueur. En fabriquant des couples à partir des deux séries d’états, on obtient un calcul
réussi dans l’automate produit.
Remarque : Si les automates sont des AFD, alors l’automate produit obtenu pour
l’intersection est aussi un AFD.
Propriété. Si L1 , L2 sont des langages reconnus par AFN, alors L1 ∪ L2 l’est aussi.
Propriété. Si L1 , L2 sont des langages reconnus par AFN, alors L1 L2 l’est aussi.
6
on rajoute des ε–transitions de tous les états finals de l’AFN pour L1 , vers tous les états
initiaux de l’AFN pour L2 .
De même, on rajoute des ε–transitions de tous les états finals de l’AFN pour L, vers tous
ses états initiaux.
4.5 Théorème de clôture pour les langages reconnus par AFN ou AFD
Théorème. Si L, L' sont des langages reconnus par AFN, alors il en est de même :
• du complémentaire Σ* \L,
• de l’intersection L ∩ L',
• de l’union L ∪ L',
• de la concaténation LL',
• de l’étoile L*
Les langages reconnus par AFD étant les mêmes que les langages reconnus par AFN, ces
propriétés de clôture sont vraies aussi pour les langages reconnus par AFD.
Une chaîne de Markov est un AFN dont les transitions sont munies de probabilités.
a b
a 3 1
b 1 0
Dans cet exemple, le contexte est de longueur 1, mais on peut étudier des contextes plus
long, par exemple de longueur 2 :
- combien de fois aa est suivi de a ? de b ?
- combien de fois ab est suivi de a ? de b ?
- etc
7
? (creer-table "Bonjour, ceci est le cours sur les automates,
c'est-à-dire une structure de calcul très simple, mais qui a
beaucoup de propriétés intéressantes")
((#\é 3 (#\r 1) (#\s 1) (#\t 1)) (#\q 1 (#\u 1)) (#\p 4 (#\r
2) (#\Space 1) (#\l 1)) (#\è 1 (#\s 1)) (#\d 3 (#\e 2) (#\i
1)) (#\à 1 (#\- 1)) (#\- 2 (#\d 1) (#\à 1)) (#\' 1 (#\e 1))
(#\m 3 (#\p 1) (#\a 2)) (#\a 7 (#\n 1) (#\Space 1) (#\i 1)
(#\l 1) (#\t 1) (#\u 2)) (#\l 5 (#\Space 1) (#\c 1) (#\e 3))
(#\t 10 (#\é 2) (#\u 1) (#\r 2) (#\- 1) (#\e 2) (#\o 1)
(#\Space 1)) (#\s 13 (#\a 1) (#\s 1) (#\i 1) (#\, 1) (#\u 1)
(#\Space 5) (#\t 3)) (#\i 7 (#\n 1) (#\é 1) (#\s 1) (#\m 1)
(#\r 1) (#\Space 2)) (#\e 15 (#\a 1) (#\, 1) (#\Space 6) (#\s
6) (#\c 1)) (#\c 8 (#\u 1) (#\a 1) (#\t 1) (#\' 1) (#\o 2)
(#\i 1) (#\e 1)) (#\Space 21 (#\i 1) (#\p 1) (#\b 1) (#\q 1)
(#\m 1) (#\t 1) (#\d 2) (#\u 1) (#\a 2) (#\s 3) (#\l 2) (#\e
1) (#\c 4)) (#\, 3 (#\Space 3)) (#\r 10 (#\i 1) (#\o 1) (#\è
1) (#\u 1) (#\e 3) (#\Space 1) (#\s 1) (#\, 1)) (#\u 11 (#\p
1) (#\i 1) (#\l 1) (#\c 2) (#\n 1) (#\t 1) (#\r 4)) (#\j 1
(#\o 1)) (#\n 4 (#\t 2) (#\e 1) (#\j 1)) (#\o 6 (#\p 1) (#\m
1) (#\u 3) (#\n 1)) (#\B 2 (#\e 1) (#\o 1)))
? (markov 5000)
"'es s complcai ma truromai cturestéste des quronjop caless s
lc'e dint s primp e, les cop comaure qurouciés cop satés
uctér, e qunjounjomantérèsauiétres quront e ciés ciére s s
près mp esss auci curoulesainjompres cureaintéres, c'e
dintériétététuc'ecuimanjompr a couprèsalcturesi di ecoui près
le aiésuc'estr térintrèsa b"
? (markov 5000)
" i pre al ce c'esulestérsururs ines aur sa t-à-à-dess cout-
à-de les e, diromales p cintes, cis le ul destétre s
trestéroure b"
8
CHAPITRE 4 : RECHERCHE DE MOTIFS
Problème. Rechercher toutes les occurrences d’un motif x dans un texte t. On généralisera
ensuite à la recherche de plusieurs motifs x1 , … xn . Dans un chapitre ultérieur, on envisagera
la recherche d’une expression régulière r (comme avec la commande egrep d’Unix).
1
b a c b b échec
b échec
b échec
b a c b b = OK
2
Coût de la recherche = coût de la construction + coût de la lecture
Dans le cas n° 1, les deux approches gauche-droite et droite-gauche se traduisent par deux
fameux algorithmes :
- Knuth, Morris & Pratt : c’est une variante de Morris & Pratt, c’est-à-dire
avec construction de l’AFD pour Σ* x. Dans ce cas, le texte t est comparé à x
en commençant par le début de x (état initial de l’AFD). Les échecs se
traduisent par le passage par certaines transitions de l’AFD qui reviennent à
décaler x dans le texte (voir plus loin).
- Boyer & Moore : la comparaison entre x et t se fait en commençant par la
fin de x (de droite à gauche), et s’il y a échec, on décale x dans le texte avec
un saut judicieusement choisi.
Dans le cas n° 2 (texte fixe), la méthode générale est basée sur l’AFD reconnaissant les
suffixes de t. Cet AFD ne doit pas nécessairement être complet, mais il doit être accessible
et co-accessible (tout état doit pouvoir être atteint de l’état initial, et de tout état, on doit
pouvoir atteindre un état final). On lit x dans l’AFD : si on peut le lire intégralement, c’est
qu’on a lu un préfixe d’un suffixe de t, donc une occurrence de x dans t.
L’écorché d’un mot x de longueur n est l’AFD obtenu avec n + 1 états, et n flèches
correspondant aux lettres successives du mot. Dans cet automate, on voit clairement que les
états ont un rôle de « mémoire » : chaque état mémorise la position dans le mot x.
Pour reconnaître Σ* x, il suffit de rajouter une boucle sur l’état initial avec toutes les lettres
de l’alphabet. On obtient un AFN.
On a vu que la déterminisation d’un AFN pouvait être très coûteuse (2n états pour un AFN
ayant n états). Il est remarquable que dans le cas de Σ* x, le coût de la déterminisation soit
faible.
3
Proposition. L’AFD obtenu en déterminisant l’écorché d’un mot x de longueur n,
augmenté d’une boucle sur l’état initial avec toutes les lettres, a au plus n états.
Exemple : x = bacbb
a b a c b a c b b a
0
on reste à l’état 0 comme si on n’avait rien lu, donc cela revient à recommencer la lecture en
se décalant d’une lettre
a b a c b a c b b a
0 1 2 3 4 2 échec
on ne peut aller jusqu’à l’état final 5, donc échec : on revient à l’état 2,
4
cela revient à recommencer la lecture comme si on avait déjà lu ba
donc à se décaler avec un saut comme on l’a vu plus haut.
a b a c b a c b b a
0 1 2 3 4 2 3 4 5 = OK (5 est terminal)
On peut calculer directement l’AFD pour Σ* x, sans passer par la déterminisation de l’AFN,
grâce à une fonction d’échec.
Pour calculer facilement f(p), il faut voir si on peut le déduire de f(p-1). Il se trouve que ça
marche.
i = f(p-1)
x(i+1) x(p)
|––––––––––––––––––––––––––––|–––|–––––––––––––––––––––––––––––––––––|–––|
5
f(f(p-1)) f(p-1) p-1
|––––––•–––––––––––––––––––•–|–––|–––––––––––––––––––––––––––––––––•–|–––|
i = f(f(p-1))
et on recommence la comparaison entre x(i+1) et x(p)
f(0) ← -1
i ← -1
pour p = 1 à n
tant que i≥ 0 et x(i+1) ≠ x(p)
i ← f(i)
i ← i+1
f(p) ← i
f(0) = -1, i = -1
p = 1 i = -1, on incrémente i = 0, et pose f(1) = 0
État p 0 1 2 3 4 5
f(p) 0 0 0 1 1
6
Exemple :
• pour p = 2, le mot lu est ba, il n’a pas de suffixe propre qui soit préfixe, donc f(2) = 0
• pour p = 4, le mot lu est bacb, le plus long suffixe propre qui soit préfixe est b, donc f(4)
=1
• pour p = 5, le mot lu est bacbb, le plus long suffixe propre qui soit préfixe est b, donc f(5)
=1
p
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
f(p)
=> Si on veut essayer une position de la fenêtre qui chevauche la partie lue de gauche à
droite, il faut qu’un suffixe de cette partie lue soit préfixe de x.
a b a c b a c b b a
0
dans l’état 0 de l’écorché de x = bacbb, on ne peut pas lire a, donc on reste en 0
a b a c b a c b b a
0 1 2 3 4 échec
dans l’état 4, on ne peut pas lire a,
donc on revient à l’état 1 = f(4) qui correspond à b = préfixe et suffixe de bacb
puis on lit le a qui fait passer en 2
7
a b a c b a c b b a
0 1 2 3 4 2 3 4 5 = OK (5 est terminal)
p ← 0 (état initial)
tant que t non vide
avancer dans t, a ← 1ère lettre de t
tant que p ≠ 0 et δ(p, a) non défini
p ← f(p)
si δ(p, a) défini alors p ← δ(p, a)
si p ∈ F alors signaler occurrence de x dans t
Une variante de cet algorithme est l’algorithme de Knuth, Morris & Pratt. Il consiste à
optimiser la fonction d’échec (voir TD).
8
3. Recherche d’un motif fixe par lecture droite-gauche
Le miroir x~ d’un mot x est le mot lu à l’envers (en commençant par la fin).
Méthode :
• On construit l’AFD qui reconnaît les suffixes du mot miroir x~
• On lit le texte t dans l’AFD par une fenêtre de longueur |x|, dans laquelle on lit de droite à
gauche : en cas d’échec, on revient au dernier état final de l’AFD rencontré. Il correspond à
un suffixe s de x~. Donc de gauche à droite, s~ est un préfixe de x. On se décale dans t
pour commencer une nouvelle lecture à partir de s~. Ces sauts accélèrent considérablement
la recherche.
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
=> Si on veut essayer une position de la fenêtre qui chevauche la partie lue de droite à
gauche, il faut prendre le plus grand suffixe de cette partie lue (donné par un état final de
l’AFD) qui soit préfixe de x.
On notera qu’aucune position de la fenêtre située avant le début de ce suffixe (en particulier
avant la partie chevauchante) ne peut convenir, car si on pouvait lire x en entier de cette
position, on aurait lu un plus long suffixe dans la partie lue de droite à gauche.
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
9
On part de l’écorché de x~, et on rend initiaux tous les états sauf le dernier.
a b a c b a c b b a
5 4 3 1
dans l’état 5, on ne peut lire a,
5 étant terminal, il correspond au suffixe bcab de x~ = bbcab,
donc on reprend la lecture avec son miroir bacb, qui est préfixe de x
ce qui revient à se décaler d’une lettre
a b a c b a c b b a
5 4
dans l’état 5, on ne peut lire c,
5 étant terminal, il correspond au suffixe ab de x~ = bbcab,
donc on reprend la lecture avec son miroir ba, qui est préfixe de x
ce qui revient à se décaler de trois lettres
a b a c b a c b b a
5 4 3 2 1 = OK (on a tout lu et 5 est terminal)
10
Dans la méthode précédente avec l’AFD des suffixes du miroir x~, en cas d’échec dans la
lecture droite-gauche, l’AFD permet de trouver un préfixe du mot qu’on a lu qui est suffixe
de x~, c’est-à-dire dont le miroir est préfixe de x. On se décale pour recommencer la lecture
à partir de ce préfixe de x, qui peut être un début possible pour une nouvelle occurrence.
Dans l’algorithme de Boyer & Moore, qui est une variante, mais sans construire l’AFD
précédent, on ne repart pas d’un préfixe plus court que le mot lu dans la fenêtre droite-
gauche (donné par l’AFD), mais on utilise ce mot complet (on note w son miroir, dans
l’ordre normal gauche-droite) :
1) on numérote les positions dans la fenêtre de 1 à n (où n est la longueur du motif x)
dans l’ordre normal gauche-droite,
2) on construit une fonction f(i) qui donne la position la plus à droite où le suffixe de x
qu’on a lu dans t, noté w = x(i)…x(n) entre les positions i et n de la fenêtre,
réapparaît à gauche dans le motif x (s’il réapparaît en plusieurs endroits, c’est le
premier en partant de la droite qui est pris en compte),
3) on se décale dans le texte t pour faire coïncider la fenêtre avec cette occurrence de w,
soit i–f(i) lettres.
S’il y a eu échec sans lire un suffixe de x (dès la première lettre), on se décale d’une lettre
dans t.
i n
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
f(i)
<- i–f(i) ->
Position i 1 2 3 4 5 6
f(i) 0 0 0 0 2 5
a a a b a a c a a
c a a
5 = échec de la lecture du c attendu au lieu de b, suffixe lu = aa
on décale la fenêtre pour la faire coïncider avec le aa trouvé, en utilisant f(5) = 2
b a a c a a = OK occurrence trouvée
2
11
La différence avec l’AFD des suffixes de x~ est que le décalage ne garantit pas qu’on
repart d’un début de x. Avant l’occurrence du suffixe mise en coïncidence avec une portion
du motif, il peut y avoir des lettres divergentes qui font échec.
i n
|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
Exemple : Avec le même motif x = baacaa, si le texte avait été t = aaadaacaa au lieu de
aaabaacaa (avec d à la place de b), le décalage aurait donné un échec.
En fait, dans l’algorithme de Boyer & Moore, on utilise aussi le caractère qui fait l’échec
(ici le b trouvé à la place du c) pour améliorer le décalage. Cela ne change pas le calcul ci-
dessus dans le premier exemple. Mais dans le deuxième avec d à la place de b, cela aurait
évité le décalage donnant un échec.
L’algorithme tel qu’il est décrit ici est inopérant dans les cas où l’échec se produit dès la
première lettre, sans lecture dans la fenêtre droite-gauche : dans ce cas, on se décale d’une
seule lettre. Dans le véritable algorithme de Boyer & Moore, l’utilisation de la lettre d’échec
permet d’améliorer cette situation (voir TD).
Position i 1 2 3 4 5
f(i) 0 0 0 0 4
a b a c b a c b b a
b b
5 = échec de la lecture du b attendu au lieu de c, suffixe lu = b
on décale la fenêtre pour la faire coïncider avec le b trouvé, en utilisant f(5) = 4
mais cela revient à ne faire un décalage que d’une seule lettre
(b) b
4
échec dès la première lettre, pas de suffixe lu, donc décalage d’une seule lettre
b
b b
5 = échec de la lecture du b attendu au lieu de c, suffixe lu = b
12
on décale la fenêtre pour la faire coïncider avec le b trouvé, en utilisant f(5) = 4
mais cela revient à ne faire un décalage que d’une seule lettre
b a c b b = OK occurrence trouvée
4
4. Remarques complémentaires
4.3 Comparaison des deux méthodes : Morris & Pratt, suffixes du miroir
La méthode de l’AFD de Σ* x avec une représentation de l’AFD par une table de transition,
a un coût de construction en O(|x||Σ|), et un coût pour la lecture de t en O(|t|).
Si l’on ne construit par effectivement l’AFD, mais qu’on construit seulement la fonction
d’échec, le coût de la construction est en O(|x|), et celui de l’analyse en O(|t|log|Σ|).
Dans l’exemple traité ci-dessus, la méthode de Morris & Pratt (AFD de Σ* x) donnait les
mêmes sauts que celle de l’AFD des suffixes de x~ (trois positions de la fenêtre dans les
deux cas) Mais ce n’est pas vrai dans le cas général. Voici un exemple qui montre les
différences entre les deux méthodes.
Position 1 2 3 4 5 6 7 8
f(i) 0 1 0 1 2 3 4 5
a b b b b a b b a b b
0 échec dans la lecture : t contient a, alors qu’il faut un b
on se décale d’une lettre
13
a b b b b a b b a b b
1 2 échec dans la lecture : t contient b, alors qu’il faut un a
1 on reprend à l’état 1 = f(2)
a b b b b a b b a b b
1 2 échec dans la lecture : t contient b, alors qu’il faut un a
1 on reprend à l’état 1 = f(2)
a b b b b a b b a b b
1 2 3 4 5 6 7 8 occurrence de x trouvée
Dans cet exemple, la fonction de Morris & Pratt ne permet d’éviter aucun saut : on essaie
toutes les positions de la fenêtre jusqu’à l’occurrence de x.
2) méthode de l’AFD de x~
Dans ce cas, x est identique à son miroir, on a : x~ = x = bbabbabb.
a b b b b a b b a b b
b b a b b échec dans la lecture droite-gauche
Le principe est de repartir du plus long préfixe w de x lu dans la fenêtre droite-gauche
(c’est-à-dire tel que w~ est suffixe de x~). Ici w = bbabb est le plus long préfixe trouvé,
donc on décale la fenêtre pour la faire coïncider avec bbabb.
a b b b b a b b a b b
occurrence de x trouvée
Dans ce cas, on ne fait qu’un seul saut, et le calcul est bien meilleur qu’avec Morris &
Pratt.
14
CHAPITRE 5 : ANALYSE LEXICALE
L’analyse lexicale est un autre domaine fondamental d’application des automates finis.
Dans la plupart des langages de programmation, les unités lexicales (identificateurs, mots-
clefs du langage, nombres, etc.) sont définies par des expressions régulières (ou
rationnelles). L’analyse lexicale consiste à détecter ces unités dans le flot de caractères
constitué par le programme. Pour ce faire, on associe des automates finis aux expressions
régulières.
Il existe des méthodes efficaces permettant de construire un AFD pour toute expression
régulière.
Définition. Soit Σ un alphabet. Les expressions rationnelles (ou régulières) sur Σ et les
langages correspondants sont définis récursivement :
1) • ∅ est une expression rationnelle, et représente l’ensemble vide,
• ε est une expression rationnelle, et représente l’ensemble {ε},
• pour toute lettre a de Σ, a est une expression rationnelle, et représente l’ensemble
{a}.
2) Si r et s sont des expressions rationnelles, qui représentent les langages R et S,
alors r + s, rs, et r* sont des expressions rationnelles qui représentent les
langages R ∪ S, RS et R* .
Théorème (Kleene). Tout langage reconnu par un AFD peut être représenté par une
expression régulière, et réciproquement.
Preuve
• Tout langage représenté par une expression régulière est reconnu par un AFD.
Les langages ∅, {ε} et {a} pour toute lettre a de Σ, sont reconnus par AFD.
De plus, les propriétés de clôture des langages reconnus par AFD montrent que l’union, la
concaténation et l’étoile sont aussi reconnus par AFD. Donc les langages représentés par
expression régulière sont reconnus par AFD (méthode dite de Thompson).
On verra plus loin une deuxième méthode effective pour construire un AFD reconnaissant
le langage décrit par une expression régulière (algo de Glushkov).
• Tout langage reconnu par AFD peut être représenté par une expression régulière.
On peut le montrer directement, ou utiliser une méthode de résolution d’équation (voir plus
loin).
1
1.3 AFN associé à une expression régulière (algorithme de Glushkov)
Première étape : Linéariser l’expression rationnelle, en remplaçant toutes les lettres par des
symboles distincts (de la gauche vers la droite).
(x1 + x2 x3 )*(ε + x4 x5 )
Suivant
x1 x1 , x2 , x4
x2 x3
x3 x1 , x2 , x4
x4 x5
x5
2
On construit par induction un AFN avec ε–transition reconnaissant le langage décrit par une
expression rationnelle, à partir des opérations de combinaison définissant les expressions
rationnelles (union, concaténation, étoile).
Exemple :
(a + ab)*(ε + ab)
La taille d’une expression rationnelle r, notée |r|, est son nombre de caractères (sans
compter les parenthèses).
Exemple : |(a + ab)*(ε + ab)| = 9.
L’analyse de t dans l’AFN se fait en partant de Cloture(I) où I est l’ensemble des états
initiaux, et en suivant les flèches indiquées par Transition(P, a).
2) analyse de t = babbaab
Remarque : dans ce cas, la construction de l’AFD par déterminisation n’aurait pas été trop
coûteuse, car l’AFN pour bba* a 3 états, et on est dans le cas où l’AFD pour Σ* bba* a le
même nombre d’états.
Exemple : supposons que le programme source contienne le calcul décrit par l’expression
Tokens reconnus : (id, var1) (op, +) (nb, 100) (op, *) (id, var2)
Cette phase de la compilation est réalisée par une grammaire (voir deuxième partie du cours
avec Patrice Enjalbert).
On peut résumer la compilation comme une succession de phases dont les deux premières
sont l’analyse lexicale et l’analyse syntaxique :
6
Cette décomposition en phases permet de rendre plus simple et plus efficace la conception
des compilateurs. Le fait que l’analyseur lexical soit la seule phase de la compilation qui
traite directement le texte source est important. À partir de la phase d’analyse syntaxique, le
compilateur ne traite plus que des unités lexicales indépendantes des particularités du
langage source.
Les unités lexicales du programme source sont définies par des modèles : identificateurs,
nombres, opérations arithmétiques, etc. Les parties du programme source éliminées au
cours de cette phase correspondent également à des modèles : espaces, commentaires.
Ces modèles ont la forme d’expressions régulières (ou rationnelles).
On a vu qu’on peut les représenter de manière équivalente par des automates finis.
Par exemple, dans un langage comme Pascal, les identificateurs sont définis par le modèle
suivant :
lettre → [A-Za-z]
chiffre→ [0-9]
id → lettre (lettre | chiffre)*
Dans le fonctionnement d’un tel automate, on associe une action à chaque état final : si on
lit par exemple « var1 », on suit le parcours 0111 dans l’automate, qui s’arrête dans l’état
final 1. On a alors détecté un identificateur.
Action : l’analyseur forme l’unité lexicale (id, var1) et ajoute le symbole var1 dans la table
des symboles.
7
Certains opérateurs qui ont des caractères communs posent problème :
Si le flot de caractères comporte « <= », l’analyseur lit d’abord <. Il se trouve alors dans
l’état final 1, mais il ne sait pas s’il doit
- s’arrêter et détecter l’unité lexicale (op, <)
- ou continuer et détecter l’unité lexicale (op, <=) dans l’état final 2.
(le problème est le même avec les boucles sur l’état final dans les automates précédents).
L’utilisation de ces automates doit donc être améliorée, en incluant un mécanisme de lecture
d’un caractère « à l’avance » pour permettre de choisir dans de telles situations.
%%
abcde printf("[%s]", yytext);
%%
int yywrap() { return 1;}
main() { yylex(); }
Avec ces instructions C minimales, l’automate lit chaque ligne, en la recopiant, et s’il trouve
le mot ‘abcde’, il l’encadre par des crochets (la variable %s renvoie à yytext, qui contient
le motif reconnu).
La commande flex permet de transformer ce fichier en un fichier essai.c de code C :
9
$ cc essai.c -o essai
Dans cette version minimale, la fonction essai ne s’arrête pas (faire Ctrl-D pour sortir).
$ essai
aaaaaaaaabcdeeeeeeee
aaaaaaaa[abcde]eeeeeee
Voilà un autre exemple avec des définitions dans la première partie. Les identifiants utilisés
dans ces définitions peuvent être repris dans les expressions régulières de la deuxième
partie, entre accolades :
voyelle [aeiou]
consonne [b-df-hj-np-tv-z]
%%
({consonne}{voyelle})*{consonne} printf("[%s]", yytext);
%%
int yywrap() { return 1; }
main() { yylex(); }
On obtient :
$ essai
consonne
[con][son][ne]
bonjour
[bon][j]ou[r]
Encore un autre exemple qui montre un mini-analyseur lexical pour un langage de type
Pascal, en se limitant aux espacements (blancs, tabulations), identificateurs, nombres,
opérations :
delim [ \t]
bl {delim}+
lettre [A-Za-z]
chiffre [0-9]
id {lettre}+({lettre}|{chiffre})*
nb {chiffre}+(\.{chiffre}+)?(E[+\-]?{chiffre}+)?
%%
{bl} {/* pas d action */}
{id} printf("[id,%s]", yytext);
{nb} printf("[nb,%s]", yytext);
[+*] printf("[op,%s]", yytext);
%%
int yywrap() { return 1;}
main() { yylex(); }
Après compilation du programme lex, on peut analyser des expressions et détecter les
unités lexicales :
10
$ pasc
var1 + var2
[id,var1][op,+][id,var2]
x1 + 1000 * valeur
[id,x1][op,+][nb,1000][op,*][id,valeur]
11
CHAPITRE 6 : MINIMISATION D’UN AFD
1.1 Problème
Parmi les AFD reconnaissant un même langage L, peut-on en trouver un qui a le nombre
minimal d’états ?
Oui, et il est unique (à un renommage près des états) :
• on l’obtient en supprimant les états inaccessibles
• puis on identife les états restants qui jouent un rôle identique du point de vue de la
reconnaissance
Les états qu’on ne peut atteindre à partir de l’état initial n’apportent aucune contribution au
langage L reconnu par l’AFD.
Proposition. La relation ~ est une relation d’équivalence sur l’ensemble des états Q :
réflexive, symétrique, transitive.
Exemple :
2
i = 1, F = {6, 7, 8}
1 2 3 4 5 6 7 8
a 1 2 4 3 2 3 8 7
b 5 5 6 8 4 7 7 7
On voit facilement qu’aucun mot ne sépare les états 7 et 8. À partir de ces états, on peut lire
n’importe quel mot sur {a, b}. Ces deux états pourront donc être confondus dans l’AFD
minimal.
1. Équivalence ~0
Initialement, les deux classes sont celle des états finals [6]0 = {6, 7, 8} et celle des autres
[1]0 = {1, 2, 3, 4, 5}
1 2 3 4 5 6 7 8
~ 0 [1]0 [1]0 [1]0 [1]0 [1]0 [6]0 [6]0 [6]0
2. Équivalence ~1
Pour éventuellement scinder des classes, il faut regarder dans quelles classes vont les
transitions partant des états. On en déduit les nouvelles classes pour ~1 .
1 2 3 4 5 6 7 8
~ 0 [1]0 [1]0 [1]0 [1]0 [1]0 [6]0 [6]0 [6]0
a [1]0 [1]0 [1]0 [1]0 [1]0 [1]0 [6]0 [6]0
b [1]0 [1]0 [6]0 [6]0 [1]0 [6]0 [6]0 [6]0
~ 1 [1]1 [1]1 [3]1 [3]1 [1]1 [6]1 [7]1 [7]1
3. Équivalence ~2
Dans le tableau ci-après, on n’indique que les transitions qui vont dans une autre classe.
3
1 2 3 4 5 6 7 8
~ 1 [1]1 [1]1 [3]1 [3]1 [1]1 [6]1 [7]1 [7]1
a
b [6]1 [7]1 [3]1
~ 2 [1]2 [1]2 [3]2 [4]2 [5]2 [6]2 [7]2 [7]2
4. Équivalence ~3 = ~2
Il n’y a plus aucune scission de classes, donc la suite d’équivalences s’est stabilisée, et on
obtient celle de l’AFD minimal.
1 2 3 4 5 6 7 8
~ 2 [1]2 [1]2 [3]2 [4]2 [5]2 [6]2 [7]2 [7]2
a
b
~ 3 [1]3 [1]3 [3]3 [4]3 [5]3 [6]3 [7]3 [7]3