Automates finis
Cours complet · OCaml (option informatique), chapitre 18 · prépas MPSI et MP, option informatique
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
<i class="fa-solid fa-compass mr-2" style="color:#9A563B"></i>18.1 Introduction et motivation
Comment décider, mécaniquement, si un mot appartient à un langage — les mots se terminant par a, ceux ayant un nombre pair de a, ceux contenant le motif ab ? L'outil fondamental est l'automate fini : une machine à un nombre fini d'états, qui lit le mot lettre par lettre et change d'état selon des transitions. Le mot est accepté si, une fois lu, la machine se trouve dans un état acceptant.
Ce chapitre, en ouverture du cours, est une belle synthèse : un automate se modélise par un enregistrement (chapitre 7) contenant un tableau de transitions (chapitre 5) ; la reconnaissance lit une chaîne (chapitre 6) ; tester si le langage est vide est une question d'accessibilité dans un graphe (chapitre 15) ; et compter les mots acceptés relève de la programmation dynamique (chapitre 13).
18.2 Représenter un automate
On considère un automate fini déterministe (AFD) complet : un état initial, des états acceptants, et pour chaque état et chaque lettre, exactement une transition. Les états sont numérotés 0..n-1, les lettres codées par des indices 0..k-1.
type automate = {
nb_etats : int;
initial : int;
acceptants : bool array; (* acceptants.(e) = e est acceptant *)
delta : int array array; (* delta.(etat).(lettre) = état suivant *)
}
Deux états : 0 (la dernière lettre lue n'est pas a, ou rien lu) et 1 (la dernière lettre est a). On code a par 0, b par 1.
let termine_par_a = {
nb_etats = 2;
initial = 0;
acceptants = [| false; true |];
delta = [| [| 1; 0 |]; (* depuis 0 : lire a -> 1, lire b -> 0 *)
[| 1; 0 |] |]; (* depuis 1 : lire a -> 1, lire b -> 0 *)
}
L'état 1 (acceptant) est atteint exactement quand la dernière lettre lue est a.
18.3 Reconnaître un mot
On part de l'état initial et l'on applique, pour chaque lettre, la transition correspondante ; le mot est accepté si l'état final est acceptant. La fonction symbole traduit un caractère en indice de lettre (elle dépend de l'alphabet).
let reconnait a symbole mot =
let etat = ref a.initial in
for i = 0 to String.length mot - 1 do
etat := a.delta.(!etat).(symbole mot.[i])
done;
a.acceptants.(!etat)
# let sym c = int_of_char c - int_of_char 'a';; (* a->0, b->1 *)
# reconnait termine_par_a sym "abba";;
- : bool = false
# reconnait termine_par_a sym "abba" ;;
# reconnait termine_par_a sym "baba";;
- : bool = false
# reconnait termine_par_a sym "bba";;
- : bool = true
La reconnaissance est linéaire en la longueur du mot, : une seule passe, une transition par lettre, en temps constant (accès delta.(e).(s)). C'est l'efficacité qui fait la force des automates pour l'analyse lexicale, la recherche de motifs, etc.
18.4 Opérations sur les automates
Échanger états acceptants et non acceptants reconnaît le langage complémentaire (tous les mots sauf ceux acceptés).
let complement a = { a with acceptants = Array.map not a.acceptants }
La mise à jour fonctionnelle { a with ... } (chapitre 7) produit un nouvel automate ; Array.map not inverse chaque état acceptant.
Pour reconnaître les mots acceptés par deux automates à la fois, on fait fonctionner les deux en parallèle : un état du produit est un couple (p, q), codé par p * b.nb_etats + q.
let intersection a b =
let nb_sym = Array.length a.delta.(0) in
let code p q = p * b.nb_etats + q in
let n = a.nb_etats * b.nb_etats in
let delta = Array.make_matrix n nb_sym 0 in
let acceptants = Array.make n false in
for p = 0 to a.nb_etats - 1 do
for q = 0 to b.nb_etats - 1 do
let e = code p q in
acceptants.(e) <- a.acceptants.(p) && b.acceptants.(q);
for s = 0 to nb_sym - 1 do
delta.(e).(s) <- code a.delta.(p).(s) b.delta.(q).(s)
done
done
done;
{ nb_etats = n; initial = code a.initial b.initial; acceptants; delta }
Un état produit est acceptant si les deux composantes le sont ; la transition fait avancer les deux automates simultanément. C'est la construction par produit.
<i class="fa-solid fa-dumbbell mr-2" style="color:#2E7559"></i>18.5 Exercices résolus
Niveau (application directe du cours)
Écrire reconnait et l'appliquer pour vérifier que "bba" se termine par a mais pas "bab".
Démonstration
let reconnait a symbole mot =
let etat = ref a.initial in
for i = 0 to String.length mot - 1 do
etat := a.delta.(!etat).(symbole mot.[i])
done;
a.acceptants.(!etat)
let sym c = int_of_char c - int_of_char 'a'
(* reconnait termine_par_a sym "bba" = true ; ... "bab" = false *)
Sur "bba" : états (acceptant) true. Sur "bab" : (non acceptant) false.
Définir l'automate termine_par_a (alphabet ).
Démonstration
let termine_par_a = {
nb_etats = 2; initial = 0;
acceptants = [| false; true |];
delta = [| [| 1; 0 |]; [| 1; 0 |] |];
}
Lire a mène toujours à l'état 1 (acceptant), lire b à l'état 0 : l'état final reflète la dernière lettre.
Définir pair_de_a : les mots ayant un nombre pair de a.
Démonstration
let pair_de_a = {
nb_etats = 2; initial = 0; (* état = parité du nombre de a lus *)
acceptants = [| true; false |]; (* 0 = pair (acceptant), 1 = impair *)
delta = [| [| 1; 0 |]; (* depuis pair : a -> impair, b -> pair *)
[| 0; 1 |] |]; (* depuis impair : a -> pair, b -> impair *)
}
Lire a bascule la parité (états 0 et 1), lire b la laisse. L'état acceptant 0 (pair) inclut le mot vide ( occurrence, pair).
Niveau (raisonnement intermédiaire)
Définir un automate reconnaissant les mots qui contiennent ab comme facteur.
Démonstration
let contient_ab = {
nb_etats = 3; initial = 0;
(* 0 : rien de prometteur ; 1 : on vient de lire un a ; 2 : ab vu (piège acceptant) *)
acceptants = [| false; false; true |];
delta = [| [| 1; 0 |]; (* 0 : a -> 1, b -> 0 *)
[| 1; 2 |]; (* 1 : a -> 1 (reste prêt), b -> 2 (ab trouvé !) *)
[| 2; 2 |] |]; (* 2 : on y reste, ab est déjà vu *)
}
L'état 1 mémorise « le dernier caractère est un a » ; y lire un b complète le motif et fait passer à l'état acceptant 2, dont on ne sort plus (état puits acceptant).
Écrire langage_non_vide a : existe-t-il un mot accepté ?
Démonstration
let langage_non_vide a =
let vu = Array.make a.nb_etats false in
let nb_sym = Array.length a.delta.(0) in
let rec visite e =
if not vu.(e) then begin
vu.(e) <- true;
for s = 0 to nb_sym - 1 do visite a.delta.(e).(s) done
end
in
visite a.initial;
let trouve = ref false in
for e = 0 to a.nb_etats - 1 do
if vu.(e) && a.acceptants.(e) then trouve := true
done;
!trouve
Le langage est non vide si et seulement si un état acceptant est accessible depuis l'état initial dans le graphe des transitions. C'est un parcours en profondeur (chapitre 15) : l'automate est un graphe orienté.
Écrire complement a et vérifier que complement termine_par_a reconnaît les mots qui ne se terminent pas par a.
Démonstration
let complement a = { a with acceptants = Array.map not a.acceptants }
Sur un AFD complet, inverser les états acceptants suffit : un mot est dans le complément ssi il n'était pas accepté. reconnait (complement termine_par_a) sym "bba" vaut false, et ... "bab" vaut true.
Niveau (approfondissement)
Écrire intersection a b (automate produit) et l'utiliser pour reconnaître les mots qui se terminent par a et ont un nombre pair de a.
Démonstration
let intersection a b =
let nb_sym = Array.length a.delta.(0) in
let code p q = p * b.nb_etats + q in
let n = a.nb_etats * b.nb_etats in
let delta = Array.make_matrix n nb_sym 0 in
let acceptants = Array.make n false in
for p = 0 to a.nb_etats - 1 do
for q = 0 to b.nb_etats - 1 do
let e = code p q in
acceptants.(e) <- a.acceptants.(p) && b.acceptants.(q);
for s = 0 to nb_sym - 1 do
delta.(e).(s) <- code a.delta.(p).(s) b.delta.(q).(s)
done
done
done;
{ nb_etats = n; initial = code a.initial b.initial; acceptants; delta }
let les_deux = intersection termine_par_a pair_de_a
L'automate produit fait avancer les deux automates en parallèle ; un état est acceptant ssi ses deux composantes le sont. les_deux reconnaît l'intersection des deux langages.
Écrire compte_mots a longueur : le nombre de mots de longueur exactement longueur acceptés par a.
Démonstration
let compte_mots a longueur =
let nb_sym = Array.length a.delta.(0) in
let nb = Array.make a.nb_etats 0 in
nb.(a.initial) <- 1;
for _i = 1 to longueur do
let suivant = Array.make a.nb_etats 0 in
for e = 0 to a.nb_etats - 1 do
for s = 0 to nb_sym - 1 do
let e' = a.delta.(e).(s) in
suivant.(e') <- suivant.(e') + nb.(e)
done
done;
for e = 0 to a.nb_etats - 1 do nb.(e) <- suivant.(e) done
done;
let total = ref 0 in
for e = 0 to a.nb_etats - 1 do
if a.acceptants.(e) then total := !total + nb.(e)
done;
!total
Programmation dynamique (chapitre 13) sur les états : nb.(e) compte les mots (de la longueur courante) menant à l'état e. À chaque lettre lue, on propage ces comptes le long des transitions. À la fin, on somme sur les états acceptants. Coût — sans énumérer les mots un à un.
Construire un automate qui, lisant un entier écrit en binaire (poids fort en tête, le bit 0 codé a, le bit 1 codé b), accepte ssi cet entier est multiple de .
Démonstration
(* état = reste modulo 3 du préfixe lu ; lire un bit b : reste := (2*reste + b) mod 3 *)
let mult3 = {
nb_etats = 3; initial = 0;
acceptants = [| true; false; false |]; (* reste 0 : multiple de 3 *)
delta = [| [| 0; 1 |]; (* reste 0 : bit0 -> 0, bit1 -> 1 *)
[| 2; 0 |]; (* reste 1 : bit0 -> 2, bit1 -> 0 *)
[| 1; 2 |] |]; (* reste 2 : bit0 -> 1, bit1 -> 2 *)
}
L'état est le reste modulo de la valeur lue : lire un bit b transforme le reste r en (2r + b) mod 3 (décalage binaire). On accepte au reste 0. Par exemple "bb" code : accepté. Un automate à trois états reconnaît ainsi une infinité de nombres — la puissance de la mémoire finie*.
Écrire equivalents a b longueur : a et b acceptent-ils les mêmes mots de longueur longueur ?
Démonstration
let equivalents a b longueur =
let nb_sym = Array.length a.delta.(0) in
let ok = ref true in
let rec explore reste ea eb =
if a.acceptants.(ea) <> b.acceptants.(eb) then ok := false;
if reste > 0 then
for s = 0 to nb_sym - 1 do
explore (reste - 1) a.delta.(ea).(s) b.delta.(eb).(s)
done
in
explore longueur a.initial b.initial;
!ok
On fait avancer simultanément les deux automates (comme dans le produit) le long de tous les préfixes de longueur longueur : à chaque étape, l'acceptation doit coïncider. Inutile de construire les mots — seuls comptent les deux états courants (ea, eb). Pour une équivalence exacte (toutes longueurs), on testerait le vide de intersection a (complement b) et réciproquement : l'équivalence des AFD est décidable.
- Un AFD : états
0..n-1, uninitial, desacceptants, etdelta.(etat).(lettre)(enregistrement + tableau de transitions). - Reconnaître : partir de l'initial, suivre une transition par lettre, accepter si l'état final est acceptant. Coût .
- Construire un automate, c'est trouver la bonne information à mémoriser dans l'état (dernière lettre, parité, reste modulo , avancement d'un motif).
- Complément : inverser les acceptants (AFD complet). Intersection : automate produit (états = couples, avancer les deux en parallèle).
- Langage non vide : accessibilité d'un acceptant (parcours, chapitre 15). Compter les mots acceptés : programmation dynamique sur les états (chapitre 13).
- Un nombre fini d'états reconnaît des langages infinis ; l'équivalence des AFD est décidable.
18.6 Exercices d'entraînement
Légende : application directe, raisonnement intermédiaire, approfondissement ; signale un classique incontournable. La numérotation prolonge celle des dix exercices résolus.
Thème A — Construire des automates.
- [11.] Automate reconnaissant les mots commençant par
a. - [12.] Automate reconnaissant les mots de longueur paire.
- [13.] Automate reconnaissant les mots sans le facteur
aa.
Thème B — Reconnaissance et variantes.
- [14.] Adapter
reconnaitpour un automate incomplet (transition absente codée-1rejet). - [15.]
reconnait_liste: reconnaissance d'un mot donné commechar listau lieu d'une chaîne. - [16.]
etats_atteignables a: la liste des états accessibles depuis l'initial.
Thème C — Opérations.
- [17.]
union a b(automate produit, acceptant si l'un accepte). - [18.]
langage_infini a: le langage est-il infini ? (un cycle « utile » entre l'initial et un acceptant). - [19.]
plus_court_mot_accepte a: la longueur du plus court mot accepté (BFS sur les états,Nonesi vide).
Thème D — Modélisation.
- [20.] Automate des entiers binaires multiples de (cinq états).
- [21.] Recherche d'un motif quelconque
mdans un texte par construction d'un automate (lien avec le chapitre 6). - [22.] Discuter : quelle information un automate fini ne peut pas mémoriser, et pourquoi reconnaître « autant de
aque deb» lui est impossible ?