Skip to content

Exercice 1 : Recherche de motifs (algorithmes KMP et Boyer-Moore)

🌐 Énoncé du problème / Cadre d’étude

Section titled “🌐 Énoncé du problème / Cadre d’étude”

La recherche efficace de motifs dans une chaîne de caractères est un enjeu fondamental en informatique, intervenant dans l’analyse de séquences génomiques, le traitement du langage naturel ou encore la recherche textuelle (fonctionnalité “Rechercher” des éditeurs).

Soit TT un texte de longueur nn et PP un motif de longueur mm, tous deux construits sur un alphabet fini Σ\Sigma de taille σ\sigma. On suppose généralement m≤nm \le n. L’objectif est de trouver toutes les occurrences de PP dans TT, c’est-à-dire l’ensemble des indices i∈{0,…,n−m}i \in \{0, \dots, n-m\} tels que pour tout j∈{0,…,m−1}j \in \{0, \dots, m-1\}, T[i+j]=P[j]T[i+j] = P[j].

Dans ce problème, nous étudions deux algorithmes de pointe permettant d’améliorer la complexité naïve en O(n⋅m)O(n \cdot m) :

  1. L’algorithme de Knuth-Morris-Pratt (KMP), qui exploite les redondances au sein du motif PP pour éviter de reculer dans le texte TT lors d’une mismoyenne.
  2. L’algorithme de Boyer-Moore (version Boyer-Moore-Horspool ou règle du mauvais caractère), qui parcourt le motif de droite à gauche et permet de sauter des portions entières du texte.

❓ Questions progressives (incluant une analyse critique du modèle)

Section titled “❓ Questions progressives (incluant une analyse critique du modèle)”

Partie I : L’algorithme de Knuth-Morris-Pratt (KMP)

Section titled “Partie I : L’algorithme de Knuth-Morris-Pratt (KMP)”
  1. Définition de la table de préfixes (ou tableau π\pi) Soit le motif PP de longueur mm. On définit la fonction π\pi (souvent appelée tableau next ou pi) sur l’ensemble {0,…,m−1}\{0, \dots, m-1\} telle que π[i]\pi[i] est la longueur du plus grand préfixe propre de P[0…i]P[0 \dots i] qui est aussi un suffixe de P[0…i]P[0 \dots i].

    • Rédiger la spécification formelle de π[i]\pi[i].
    • Calculer à la main le tableau π\pi pour le motif P="ABABAX"P = \text{"ABABAX"}.
  2. Utilisation de la table π\pi pour la recherche Expliquer comment, lors d’une divergence entre T[i+j]T[i+j] et P[j]P[j], l’utilisation de π[j−1]\pi[j-1] permet de reprendre la comparaison sans réexaminer les caractères de TT déjà validés.

  3. Complexité temporelle de KMP

    • Écrire la fonction de calcul de la table π\pi en langage Python.
    • Démontrer par un argument d’amortissement (ou analyse du potentiel) que le calcul de la table π\pi s’effectue en temps O(m)O(m) et la recherche textuelle globale en temps O(n+m)O(n + m).

Partie II : L’algorithme de Boyer-Moore (Règle du mauvais caractère)

Section titled “Partie II : L’algorithme de Boyer-Moore (Règle du mauvais caractère)”
  1. Principe de la règle du mauvais caractère On considère un parcours du motif de droite à gauche (jj allant de m−1m-1 à 00). Supposons que T[i+j]≠P[j]T[i+j] \neq P[j] (mauvais caractère à l’indice jj du motif, correspondant au caractère c=T[i+j]c = T[i+j] dans le texte).

    • Si cc apparaît dans PP à des indices inférieurs à jj, de combien de positions peut-on décaler le motif PP vers la droite pour aligner la dernière occurrence de cc dans PP avec T[i+j]T[i+j] ?
    • Si cc n’apparaît pas du tout dans PP, quel est le décalage maximal possible ?
  2. Mise en œuvre et complexité

    • Définir une structure de données (pré-calcul) permettant d’obtenir le dernier indice d’apparition d’un caractère de Σ\Sigma dans PP en temps O(1)O(1).
    • Quel est le pire des cas théorique de l’algorithme de Boyer-Moore complet (comportant la règle du bon suffixe et du mauvais caractère) ? Donnez un exemple de texte et de motif illustrant ce pire cas.

💡 Pistes & Indications méthodologiques

Section titled “💡 Pistes & Indications méthodologiques”
  • Pour la table π\pi (KMP) : Pensez à l’algorithme de construction par récurrence. Si l’on connait π[i−1]\pi[i-1], on peut tester si P[i]==P[π[i−1]]P[i] == P[\pi[i-1]]. Attention aux rebonds successifs si l’égalité n’est pas vérifiée.
  • Amortissement KMP : Pour prouver la complexité linéaire, définissez un potentiel lié à l’indice de lecture du texte ii et à l’indice dans le motif jj. Montrez que chaque opération élémentaire incrémente strictement une grandeur bornée ou consomme du potentiel.
  • Boyer-Moore : Le pré-calcul de la table du mauvais caractère nécessite de parcourir le motif PP une seule fois. Attention à bien gérer les caractères de l’alphabet Σ\Sigma absents de PP (décalage de mm).

  1. Table des préfixes π\pi pour P="ABABAX"P = \text{"ABABAX"}

    • π[0]=0\pi[0] = 0 (pas de préfixe propre pour un seul caractère).
    • Pour P[0…1]="AB"P[0 \dots 1] = \text{"AB"} : aucun préfixe propre n’est suffixe   ⟹  π[1]=0\implies \pi[1] = 0.
    • Pour P[0…2]="ABA"P[0 \dots 2] = \text{"ABA"} : le préfixe "A"\text{"A"} est suffixe   ⟹  π[2]=1\implies \pi[2] = 1.
    • Pour P[0…3]="ABAB"P[0 \dots 3] = \text{"ABAB"} : le préfixe "AB"\text{"AB"} est suffixe   ⟹  π[3]=2\implies \pi[3] = 2.
    • Pour P[0…4]="ABABA"P[0 \dots 4] = \text{"ABABA"} : le préfixe "ABA"\text{"ABA"} est suffixe   ⟹  π[4]=3\implies \pi[4] = 3.
    • Pour P[0…5]="ABABAX"P[0 \dots 5] = \text{"ABABAX"} : aucun préfixe propre ne correspond à un suffixe se terminant par ’X’\text{'X'}   ⟹  π[5]=0\implies \pi[5] = 0.

    Résultat : π=[0,0,1,2,3,0]\pi = [0, 0, 1, 2, 3, 0].

  2. Utilisation lors d’une divergence Si une divergence survient à l’indice jj du motif (T[i+j]≠P[j]T[i+j] \neq P[j]), cela signifie que le sous-texte T[i…i+j−1T[i \dots i+j-1] correspond exactement à P[0…j−1]P[0 \dots j-1]. Le plus grand préfixe de PP qui est aussi un suffixe de P[0…j−1]P[0 \dots j-1] a une longueur de π[j−1]\pi[j-1]. Par conséquent, on peut directement aligner ce préfixe avec la fin du segment textuel déjà reconnu, en reprenant les comparaisons à l’indice i+j−π[j−1]i + j - \pi[j-1] dans le texte et à l’indice π[j−1]\pi[j-1] dans le motif, sans jamais reculer l’indice ii du texte.

  3. Implémentation et complexité de KMP

def calculer_pi(P):
m = len(P)
pi = [0] * m
j = 0
for i in range(1, m):
while j > 0 and P[i] != P[j]:
j = pi[j - 1]
if P[i] == P[j]:
j += 1
pi[i] = j
return pi
def kmp(T, P):
n, m = len(T), len(P)
if m == 0: return []
pi = calculer_pi(P)
resultats = []
j = 0
for i in range(n):
while j > 0 and T[i] != P[j]:
j = pi[j - 1]
if T[i] == P[j]:
j += 1
if j == m:
resultats.append(i - m + 1)
j = pi[j - 1] # Pour trouver les occurrences suivantes potentiellement chevauchantes
return resultats
  • Démonstration de la complexité linéaire : Analysons la boucle principale de recherche. Soit le couple (i,j)(i, j) représentant la position dans TT et dans PP. À chaque tour de boucle, soit ii est incrémenté de 11 (qu’il y ait correspondance ou non après les rebonds de jj), soit jj diminue strictement lors des instructions j = pi[j - 1]. Comme jj ne peut être incrémenté au total que nn fois (autant de fois que ii avance), et que chaque décrémentation de jj le rapproche de 00, le nombre total d’exécutions de la boucle while est borné linéairement par O(n)O(n). Le calcul préalable de π\pi s’analysant de la même manière en O(m)O(m), la complexité globale est O(n+m)\mathcal{O}(n + m).

Partie II : Boyer-Moore (Règle du mauvais caractère)

Section titled “Partie II : Boyer-Moore (Règle du mauvais caractère)”
  1. Principe de la règle du mauvais caractère Soit c=T[i+j]c = T[i+j] le caractère du texte provoquant une discordance avec P[j]P[j] lors d’un parcours de droite à gauche.

    • Si cc apparaît dans PP à des indices strictement inférieurs à jj, soit kk le plus grand indice tel que P[k]=cP[k] = c (k<jk < j). Le décalage vers la droite du motif est de j−kj - k.
    • Si cc n’apparaît pas dans PP, aucune occurrence de cc ne peut coïncider ; on décale le motif pour le positionner juste après l’indice i+ji+j, soit un décalage de j+1j + 1 (ou mm si j=m−1j = m-1).
  2. Structure de données et pire des cas

    • Table des derniers indices : On utilise un tableau (ou une table de hachage) de taille σ\sigma (la taille de l’alphabet), noté dernier[c], qui stocke pour chaque caractère c∈Σc \in \Sigma l’indice de sa dernière apparition dans PP (ou −1-1 s’il n’y figure pas). Sa construction s’effectue en O(m+σ)O(m + \sigma) par un simple parcours de PP.
    • Pire cas théorique : L’algorithme de Boyer-Moore (dans sa version complète) atteint un pire cas en O(n⋅m)\mathcal{O}(n \cdot m) comparaisons, par exemple lorsque T="AAAAAAAAAAAAAAAAAAAA"T = \text{"AAAAAAAAAAAAAAAAAAAA"} et P="BA"P = \text{"BA"}. À chaque tentative, le premier caractère testé (en partant de la droite) échoue, mais la règle du bon suffixe ou du mauvais caractère ne génère que des décalages minimes de 11 position, conduisant à mm comparaisons pour chacun des n−m+1n-m+1 décalages.