Aller au contenu

Un mot pour Fahmio

Tu écris. Tu continues.

Un problème, une idée — assez pour qu’on comprenne, puis tu retournes à ta copie.

On lit. Tu n’as rien d’autre à faire.

Où veux-tu aller ?

↑ ↓ pour parcourir · Entrée pour ouvrir · Échap pour fermer

Fahmio
Fahmio
Bac Tunisie
RV-BRécursivité

Récursivité d'ordre 1 sur une chaîne — décrire la récurrence, puis la programmer

Idée directrice

Ici la récursivité ne porte pas sur un nombre mais sur une chaîne. Le schéma d'écriture ne change pas — un cas d'arrêt, un seul appel récursif —, mais trois questions se posent autrement : quelle grandeur décroît (la longueur, pas la valeur), ce que retourne le cas d'arrêt (la chaîne vide, pas 1), et comment le résultat se recompose (par concaténation, pas par multiplication).

C'est le « problème récurrent d'ordre 1 » des devoirs : un seul appel, une seule grandeur qui diminue d'un cran. Reconnaître cette forme, c'est pouvoir écrire la fonction sans chercher de boucle.

Ce qui aide à reconnaître la démarche

« Est un problème récurrent d'ordre 1 » ; « Peut être traité d'une façon récursive » ; « Quel est l'ordre de récurrence de cette formule ? » ; « Proposer une fonction récursive … » (devoirs locaux de 4e SI, non des épreuves nationales)

Besoin d’un point de départ ? Voir la méthode
  • Écrire d'abord la relation en français : « ce que je retourne pour le cas le plus petit » puis « comment je me ramène à plus court ».
  • Nommer la grandeur qui décroît (ici la longueur) et de combien elle décroît : 1 pour Renv, 2 pour Pal par les extrémités.
  • Compter les cas d'arrêt : une grandeur qui perd 1 en demande un, une grandeur qui perd 2 en demande deux (parité).
  • Traduire ensuite seulement en pseudo-code, puis en Python où ch[1:] est exactement sous(ch, 1, long(ch) - 1).
  • Tracer sur un exemple court : descendre jusqu'au cas d'arrêt, puis remonter en recollant.
Les pièges à vérifier dans ta démarche

Pièges fréquents : recoller le caractère du mauvais côté (ch[0] + Renv(reste) redonne la chaîne de départ, pas son renversé) ; retourner ch au lieu de la chaîne vide dans le cas d'arrêt ; n'écrire qu'un seul cas d'arrêt pour une récurrence qui saute de 2 en 2, ce qui fait passer les longueurs impaires à côté de l'arrêt ; croire qu'une fonction qui appelle Pal(ch) sur la même chaîne finira quand même par s'arrêter — un cas d'arrêt ne sert à rien si l'argument ne décroît pas.

Sujet principal

Exercice — renverser une chaîne, puis reconnaître un palindrome

6 questionsCorrigé masqué

Une chaîne ch ne contient que des lettres minuscules. On veut écrire, sans aucune boucle, deux fonctions récursives : Renv(ch) qui retourne la chaîne renversée, et Pal(ch) qui retourne Vrai si la chaîne se lit de la même façon dans les deux sens (palindrome), Faux sinon.

Notations imposées : long(ch) est la longueur de la chaîne ; ch[i] est le caractère de rang i, les rangs allant de 0 à long(ch) - 1 ; sous(ch, i, k) retourne la portion de longueur k qui commence au rang i ; le signe + concatène deux chaînes ; la chaîne vide s'écrit "".

  1. Écrire la relation de récurrence de Renv : ce que retourne le cas d'arrêt pour la chaîne vide, et comment Renv(ch) se ramène à un seul appel récursif sur une chaîne plus courte. Donner l'ordre de cette récurrence et nommer la grandeur qui décroît à chaque appel.
  2. Écrire Renv en pseudo-code, puis son équivalent en Python.
  3. Donner la trace des appels de Renv("bac") jusqu'au cas d'arrêt, puis les valeurs retournées en remontant.
  4. Une élève teste le palindrome par Pal(ch) = (ch = Renv(ch)). Un élève propose au contraire de comparer le caractère de rang 0 et celui de rang long(ch) - 1, puis de recommencer sur ce qui reste entre les deux. Écrire la fonction récursive Pal de cette seconde méthode, et dire laquelle des deux méthodes donne sa réponse après le moins d'appels sur la chaîne "abcdefgh".
  5. Cas limites : que doivent retourner Pal("") et Pal("r") ? Expliquer pourquoi la seconde méthode réclame deux cas d'arrêt alors que Renv n'en demande qu'un.
  6. La récurrence de la factorielle porte sur un entier. Dire ce qui joue, pour Renv, le rôle de n - 1, et ce qui joue le rôle du cas n = 0.
Voir la correction commentéeAprès avoir posé votre démarche

Idée : sur une chaîne, la récursivité d'ordre 1 se lit toujours de la même façon — on détache un caractère, on relance la fonction sur ce qui reste, et on recolle. Tout le travail consiste à dire quel caractère on détache et de quel côté on le recolle.

1. Relation de récurrence de Renv. Cas d'arrêt : Renv("") = "" — renverser une chaîne vide la laisse vide. Cas général, pour long(ch) ≥ 1 : on détache le caractère de rang 0 et on le recolle à la fin du renversé de ce qui reste :

Renv(ch) = Renv(sous(ch, 1, long(ch) - 1)) + ch[0]

Un seul appel récursif figure dans cette égalité : la récurrence est d'ordre 1. La grandeur qui décroît est la longueur de la chaîne, qui perd exactement 1 à chaque appel ; partant d'une longueur finie, elle atteint 0 en long(ch) appels, donc l'arrêt est garanti.

2. Écriture. TDOL : ch de type chaîne ; aucun objet local.

DEF FN Renv(ch : chaîne) : chaîne
Début
  Si ch = "" alors
    Retourner ""
  Sinon
    Retourner Renv(sous(ch, 1, long(ch) - 1)) + ch[0]
  Fin Si
Fin Renv
def renv(ch):
    if ch == "":
        return ""
    return renv(ch[1:]) + ch[0]

print(renv("bac"))   # cab

3. Trace de Renv("bac"). Descente :

  • Renv("bac") → attend Renv("ac") + "b"
  • Renv("ac") → attend Renv("c") + "a"
  • Renv("c") → attend Renv("") + "c"
  • Renv("") → cas d'arrêt, retourne ""

Remontée : Renv("c") = "" + "c" = "c", puis Renv("ac") = "c" + "a" = "ca", puis Renv("bac") = "ca" + "b" = "cab". Quatre appels en tout, cas d'arrêt compris.

4. Palindrome par les extrémités. On compare les deux bouts, puis on recommence sur l'intérieur ; la longueur perd 2 à chaque appel.

DEF FN Pal(ch : chaîne) : booléen
Début
  Si long(ch) <= 1 alors
    Retourner Vrai
  Sinon Si ch[0] <> ch[long(ch) - 1] alors
    Retourner Faux
  Sinon
    Retourner Pal(sous(ch, 1, long(ch) - 2))
  Fin Si
Fin Pal

Sur "abcdefgh" : la méthode des extrémités compare a et h, qui diffèrent, et retourne Faux dès le premier appel. La méthode ch = Renv(ch) doit d'abord construire le renversé en entier, soit 9 appels de Renv (8 descentes plus le cas d'arrêt), avant la moindre comparaison. La seconde méthode répond donc au plus tôt : elle peut échouer sur le premier caractère, alors que la première paie toujours le prix complet.

5. Cas limites. Pal("") retourne Vrai (rien à comparer) et Pal("r") retourne Vrai (un caractère unique est son propre miroir). Il faut deux cas d'arrêt parce que la longueur diminue de 2 : partant d'une longueur paire elle atteint 0, partant d'une longueur impaire elle atteint 1. Un seul test long(ch) = 0 laisserait les chaînes de longueur impaire appeler sous(ch, 1, -1), c'est-à-dire une longueur négative. Renv, dont la longueur diminue de 1, passe forcément par 0 : un seul cas d'arrêt lui suffit.

6. Transposition depuis la factorielle. Le rôle de n - 1 est tenu par sous(ch, 1, long(ch) - 1), la chaîne privée de son premier caractère : c'est l'argument réduit d'un cran. Le rôle du cas n = 0 est tenu par la chaîne vide, valeur à partir de laquelle plus rien ne se réduit. La multiplication n × Fact(n-1) devient la concaténation ; l'élément neutre 1 devient la chaîne vide.

Changer d’énoncé, garder le raisonnement

Choisis un seul transfert après le sujet principal. Qu’est-ce qui change ? Quel résultat ou quelle méthode peux-tu réutiliser ? Les autres exercices restent disponibles pour une prochaine séance.

Exercices d'entraînement — une nuance à la fois

1 Transfert — la même récurrence sur un tableau : où poser le cas d'arrêt
Entraînement 01 / 03

Transfert — la même récurrence sur un tableau : où poser le cas d'arrêt

4 questionsCorrigé masqué

Un tableau T contient n entiers rangés aux rangs 0 à n - 1. On veut leur somme sans boucle, par une fonction récursive Som(T, k) qui retourne la somme des k premières cases ; l'appel initial est Som(T, n). Exemple de travail : T = [4, -2, 7, 1] avec n = 4.

  1. Écrire la relation de récurrence de Som : cas d'arrêt pour k = 0, puis cas général. Nommer la grandeur qui décroît et donner l'ordre de la récurrence.
  2. Écrire Som en pseudo-code (objets déclarés) puis en Python.
  3. Donner la trace de Som(T, 4) sur l'exemple, avec les valeurs retournées en remontant.
  4. Un camarade pose le cas d'arrêt à k = 1 et retourne T[0]. Sa fonction donne-t-elle le bon résultat pour n = 4 ? Et que se passe-t-il si n = 0 ? Conclure sur le rôle du cas d'arrêt.
Voir la correction commentéeAprès avoir posé votre démarche

Idée : sur un tableau, ce n'est pas le tableau qui rétrécit — il reste le même en mémoire. C'est le nombre de cases encore à additionner qui décroît : la récurrence porte sur k, pas sur T.

1. Relation. Cas d'arrêt : Som(T, 0) = 0, la somme d'aucune case. Cas général, pour k ≥ 1 : Som(T, k) = Som(T, k - 1) + T[k - 1]. Un seul appel récursif : récurrence d'ordre 1 ; la grandeur qui décroît est k, qui perd 1 à chaque appel et atteint 0 en k appels.

2. Écriture. TDOL : T tableau d'entiers, k entier avec 0 ≤ k ≤ n.

DEF FN Som(T : tab ; k : entier) : entier
Début
  Si k = 0 alors
    Retourner 0
  Sinon
    Retourner Som(T, k - 1) + T[k - 1]
  Fin Si
Fin Som
def som(T, k):
    if k == 0:
        return 0
    return som(T, k - 1) + T[k - 1]

print(som([4, -2, 7, 1], 4))   # 10

3. Trace de Som(T, 4). Descente : Som(T,4) attend Som(T,3) + 1 ; Som(T,3) attend Som(T,2) + 7 ; Som(T,2) attend Som(T,1) + (-2) ; Som(T,1) attend Som(T,0) + 4 ; Som(T,0) = 0. Remontée : Som(T,1) = 4, Som(T,2) = 2, Som(T,3) = 9, Som(T,4) = 10, ce que confirme 4 - 2 + 7 + 1 = 10.

4. Cas d'arrêt déplacé. Pour n = 4 la version du camarade donne le même 10 : elle s'arrête une marche plus haut, sur Som(T,1) = T[0] = 4, et la remontée est identique. Le défaut n'apparaît que sur le bord : avec n = 0, k vaut 0, le test k = 1 échoue, la fonction s'appelle avec k = -1, puis -2… Le test d'arrêt n'est jamais rencontré : les appels s'empilent jusqu'au débordement de pile, et T[k - 1] désigne au passage une case qui n'existe pas. Un cas d'arrêt doit être placé sur la valeur que la suite décroissante atteint réellement, pas sur la première valeur utile.

2 Transfert — construire une chaîne par récursivité : quelle grandeur décroît
Entraînement 02 / 03

Transfert — construire une chaîne par récursivité : quelle grandeur décroît

4 questionsCorrigé masqué

On veut une fonction récursive Rep(ch, n) qui retourne la chaîne obtenue en écrivant n fois de suite la chaîne ch, avec n entier positif ou nul. Exemples attendus : Rep("ab", 3) retourne "ababab", et Rep("ab", 0) retourne la chaîne vide "". Aucune boucle n'est autorisée ; + concatène.

  1. Deux grandeurs sont en jeu : la longueur de ch et l'entier n. Dire laquelle doit décroître pour que la fonction s'arrête, puis écrire la relation de récurrence.
  2. Écrire Rep en pseudo-code puis en Python.
  3. Un élève écrit le cas d'arrêt « si n = 1 alors retourner ch ». Sa fonction est-elle juste pour n = 3 ? Que fait-elle pour n = 0 ? Dire pourquoi le cas d'arrêt n = 0 → chaîne vide est celui qui convient.
  4. Combien d'appels de Rep, cas d'arrêt compris, l'appel Rep("ab", 3) déclenche-t-il ? Combien de caractères la chaîne retournée contient-elle ?
Voir la correction commentéeAprès avoir posé votre démarche

Idée : la chaîne ch est ici une donnée qu'on recopie, pas la grandeur qu'on réduit. Ce qui doit décroître, c'est le nombre de copies restant à écrire.

1. Relation. Cas d'arrêt : Rep(ch, 0) = "". Cas général, pour n ≥ 1 : Rep(ch, n) = ch + Rep(ch, n - 1). La grandeur qui décroît est n, de 1 en 1 ; ch ne change jamais. Ordre 1.

2. Écriture. TDOL : ch chaîne, n entier avec n ≥ 0.

DEF FN Rep(ch : chaîne ; n : entier) : chaîne
Début
  Si n = 0 alors
    Retourner ""
  Sinon
    Retourner ch + Rep(ch, n - 1)
  Fin Si
Fin Rep
def rep(ch, n):
    if n == 0:
        return ""
    return ch + rep(ch, n - 1)

print(rep("ab", 3))   # ababab
print(rep("ab", 0))   # (chaîne vide)

3. Cas d'arrêt à n = 1. Pour n = 3 la réponse est correcte : ch + (ch + ch) donne bien "ababab". Mais pour n = 0 le test n = 1 n'est pas vérifié, l'appel se poursuit avec n = -1, -2, … et ne rencontre jamais l'arrêt : débordement de pile. Le cas n = 0 est le bon parce qu'il est la valeur que la décroissance atteint depuis n'importe quel n ≥ 0, et parce que la chaîne vide est l'élément neutre de la concaténation, exactement comme 1 l'est de la multiplication dans la factorielle.

4. Comptage. Les appels sont Rep("ab",3), Rep("ab",2), Rep("ab",1), Rep("ab",0) : 4 appels, soit n + 1. La chaîne retournée contient 3 × 2 = 6 caractères.

3 Transfert — le cas d'arrêt existe mais l'argument ne décroît pas : preuve d'arrêt
Entraînement 03 / 03

Transfert — le cas d'arrêt existe mais l'argument ne décroît pas : preuve d'arrêt

4 questionsCorrigé masqué

On propose deux fonctions récursives travaillant sur une chaîne ch de lettres minuscules, avec les mêmes notations que d'habitude (long, ch[i], sous(ch, i, k)).

DEF FN F(ch : chaîne) : entier
Début
  Si ch = "" alors Retourner 0
  Sinon Retourner 1 + F(sous(ch, 1, long(ch) - 1))
  Fin Si
Fin F
DEF FN G(ch : chaîne) : entier
Début
  Si ch = "" alors Retourner 0
  Sinon Si ch[0] = "a" alors Retourner 1 + G(sous(ch, 1, long(ch) - 1))
  Sinon Retourner G(ch)
  Fin Si
Fin G
  1. Pour chacune des deux, dire si tout appel finit par rencontrer le cas d'arrêt. Justifier en nommant la grandeur qui décroît — ou en montrant qu'aucune ne décroît.
  2. Donner ce que retourne F("bac") et ce que fait G("bac"), puis ce que retourne chacune sur la chaîne vide.
  3. Réparer G pour qu'elle compte les lettres a de la chaîne, en modifiant une seule ligne. Vérifier la réparation sur "banana".
  4. Les deux fonctions possèdent pourtant un cas d'arrêt correct. Dire, en une phrase, quelle condition supplémentaire une fonction récursive doit remplir pour s'arrêter.
Voir la correction commentéeAprès avoir posé votre démarche

Idée : un cas d'arrêt n'arrête rien tout seul. Il n'arrête la récursivité que si chaque appel rapproche l'argument de ce cas — c'est-à-dire s'il existe une grandeur entière positive qui diminue strictement d'un appel au suivant.

1. F s'arrête, G non. Dans F, le seul appel récursif se fait sur une chaîne de longueur long(ch) - 1 : la longueur est un entier positif qui perd 1 à chaque appel, elle atteint donc 0 en long(ch) appels, où le cas d'arrêt est rencontré. Dans G, la branche ch[0] = "a" réduit bien la longueur de 1, mais la branche Sinon relance G(ch) sur la même chaîne : rien n'a changé, le même test échoue de nouveau, et la fonction se rappelle indéfiniment. Le cas d'arrêt ch = "" est pourtant écrit et correct : il est simplement inatteignable dès que la chaîne commence par une lettre autre que a.

2. Exécutions. F("bac") = 1 + F("ac") = 1 + 1 + F("c") = 1 + 1 + 1 + F("") = 3 : F compte les caractères. G("bac") teste ch[0] = "b", entre dans la branche Sinon et rappelle G("bac") : boucle infinie, qui se termine en pratique par un débordement de pile. Sur la chaîne vide, les deux retournent 0 par leur cas d'arrêt.

3. Réparation. Une seule ligne change — la branche Sinon doit, elle aussi, raccourcir la chaîne, sans compter la lettre :

  Sinon Retourner G(sous(ch, 1, long(ch) - 1))

Sur "banana" : b non comptée → "anana" ; a comptée (1) → "nana" ; n non comptée → "ana" ; a comptée (2) → "na" ; n non comptée → "a" ; a comptée (3) → "" → 0. Le total retourné est 3, ce qui est bien le nombre de a de "banana", et les 7 appels successifs voient la longueur passer de 6 à 0.

4. Condition d'arrêt. Il ne suffit pas qu'un cas d'arrêt existe : il faut qu'une grandeur entière positive attachée à l'argument décroisse strictement à chaque appel, dans toutes les branches récursives, de sorte que le cas d'arrêt soit atteint après un nombre fini d'appels.

Relier ce raisonnement au bac

Choisis une filière pour voir les sujets qui étayent ce scénario.