Leçon 12 sur 20

Chapitre 12 — Récursivité

Résoudre un problème par des appels contrôlés à soi-même

 

Fiche pédagogique du chapitre

Objectifs d’apprentissage

À la fin de ce chapitre, l’étudiant devra être capable de :

  • définir la récursivité et reconnaître une fonction qui s’appelle elle-même ;
  • identifier le cas de base, le cas récursif et la mesure de progression ;
  • expliquer la descente des appels et la remontée des résultats ;
  • représenter l’empilement et le dépilement des appels dans une pile d’exécution ;
  • tracer manuellement l’exécution d’un algorithme récursif ;
  • transformer, lorsque cela est pertinent, une solution récursive en solution itérative ;
  • comparer les avantages, les limites et les coûts des deux approches ;
  • prévenir la récursion infinie et les dépassements de pile ;
  • concevoir les fonctions récursives de factorielle, somme, puissance et PGCD ;
  • analyser la récursivité naïve de la suite de Fibonacci ;
  • effectuer une recherche récursive dans un tableau ;
  • tester un algorithme récursif avec des cas normaux, limites et incorrects.

Prérequis

  • maîtriser les variables, les expressions et les structures conditionnelles ;
  • savoir utiliser les boucles Pour et TantQue ;
  • connaître les fonctions, les paramètres et les valeurs de retour ;
  • savoir manipuler un tableau à une dimension ;
  • être capable de construire une table de trace.

Plan du chapitre

12.1 Principe de la récursivité

12.2 Récursivité et itération

12.3 Exemples classiques

Applications, traçage, travaux dirigés, corrigés et synthèse

Organisation pédagogique indicative

Activité

Durée indicative

Objectif principal

Cours3 hComprendre les appels récursifs, le cas de base et la pile d’exécution.
Travaux dirigés3 hTracer les appels et construire des récurrences correctes.
Travaux pratiques3 hImplémenter et tester plusieurs fonctions récursives.
Travail personnel2 hComparer les versions récursives et itératives.
Idée centrale : une fonction récursive résout un problème en le réduisant à une instance plus petite du même problème. La réduction doit obligatoirement conduire à un cas de base qui arrête les appels.

Introduction à la récursivité

La récursivité est une technique de résolution dans laquelle un sous-programme utilise sa propre définition. Au lieu de répéter explicitement des instructions avec une boucle, la fonction délègue une partie du travail à un nouvel appel portant sur une donnée plus petite ou plus simple.

Cette technique apparaît naturellement dans les problèmes définis par récurrence, dans les structures hiérarchiques et dans les stratégies de division d’un problème. Elle est particulièrement utile lorsque la solution d’un cas dépend directement de la solution d’un cas plus petit.

Cependant, une définition récursive correcte ne consiste pas seulement à écrire un auto-appel. Elle doit préciser une situation terminale, garantir une progression vers cette situation et combiner correctement le résultat obtenu lors de la remontée des appels.

Principe de sécurité : avant d’écrire un appel récursif, identifier le cas de base et prouver que chaque appel se rapproche de ce cas.

12.1 Principe de la récursivité

12.1.1 Fonction qui s’appelle elle-même

Une fonction est dite récursive lorsqu’elle s’appelle directement ou indirectement pendant son exécution. Dans la récursivité directe, la fonction contient un appel à son propre nom. Dans la récursivité indirecte, une fonction A appelle une fonction B qui finit par rappeler A.

Dans ce chapitre, les exemples utilisent principalement la récursivité directe, plus simple à observer et à tracer.

Structure générale d’une fonction récursive

Fonction Traiter(probleme) : TypeResultat
Début
    Si CasDeBase(probleme) Alors
        Retourner ResultatSimple
    Sinon
        sousProbleme ← Reduire(probleme)
        resultatPartiel ← Traiter(sousProbleme)
        Retourner Combiner(probleme, resultatPartiel)
    FinSi
Fin

 

Élément

Rôle

Question à se poser

ParamètreDécrit l’instance courante du problème.Quelle donnée diminue ou se simplifie ?
Cas de baseFournit un résultat sans nouvel appel.Quand puis-je répondre directement ?
Appel récursifRésout une instance plus petite.Le nouvel argument se rapproche-t-il du cas de base ?
CombinaisonConstruit le résultat courant à partir du résultat partiel.Que faire du résultat retourné ?

12.1.2 Exemple introductif : compte à rebours

L’exemple suivant affiche les entiers de N à 1. Le cas N = 0 arrête la récursion. Pour N > 0, la fonction affiche N puis s’appelle avec N - 1.

Pseudo-code

Procédure CompteARebours(n : Entier)
Début
    Si n = 0 Alors
        Afficher("Terminé")
    Sinon
        Afficher(n)
        CompteARebours(n - 1)
    FinSi
Fin

 

Appel courant

Action avant l’appel

Appel suivant

CompteARebours(3)Afficher 3CompteARebours(2)
CompteARebours(2)Afficher 2CompteARebours(1)
CompteARebours(1)Afficher 1CompteARebours(0)
CompteARebours(0)Afficher « Terminé »Aucun appel
Observation : les instructions placées avant l’appel récursif s’exécutent pendant la descente. Celles placées après l’appel s’exécutent pendant la remontée.

12.1.3 Cas de base

Le cas de base est une situation assez simple pour être résolue immédiatement. Il empêche la fonction de s’appeler indéfiniment. Une fonction peut posséder un seul cas de base ou plusieurs cas de base.

  • Factorielle : 0! = 1 constitue le cas de base.
  • Fibonacci : F(0) = 0 et F(1) = 1 constituent deux cas de base.
  • Recherche dans un tableau : atteindre la fin du tableau constitue un cas d’échec.
  • Recherche dichotomique : l’intervalle vide constitue un cas d’échec.
Attention : un cas de base présent dans le code ne suffit pas. Il doit être effectivement atteignable par tous les appels valides.

12.1.4 Cas récursif

Le cas récursif exprime la solution du problème courant à partir d’un ou plusieurs problèmes plus petits. Il comporte généralement une transformation des paramètres, un appel récursif et une combinaison du résultat.

Pour calculer la somme des entiers de 1 à N, on peut utiliser la relation Somme(N) = N + Somme(N - 1). La valeur de N diminue à chaque appel, jusqu’à N = 0.

Exemple minimal

Fonction Somme(n : Entier) : Entier
Début
    Si n = 0 Alors
        Retourner 0
    Sinon
        Retourner n + Somme(n - 1)
    FinSi
Fin

 

12.1.5 Mesure de progression et terminaison

Pour justifier qu’un algorithme récursif se termine, on choisit une mesure entière et non négative qui diminue strictement à chaque appel. Cette mesure peut être la valeur de N, la taille d’un intervalle, le nombre d’éléments restant à traiter ou la hauteur d’une structure.

Problème

Mesure de progression

Évolution

Factorielle de nnn devient n - 1
PGCD(a, b)valeur du second argumentb devient a modulo b, inférieur à b
Recherche séquentiellenombre d’éléments non examinésl’indice augmente
Recherche dichotomiquetaille de l’intervallel’intervalle est approximativement divisé par deux

Une preuve élémentaire de terminaison suit donc deux idées : la mesure ne peut pas décroître indéfiniment et le cas de base est atteint lorsque cette mesure prend sa valeur minimale.

12.1.6 Empilement des appels

Chaque appel de fonction possède son propre contexte d’exécution : paramètres, variables locales, adresse de retour et état des calculs en attente. Ces contextes sont mémorisés dans une pile d’appels. Le dernier appel créé est le premier à se terminer, selon le principe LIFO : dernier entré, premier sorti.

Étape

Sommet de la pile

Calcul en attente

1Factorielle(4)4 × Factorielle(3)
2Factorielle(3)3 × Factorielle(2)
3Factorielle(2)2 × Factorielle(1)
4Factorielle(1)1 × Factorielle(0)
5Factorielle(0)Retourner 1
6Dépilement1 × 1 = 1, puis 2 × 1 = 2, puis 3 × 2 = 6, puis 4 × 6 = 24

La phase de descente crée les appels. La phase de remontée restitue les résultats dans l’ordre inverse. Une variable locale appartenant à un appel n’est pas confondue avec la variable locale portant le même nom dans un autre appel.

12.1.7 Arbre des appels

Lorsqu’un appel récursif produit plusieurs nouveaux appels, la trace prend la forme d’un arbre. La suite de Fibonacci en fournit un exemple : Fibonacci(5) appelle Fibonacci(4) et Fibonacci(3), chacun générant à son tour d’autres appels.

Arbre partiel de Fibonacci(5)

F(5)
├── F(4)
│   ├── F(3)
│   │    ├── F(2)
│   │    └── F(1)
│   └── F(2)
└── F(3)
    ├── F(2)
    └── F(1)

 

Cet arbre met en évidence des calculs répétés. Par exemple, F(3) et F(2) sont recalculés plusieurs fois. Une formulation récursive élégante peut donc être inefficace si elle duplique beaucoup de travail.

12.1.8 Contrat d’une fonction récursive

Un contrat précise les conditions d’utilisation et le résultat garanti. Il est particulièrement important en récursivité, car un paramètre incorrect peut empêcher d’atteindre le cas de base.

Élément du contrat

Exemple pour Factorielle(n)

Préconditionn est un entier supérieur ou égal à 0.
Postconditionla fonction retourne le produit des entiers de 1 à n.
Cas de basesi n = 0, retourner 1.
Progressionl’appel utilise n - 1.
Invariant attenduchaque appel reçoit un entier non négatif.

12.2 Récursivité et itération

12.2.1 Solution récursive

Une solution récursive exprime le problème à l’aide d’une relation entre une instance et une instance plus petite. Elle met l’accent sur la définition du problème plutôt que sur la gestion explicite de la répétition.

Factorielle récursive

Fonction FactorielleRec(n : Entier) : Entier
Début
    Si n = 0 Alors
        Retourner 1
    Sinon
        Retourner n × FactorielleRec(n - 1)
    FinSi
Fin

 

12.2.2 Solution itérative

Une solution itérative utilise une boucle et des variables d’état. Elle contrôle explicitement la progression et ne crée pas une nouvelle pile d’appels pour chaque répétition.

Factorielle itérative

Fonction FactorielleIter(n : Entier) : Entier
Variables
    i, produit : Entier
Début
    produit ← 1
    Pour i allant de 2 à n Faire
        produit ← produit × i
    FinPour
    Retourner produit
Fin

 

12.2.3 Comparaison des deux approches

Critère

Récursivité

Itération

Expression du problèmeSouvent proche de la définition mathématique.Souvent centrée sur l’évolution des variables.
MémoireUtilise un contexte par appel.Utilise généralement un nombre fixe de variables.
LisibilitéTrès claire pour les problèmes naturellement récursifs.Très claire pour les répétitions simples.
Risque principalRécursion infinie ou dépassement de pile.Boucle infinie ou mauvaise mise à jour.
Coût des appelsUn coût supplémentaire peut exister à chaque appel.Pas de coût d’appel récursif.
TransformationPeut parfois être remplacée par une boucle ou une pile explicite.Peut parfois être reformulée par récurrence.

12.2.4 Avantages de la récursivité

  • elle traduit directement certaines définitions mathématiques ;
  • elle simplifie le traitement des problèmes divisés en sous-problèmes de même nature ;
  • elle rend naturelle l’exploration d’arbres, de dossiers ou de structures imbriquées ;
  • elle réduit parfois le nombre de variables de contrôle visibles ;
  • elle facilite certains raisonnements par induction.

12.2.5 Limites de la récursivité

  • chaque appel consomme de la mémoire dans la pile d’exécution ;
  • une profondeur trop grande peut provoquer un dépassement de pile ;
  • les appels répétés peuvent rendre une solution lente ;
  • le traçage peut devenir difficile lorsque plusieurs branches sont créées ;
  • une mauvaise progression peut produire une récursion infinie ;
  • certains langages limitent explicitement la profondeur des appels.
Choix raisonné : la récursivité n’est pas automatiquement meilleure qu’une boucle. Elle doit apporter une formulation plus naturelle ou une simplification réelle sans coût excessif.

12.2.6 Risque de récursion infinie

Une récursion infinie apparaît lorsque le cas de base n’est jamais atteint. Cela peut provenir d’un cas de base absent, d’une condition erronée ou d’un argument qui ne progresse pas.

Exemple incorrect

Fonction SommeIncorrecte(n : Entier) : Entier
Début
    Si n = 0 Alors
        Retourner 0
    Sinon
        Retourner n + SommeIncorrecte(n + 1)   // n s’éloigne de 0
    FinSi
Fin

 

Erreur

Conséquence

Correction

Cas de base impossibleLes appels ne s’arrêtent pas.Adapter le cas de base au domaine réel.
Argument inchangéLe même appel se répète.Modifier strictement la mesure de progression.
Progression dans le mauvais sensLa distance au cas de base augmente.Vérifier le signe de la mise à jour.
Donnée hors préconditionLe chemin prévu n’est pas suivi.Valider les paramètres avant l’appel.

12.2.7 Récursivité terminale

Une récursion est dite terminale lorsque l’appel récursif constitue la dernière opération exécutée par la fonction. Aucun calcul n’attend le retour de l’appel. Certains compilateurs ou interpréteurs peuvent optimiser ce cas, mais cette optimisation ne doit pas être supposée sans connaître le langage utilisé.

Factorielle avec accumulateur

Fonction FactAux(n, accumulateur : Entier) : Entier
Début
    Si n = 0 Alors
        Retourner accumulateur
    Sinon
        Retourner FactAux(n - 1, n × accumulateur)
    FinSi
Fin

Fonction Factorielle(n : Entier) : Entier
Début
    Retourner FactAux(n, 1)
Fin

 

Dans un cours d’algorithmique, cette forme sert surtout à comprendre le rôle d’un accumulateur et la transformation possible vers une boucle.

12.2.8 Mémoïsation et calculs répétés

La mémoïsation consiste à mémoriser les résultats déjà calculés afin de les réutiliser. Elle est utile lorsqu’un arbre récursif contient de nombreux sous-problèmes identiques, comme dans la version naïve de Fibonacci.

Cette technique échange de la mémoire contre du temps de calcul. Elle sera étudiée plus en profondeur dans les chapitres consacrés à l’optimisation ou à la programmation dynamique.

12.2.9 Comment choisir ?

Situation

Choix généralement adapté

Répéter un traitement un nombre connu de foisBoucle Pour
Accumuler une somme ou un produit simpleBoucle, sauf objectif pédagogique
Définition mathématique par récurrenceRécursivité possible
Diviser un intervalle en sous-intervallesRécursivité souvent naturelle
Explorer une structure hiérarchiqueRécursivité souvent naturelle
Profondeur potentiellement très grandeItération ou pile explicite à envisager
Sous-problèmes répétésMémoïsation ou version itérative

12.3 Exemples classiques

12.3.1 Factorielle

Pour un entier n supérieur ou égal à 0, la factorielle n! est le produit des entiers de 1 à n. Par convention, 0! = 1.

Définition

Valeur

Cas de baseFactorielle(0) = 1
Cas récursifFactorielle(n) = n × Factorielle(n - 1), pour n > 0

Algorithme

Fonction Factorielle(n : Entier) : Entier
Début
    Si n < 0 Alors
        Erreur("La factorielle exige n ≥ 0")
    FinSi
    Si n = 0 Alors
        Retourner 1
    Sinon
        Retourner n × Factorielle(n - 1)
    FinSi
Fin

 

Expression

Réduction

Factorielle(4)4 × Factorielle(3)
Factorielle(3)3 × Factorielle(2)
Factorielle(2)2 × Factorielle(1)
Factorielle(1)1 × Factorielle(0)
Factorielle(0)1
Remontée1, puis 2, puis 6, puis 24

Complexité : la fonction réalise n appels récursifs, donc son temps est O(n) et la profondeur de pile est O(n). Les valeurs augmentent très rapidement ; le type entier peut déborder pour des valeurs relativement petites.

12.3.2 Somme des nombres de 1 à N

La somme S(n) = 1 + 2 + ... + n peut être définie par S(0) = 0 et S(n) = n + S(n - 1).

Algorithme récursif

Fonction SommeEntiers(n : Entier) : Entier
Début
    Si n < 0 Alors
        Erreur("n doit être positif ou nul")
    FinSi
    Si n = 0 Alors
        Retourner 0
    Sinon
        Retourner n + SommeEntiers(n - 1)
    FinSi
Fin

 

Appel

Valeur retournée

SommeEntiers(0)0
SommeEntiers(1)1 + 0 = 1
SommeEntiers(2)2 + 1 = 3
SommeEntiers(3)3 + 3 = 6
SommeEntiers(4)4 + 6 = 10

Cette solution est utile pour comprendre la récursivité, mais une formule directe n(n + 1) / 2 ou une boucle utilise moins de pile.

12.3.3 Suite de Fibonacci

La suite de Fibonacci est définie par F(0) = 0, F(1) = 1 et F(n) = F(n - 1) + F(n - 2) pour n ≥ 2.

Version récursive naïve

Fonction Fibonacci(n : Entier) : Entier
Début
    Si n < 0 Alors
        Erreur("n doit être positif ou nul")
    FinSi
    Si n = 0 Alors
        Retourner 0
    SinonSi n = 1 Alors
        Retourner 1
    Sinon
        Retourner Fibonacci(n - 1) + Fibonacci(n - 2)
    FinSi
Fin

 

n

F(n)

Remarque

00Cas de base
11Cas de base
21F(1) + F(0)
32F(2) + F(1)
43F(3) + F(2)
55F(4) + F(3)

La version naïve est simple mais recalcule les mêmes valeurs. Son nombre d’appels croît très rapidement et son temps est exponentiel. La profondeur de pile reste linéaire, mais le nombre total d’appels est beaucoup plus élevé.

Version itérative efficace

Fonction FibonacciIter(n : Entier) : Entier
Variables
    a, b, suivant, i : Entier
Début
    a ← 0
    b ← 1
    Pour i allant de 1 à n Faire
        suivant ← a + b
        a ← b
        b ← suivant
    FinPour
    Retourner a
Fin

 

Leçon algorithmique : une définition récursive naturelle ne garantit pas une exécution efficace. Il faut repérer les sous-calculs répétés et choisir une stratégie adaptée.

12.3.4 Calcul d’une puissance

Pour un réel x et un entier naturel n, la puissance x^n peut être définie par x^0 = 1 et x^n = x × x^(n - 1).

Version récursive simple

Fonction Puissance(x : Réel, n : Entier) : Réel
Début
    Si n < 0 Alors
        Erreur("Cette version exige n ≥ 0")
    FinSi
    Si n = 0 Alors
        Retourner 1
    Sinon
        Retourner x × Puissance(x, n - 1)
    FinSi
Fin

 

Cette version effectue n multiplications. Une version plus efficace utilise la parité de n : si n est pair, x^n = (x^(n/2))² ; si n est impair, x^n = x × x^(n - 1).

Exponentiation rapide

Fonction PuissanceRapide(x : Réel, n : Entier) : Réel
Variables
    demi : Réel
Début
    Si n = 0 Alors
        Retourner 1
    FinSi
    demi ← PuissanceRapide(x, n DIV 2)
    Si n MOD 2 = 0 Alors
        Retourner demi × demi
    Sinon
        Retourner x × demi × demi
    FinSi
Fin

 

Algorithme

Nombre approximatif d’appels

Complexité

Puissance simplenO(n)
Puissance rapidelog2(n)O(log n)

12.3.5 PGCD d’Euclide

Le plus grand commun diviseur de deux entiers peut être calculé par l’algorithme d’Euclide. La relation essentielle est PGCD(a, b) = PGCD(b, a MOD b), avec le cas de base PGCD(a, 0) = |a|.

Algorithme récursif

Fonction PGCD(a, b : Entier) : Entier
Début
    a ← ValeurAbsolue(a)
    b ← ValeurAbsolue(b)
    Si b = 0 Alors
        Retourner a
    Sinon
        Retourner PGCD(b, a MOD b)
    FinSi
Fin

 

Appel

Calcul du reste

Appel suivant

PGCD(48, 18)48 MOD 18 = 12PGCD(18, 12)
PGCD(18, 12)18 MOD 12 = 6PGCD(12, 6)
PGCD(12, 6)12 MOD 6 = 0PGCD(6, 0)
PGCD(6, 0)Cas de baseRetourner 6

Le second argument diminue strictement tant qu’il est non nul. La profondeur est logarithmique dans les valeurs des entrées, ce qui rend cet algorithme très efficace.

12.3.6 Recherche récursive dans un tableau

Une recherche séquentielle récursive examine l’élément d’indice i. Si la valeur est trouvée, elle retourne i. Si i atteint la taille du tableau, elle retourne -1. Sinon, elle continue avec i + 1.

Recherche séquentielle récursive

Fonction RechercherRec(T : Tableau, n, valeur, i : Entier) : Entier
Début
    Si i = n Alors
        Retourner -1
    SinonSi T[i] = valeur Alors
        Retourner i
    Sinon
        Retourner RechercherRec(T, n, valeur, i + 1)
    FinSi
Fin

// Appel initial : RechercherRec(T, n, valeur, 0)

 

Tableau

Valeur

Indices examinés

Résultat

[7, 4, 9, 2]90, 1, 22
[7, 4, 9, 2]700
[7, 4, 9, 2]50, 1, 2, 3, fin-1
[]3fin immédiate-1

Le temps est O(n) dans le pire cas et la profondeur de pile est également O(n). Pour un tableau trié, une recherche dichotomique récursive peut réduire le temps à O(log n).

Extension : recherche dichotomique récursive

Fonction RechercheDicho(T, valeur, gauche, droite : Entier) : Entier
Variables
    milieu : Entier
Début
    Si gauche > droite Alors
        Retourner -1
    FinSi
    milieu ← (gauche + droite) DIV 2
    Si T[milieu] = valeur Alors
        Retourner milieu
    SinonSi valeur < T[milieu] Alors
        Retourner RechercheDicho(T, valeur, gauche, milieu - 1)
    Sinon
        Retourner RechercheDicho(T, valeur, milieu + 1, droite)
    FinSi
Fin

 

Traçage des appels récursifs

Le traçage manuel doit distinguer les appels créés, les paramètres propres à chaque appel et les valeurs retournées. Une méthode fiable consiste à numéroter les appels et à noter les opérations en attente.

Méthode de traçage

  1. Écrire l’appel initial avec les valeurs de ses paramètres.
  2. Évaluer la condition du cas de base.
  3. Si le cas récursif est choisi, noter l’opération en attente.
  4. Écrire le nouvel appel avec ses nouveaux paramètres.
  5. Répéter jusqu’au cas de base.
  6. Remonter les appels dans l’ordre inverse et calculer chaque valeur retournée.
  7. Vérifier que le résultat final correspond au jeu d’essai.

Niveau

Appel

Opération en attente

Retour

1Somme(3)3 + Somme(2)6
2Somme(2)2 + Somme(1)3
3Somme(1)1 + Somme(0)1
4Somme(0)Aucune0

Erreurs fréquentes et bonnes pratiques

Erreur fréquente

Pourquoi elle est dangereuse

Bonne pratique

Oublier le cas de baseLa fonction ne possède aucune sortie terminale.Écrire le cas de base avant l’appel récursif.
Appeler avec le même argumentAucune progression n’est réalisée.Identifier une mesure qui diminue strictement.
Modifier le mauvais paramètreLe cas de base peut devenir inaccessible.Tracer trois appels à la main avant de valider.
Confondre affichage et retourLe résultat ne peut pas être combiné.Retourner une valeur lorsqu’un calcul dépend du résultat.
Dupliquer des appels coûteuxLe temps peut devenir exponentiel.Mémoriser les résultats ou utiliser une version itérative.
Ignorer les entrées invalidesLe domaine de la récurrence n’est plus respecté.Définir et contrôler les préconditions.
Utiliser une profondeur excessiveLa pile peut être saturée.Préférer une boucle ou une pile explicite si nécessaire.

Checklist avant validation

  • Le cas de base couvre-t-il les plus petites entrées valides ?
  • L’appel récursif reçoit-il une instance strictement plus petite ?
  • Tous les chemins d’exécution finissent-ils par retourner une valeur ?
  • Les opérations en attente combinent-elles correctement les résultats ?
  • Les entrées invalides sont-elles refusées ou traitées ?
  • La profondeur maximale reste-t-elle raisonnable ?
  • Les mêmes sous-problèmes sont-ils recalculés inutilement ?
  • Des jeux d’essai limites ont-ils été tracés ?

Travaux dirigés

Pour chaque exercice, préciser le cas de base, le cas récursif, la mesure de progression, le pseudo-code et au moins trois jeux d’essai.

Exercice 1 — Affichage descendant et ascendant

Écrire une procédure récursive qui reçoit N et affiche d’abord les entiers de N à 1, puis les entiers de 1 à N. Expliquer la position des instructions d’affichage par rapport à l’appel récursif.

Exercice 2 — Somme des chiffres

Écrire une fonction récursive qui calcule la somme des chiffres d’un entier positif. Exemple : SommeChiffres(472) = 13.

Exercice 3 — Nombre de chiffres

Écrire une fonction récursive qui détermine le nombre de chiffres d’un entier positif. Préciser le traitement de la valeur 0.

Exercice 4 — Inversion d’une chaîne

Écrire une fonction récursive qui retourne l’inverse d’une chaîne. Exemple : Inverser("ALGO") retourne "OGLA".

Exercice 5 — Palindrome

Écrire une fonction récursive qui vérifie si une chaîne est un palindrome en comparant les caractères situés aux deux extrémités.

Exercice 6 — Minimum d’un tableau

Écrire une fonction récursive qui retourne la plus petite valeur des n premiers éléments d’un tableau non vide.

Exercice 7 — Recherche dichotomique

Écrire et tracer une recherche dichotomique récursive dans le tableau trié [2, 5, 8, 12, 16, 23, 38]. Rechercher successivement 16, 2 et 10.

Exercice 8 — Conversion décimale vers binaire

Écrire une procédure récursive qui affiche la représentation binaire d’un entier positif en utilisant la division entière par 2 et le modulo 2.

Exercice 9 — Tours de Hanoï

Trois tiges A, B et C contiennent des disques de tailles différentes. Écrire la stratégie récursive permettant de déplacer N disques de A vers C en utilisant B, sans poser un grand disque sur un plus petit.

Exercice 10 — Diagnostic d’algorithmes

Pour chacun des algorithmes suivants, indiquer s’il se termine et proposer une correction si nécessaire :

a) F(n) : si n = 0 retourner 0 sinon retourner F(n)
b) G(n) : si n ≤ 0 retourner 1 sinon retourner n × G(n - 2)
c) H(n) : si n = 1 retourner 1 sinon retourner H(n / 2)
d) R(T, i) : si T[i] = x retourner i sinon retourner R(T, i + 1)

 

Corrigés indicatifs

Corrigé 1 — Affichage descendant et ascendant

Procédure Descendant(n : Entier)
Début
    Si n > 0 Alors
        Afficher(n)
        Descendant(n - 1)
    FinSi
Fin

Procédure Ascendant(n : Entier)
Début
    Si n > 0 Alors
        Ascendant(n - 1)
        Afficher(n)
    FinSi
Fin

 

Dans la première procédure, l’affichage précède l’appel : il se produit pendant la descente. Dans la seconde, il suit l’appel : il se produit pendant la remontée.

Corrigé 2 — Somme des chiffres

Fonction SommeChiffres(n : Entier) : Entier
Début
    n ← ValeurAbsolue(n)
    Si n < 10 Alors
        Retourner n
    Sinon
        Retourner (n MOD 10) + SommeChiffres(n DIV 10)
    FinSi
Fin

 

La division entière par 10 supprime le dernier chiffre. La mesure de progression est le nombre de chiffres restant.

Corrigé 3 — Nombre de chiffres

Fonction NombreChiffres(n : Entier) : Entier
Début
    n ← ValeurAbsolue(n)
    Si n < 10 Alors
        Retourner 1
    Sinon
        Retourner 1 + NombreChiffres(n DIV 10)
    FinSi
Fin

 

La valeur 0 contient un chiffre, donc le cas n < 10 retourne 1.

Corrigé 4 — Inversion d’une chaîne

Fonction Inverser(chaine : Chaîne) : Chaîne
Début
    Si Longueur(chaine) ≤ 1 Alors
        Retourner chaine
    Sinon
        Retourner DernierCaractere(chaine)
                 + Inverser(SousChaineSansDernier(chaine))
    FinSi
Fin

 

Une autre solution place le premier caractère après l’inversion du reste. L’important est que la chaîne transmise à l’appel soit plus courte.

Corrigé 5 — Palindrome

Fonction EstPalindrome(chaine : Chaîne, gauche, droite : Entier) : Booléen
Début
    Si gauche ≥ droite Alors
        Retourner Vrai
    SinonSi chaine[gauche] ≠ chaine[droite] Alors
        Retourner Faux
    Sinon
        Retourner EstPalindrome(chaine, gauche + 1, droite - 1)
    FinSi
Fin

 

L’appel initial utilise gauche = 0 et droite = Longueur(chaine) - 1.

Corrigé 6 — Minimum d’un tableau

Fonction MinimumRec(T : Tableau, n : Entier) : Réel
Variables
    minReste : Réel
Début
    Si n = 1 Alors
        Retourner T[0]
    FinSi
    minReste ← MinimumRec(T, n - 1)
    Si T[n - 1] < minReste Alors
        Retourner T[n - 1]
    Sinon
        Retourner minReste
    FinSi
Fin

 

Précondition : n ≥ 1. Chaque appel traite un préfixe contenant un élément de moins.

Corrigé 7 — Recherche dichotomique

Pour rechercher 16, le milieu initial contient 12, puis la recherche continue à droite et trouve 16. Pour rechercher 2, elle continue à gauche jusqu’au premier élément. Pour 10, l’intervalle finit par devenir vide et la fonction retourne -1.

Fonction RechercheDicho(T, x, g, d : Entier) : Entier
Variables
    m : Entier
Début
    Si g > d Alors
        Retourner -1
    FinSi
    m ← (g + d) DIV 2
    Si T[m] = x Alors
        Retourner m
    SinonSi x < T[m] Alors
        Retourner RechercheDicho(T, x, g, m - 1)
    Sinon
        Retourner RechercheDicho(T, x, m + 1, d)
    FinSi
Fin

 

Corrigé 8 — Conversion décimale vers binaire

Procédure AfficherBinaire(n : Entier)
Début
    Si n ≥ 2 Alors
        AfficherBinaire(n DIV 2)
    FinSi
    AfficherSansRetourLigne(n MOD 2)
Fin

 

L’appel récursif est effectué avant l’affichage afin que les bits de poids fort soient affichés en premier. Pour n = 0, on peut traiter séparément l’appel initial et afficher 0.

Corrigé 9 — Tours de Hanoï

Procédure Hanoi(n : Entier, depart, intermediaire, arrivee : Caractère)
Début
    Si n = 1 Alors
        Afficher("Déplacer un disque de ", depart, " vers ", arrivee)
    Sinon
        Hanoi(n - 1, depart, arrivee, intermediaire)
        Afficher("Déplacer un disque de ", depart, " vers ", arrivee)
        Hanoi(n - 1, intermediaire, depart, arrivee)
    FinSi
Fin

 

Le nombre minimal de déplacements est 2^n - 1. L’exercice illustre une récursion à deux branches et une croissance exponentielle du nombre d’actions.

Corrigé 10 — Diagnostic d’algorithmes

Algorithme

Diagnostic

Correction ou condition

F(n)Ne progresse jamais : F(n) rappelle F(n).Utiliser un argument plus petit, par exemple F(n - 1).
G(n)Se termine pour n positif, car n diminue de 2 et le cas n ≤ 0 couvre les deux parités.Valider le domaine et vérifier le résultat attendu.
H(n)Le cas n = 1 est insuffisant pour n = 0 ; la division dépend aussi du type.Utiliser n ≤ 1 et une division entière.
R(T, i)Peut dépasser la fin du tableau si x est absent.Tester i = taille avant d’accéder à T[i].

Synthèse du chapitre

La récursivité permet à une fonction de résoudre un problème en appelant une version plus petite d’elle-même. Une solution récursive fiable repose sur un cas de base, un cas récursif, une progression démontrable et une combinaison correcte des résultats.

Notion

À retenir

Cas de baseIl retourne directement un résultat et arrête les appels.
Cas récursifIl réduit le problème et appelle la fonction sur une instance plus petite.
Pile d’appelsElle conserve les paramètres, variables locales et opérations en attente.
DescenteLes appels sont créés jusqu’au cas de base.
RemontéeLes résultats sont retournés dans l’ordre inverse.
TerminaisonUne mesure non négative doit diminuer strictement.
CoûtLa récursivité peut consommer plus de mémoire ou répéter des calculs.
ChoixUtiliser la formulation la plus claire et la plus adaptée aux contraintes.

Checklist de conception d’une fonction récursive

1. Définir précisément le problème et le domaine des entrées.

2. Identifier la plus petite instance résoluble directement.

3. Écrire le ou les cas de base.

4. Choisir une mesure de progression.

5. Construire un appel avec une instance strictement plus petite.

6. Déterminer comment combiner le résultat récursif avec le cas courant.

7. Tracer plusieurs appels, y compris les cas limites.

8. Évaluer la profondeur de pile et les calculs répétés.

9. Comparer avec une solution itérative lorsque cela est pertinent.

10. Tester les entrées incorrectes et documenter les préconditions.

Glossaire

Terme

Définition

RécursivitéTechnique dans laquelle un sous-programme s’appelle lui-même directement ou indirectement.
Cas de baseCas résolu sans nouvel appel récursif.
Cas récursifCas qui réduit le problème et effectue un nouvel appel.
Pile d’appelsStructure mémorisant les contextes des appels actifs.
ProfondeurNombre maximal d’appels simultanément présents dans la pile.
DescentePhase de création successive des appels.
RemontéePhase de retour et de combinaison des résultats.
Récursion terminaleRécursion dont l’appel récursif est la dernière opération.
MémoïsationMémorisation de résultats déjà calculés pour éviter les répétitions.
Dépassement de pileErreur provoquée par une profondeur d’appels supérieure à la capacité disponible.

Auto-évaluation

Compétence

Acquise

À renforcer

Je sais identifier le cas de base et le cas récursif.

Je sais expliquer l’empilement et le dépilement des appels.

Je sais tracer une fonction récursive simple.

Je sais vérifier qu’un argument progresse vers le cas de base.

Je sais comparer une solution récursive et une solution itérative.

Je sais écrire Factorielle, Fibonacci, Puissance et PGCD.

Je sais effectuer une recherche récursive dans un tableau.

Je sais détecter une récursion infinie ou inefficace.

Conclusion : la récursivité est une manière de penser avant d’être une syntaxe. Sa maîtrise repose sur la capacité à réduire un problème, à contrôler la terminaison et à raisonner sur la pile des appels.