cursus.

Cours 2 · Langages réguliersLeçon 1 sur 4

Automates finis déterministes

5 h de lecture9 sections Version PDF

À la fin de cette leçon, vous saurez

États, transitions, état initial, états acceptants ; exécution et acceptation d'un mot ; construire un AFD pour un langage donné ; automate complet.

Voici le cœur du cours, et l'objet le plus utile de toute l'année. Un automate fini est une machine d'une simplicité extrême — une poignée d'états, une flèche par lettre — et pourtant elle est partout : dans le premier étage de tout compilateur, dans la barre de recherche qui valide une adresse de courriel, dans le protocole réseau qui suit l'état d'une connexion, dans le distributeur de billets. Chaque fois qu'un programme doit reconnaître une forme dans un flux de symboles avec une mémoire bornée, il y a un automate fini, explicite ou caché.

Ce chapitre le définit, montre comment l'exécuter, et surtout comment en construire un pour un langage donné. Le mot d'ordre du bloc — implémenter plutôt que réciter — commence ici : à la fin, vous aurez codé le moteur d'un automate quelconque.

La définition : un quintuplet

Un automate fini déterministe (AFD) est la donnée de cinq éléments, notés (Q,Σ,δ,q0,F)(Q, \Sigma, \delta, q_0, F) :

SymboleNomCe que c'est
QQensemble des étatsfini ; chaque état est une « situation » de la lecture
Σ\Sigmal'alphabetles symboles lus en entrée (chapitre 1)
δ\deltala fonction de transitionδ:Q×ΣQ\delta : Q \times \Sigma \to Q (chapitre 2)
q0q_0l'état initialoù l'on commence ; q0Qq_0 \in Q
FFles états acceptantsFQF \subseteq Q ; où l'on a le droit de finir

L'idée à retenir dès maintenant, et qui donne le sens de tout le reste : chaque état est une mémoire. Il ne mémorise pas les symboles déjà lus — un automate fini ne peut pas se souvenir de tout — mais la seule chose qui reste utile pour la suite : « où en suis-je de ma reconnaissance ? ». Comme QQ est fini, l'automate ne peut distinguer qu'un nombre fini de situations. C'est à la fois sa force et sa limite, et le chapitre 6 en tirera les conséquences.

Le déterminisme

Le mot « déterministe » a un sens précis : depuis un état donné, en lisant une lettre donnée, il y a exactement une transition possible. C'est l'exigence de fonction du chapitre 2 — une seule sortie par entrée. L'automate ne fait donc jamais de choix : à chaque instant, l'état courant et la lettre lue déterminent entièrement l'état suivant.

Cette propriété a une conséquence pratique majeure : l'exécution est rapide et simple. On lit le mot une seule fois, de gauche à droite, en suivant les flèches, sans jamais revenir en arrière ni explorer plusieurs possibilités. Le temps de reconnaissance est proportionnel à la longueur du mot — on ne fait pas mieux. C'est pourquoi les analyseurs lexicaux réels (chapitre 9) sont des AFD.

Exécution et acceptation

Exécuter un AFD sur un mot w=a1a2anw = a_1 a_2 \dots a_n, c'est suivre un chemin :

q0  --a1-->  q1  --a2-->  q2  --a3-->  ...  --an-->  qn

On part de q0q_0, on applique δ\delta à chaque lettre, on arrive dans un état final qnq_n. Puis la règle d'acceptation, unique et sans nuance :

Le mot ww est accepté si et seulement si l'état d'arrivée qnq_n appartient à FF.

Sinon, il est rejeté. Le langage reconnu par l'automate A\mathcal{A}, noté L(A)L(\mathcal{A}), est l'ensemble de tous les mots acceptés. Un langage est dit régulier s'il existe un automate fini qui le reconnaît — c'est la définition que tout le bloc II va explorer.

Notez le cas du mot vide ε\varepsilon : l'automate ne lit aucune lettre, reste en q0q_0, et ε\varepsilon est accepté exactement quand q0Fq_0 \in F. Encore une fois, tout se joue sur le statut du mot vide.

Construire un AFD : penser en situations

C'est le savoir-faire du chapitre, et il s'apprend par une seule question, posée à l'envers de l'intuition :

Quelles situations dois-je distinguer pour décider, à la fin, si j'accepte — et rien de plus ?

Chaque situation distincte devient un état. Prenons L=L = « les mots sur {a,b}\{a, b\} contenant un nombre pair de aa ». Qu'ai-je besoin de retenir pendant la lecture ? Uniquement la parité du nombre de aa vus jusqu'ici. Deux situations, donc deux états :

        b           b       ┌──┐        ┌──┐       ▼  │        ▼  │   ──▶( pair )──a──▶( impair )        ▲               │        └───────a───────┘
  • pair est l'état initial (zéro aa, c'est pair) et le seul état acceptant ;
  • lire un a bascule la parité ; lire un b ne change rien.

Plutôt que de l'imaginer, regardez-le fonctionner. L'animation ci-dessous fait lire le mot abba à cet automate : le ruban d'entrée défile en haut, l'état courant s'allume, et un jeton portant la lettre lue parcourt la transition prise. Comme abba contient deux a, le chemin revient à l'état pair et le mot est accepté.

Animation · étape 1 / 60:00 / 0:13

L'automate démarre dans l'état pair. Mot à lire : « abba ».

Prêt à lancer · 0:00 / 0:13
Étapes

Deux réflexes de conception valent d'être énoncés.

Distinguer juste ce qu'il faut. Retenir le nombre exact de aa demanderait une infinité d'états — impossible. Ne retenir que la parité suffit, parce que c'est la seule information qui sert à la décision finale. Un bon AFD oublie tout le reste.

Ni trop, ni trop peu d'états. Trop d'états distinguent des situations qui mènent au même comportement (gaspillage) ; trop peu confondent des cas qu'il fallait séparer (erreur). Le chapitre 6 donnera l'automate minimal, qui a le nombre exact d'états nécessaires.

Quiz · vérifiez votre compréhension Sans réponse

Dans un AFD reconnaissant « les mots contenant un nombre pair de a », que représente chaque état ?

L'automate complet

Un détail technique qui a des conséquences. Un AFD est complet si δ(q,a)\delta(q, a) est défini pour tout état qq et toute lettre aa : depuis n'importe quel état, chaque lettre a une flèche sortante. Notre exemple ci-dessus est complet.

Souvent, on dessine un automate incomplet : on omet les transitions qui « mènent à l'échec », pour ne pas surcharger le schéma. Pour le rendre complet, on ajoute un état puits (ou état poubelle) : un état non acceptant, absorbant, vers lequel partent toutes les transitions manquantes, et qui boucle sur lui-même. Une fois entré dans le puits, on n'en sort plus — le mot est condamné au rejet.

Pourquoi s'en soucier ? Parce que certaines constructions l'exigent. La plus importante : pour reconnaître le complément d'un langage — les mots que l'automate rejetait —, il suffit d'échanger les états acceptants et non acceptants. Mais cet échange ne donne le bon résultat que si l'automate est complet : sinon, un mot qui « bloquait » faute de transition ne serait ni dans le langage ni dans son complément. La complétude est le petit soin qui rend les propriétés de clôture du chapitre 6 correctes.

Quiz · vérifiez votre compréhension Sans réponse

Un AFD complet reconnaît un langage L. On échange ses états acceptants et non acceptants (F devient Q\\F). Quel langage reconnaît le nouvel automate ?

À vous

Le TP fondateur du bloc : implémenter un AFD. Vous écrivez d'abord le moteur d'exécution, une poignée de lignes qui exécute n'importe quel automate décrit par sa table de transition — la preuve que toute la logique du langage tient dans δ\delta, pas dans le code. Vous le testez sur « nombre pair de aa », puis vous concevez vous-même l'automate de « mots se terminant par abab ».

C'est ce passage du papier au code que le programme du cours désigne comme le plus formateur : écrire le moteur fait comprendre l'automate mieux que n'importe quel exercice de tracé.

Exercice · JavaScript · à vous de jouer

Écrivez le moteur d'exécution générique d'un AFD (états, alphabet, transitions, initial, acceptants), testez-le sur « nombre pair de a », puis concevez vous-même l'AFD du langage « mots se terminant par ab ».

En attente
// Un AFD est un quintuplet (Q, Σ, δ, q0, F). On le décrit ici comme une
// donnée, et on écrit UNE fois le moteur qui exécute n'importe quel AFD.
//
// Langage visé : L = { mots sur {a,b} contenant un nombre PAIR de a }.

const afd = {
  etats: ["pair", "impair"],          // Q
  alphabet: ["a", "b"],               // Σ
  initial: "pair",                    // q0 : zéro 'a' lu, c'est pair
  acceptants: ["pair"],               // F
  // δ : table de transition. delta["etat"]["lettre"] = etat d'arrivée.
  delta: {
    pair:   { a: "impair", b: "pair" },   // lire un a change la parité ; b ne
    impair: { a: "pair",   b: "impair" }, // change rien
  },
};

// ── À VOUS (1) : le moteur d'exécution ──────────────────────────────────────
// Partir de l'état initial, lire le mot lettre par lettre en suivant δ,
// puis accepter si l'état final est dans F.
function accepte(afd, mot) {
  let etat = afd.initial;
  // à compléter : boucle sur les lettres de 'mot', mise à jour de 'etat'
  return false; // à remplacer : etat est-il acceptant ?
}

// ── Vérification ────────────────────────────────────────────────────────────
const tests = [
  ["",       true],   // 0 a : pair
  ["aa",     true],   // 2 a
  ["a",      false],  // 1 a
  ["abab",   true],   // 2 a
  ["baaab",  false],  // 3 a
  ["bbbb",   true],   // 0 a
];
for (const [mot, attendu] of tests) {
  const r = accepte(afd, mot);
  console.log("'" + mot + "'".padEnd(8) + " -> " + r + (r === attendu ? "  ok" : "  ✗"));
}

// ── À VOUS (2) : concevoir un AFD ───────────────────────────────────────────
// Décrivez ci-dessous un AFD 'afd2' pour L2 = { mots qui SE TERMINENT par 'ab' }.
// Indice : 3 états — "rien de spécial", "vient de voir un a", "vient de voir ab".
const afd2 = null; // à construire, sur le même modèle que 'afd'
if (afd2) {
  for (const mot of ["ab", "aab", "abab", "aba", "b", "ba"]) {
    console.log("L2 '" + mot + "' -> " + accepte(afd2, mot));
  }
}

Console de sortie
Le résultat s'affiche dans la console

Ce que la suite en fait

Vous savez maintenant ce qu'est un langage régulier — un langage reconnu par un AFD — et comment en construire un. Mais l'AFD est parfois pénible à concevoir directement : son exigence de déterminisme force à tout anticiper.

Le chapitre 4 lève cette contrainte avec l'automate non déterministe, bien plus facile à écrire, puis montre le résultat central du cours : on peut toujours transformer un automate non déterministe en AFD équivalent. C'est la déterminisation — le premier des deux points qui coincent, et celui où le 2n2^n de l'ensemble des parties (chapitre 2) va enfin se manifester.

À retenir

Flashcards · 1 / 4Toucher pour retourner

Exercices d'entraînement

Exercice 1 · cherchez avant de lire la correction

Construire un AFD

Sur Σ={a,b}\Sigma = \{a, b\}, construire un AFD reconnaissant les mots qui contiennent le facteur aa. Donner les états (avec leur signification), l'état initial, les états acceptants et la table de transition.

Exercice 2 · cherchez avant de lire la correction

Lire un AFD

Un AFD sur Σ={a,b}\Sigma = \{a, b\} a pour états p (initial) et q (acceptant), et pour transitions : δ(p,a)=q\delta(p,a)=q, δ(q,a)=q\delta(q,a)=q, δ(p,b)=p\delta(p,b)=p, δ(q,b)=p\delta(q,b)=p. Quel langage reconnaît-il ?

Exercice 3 · cherchez avant de lire la correction

Complément d'un langage

En repartant de l'AFD de l'exercice 2 (mots se terminant par a), donner un AFD reconnaissant le complément de ce langage, et décrire ce complément.

Fin de la leçon

Vous avez parcouru les 9 sections.

Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.