L’optimisation d’une fonction récursive consiste à améliorer ses performances, principalement en termes de temps d’exécution et d’utilisation de la mémoire. Voici les principales techniques d’optimisation utilisées pour les fonctions récursives en C :
La mémorisation consiste à stocker les résultats intermédiaires des appels récursifs pour éviter de recalculer les mêmes résultats à plusieurs reprises. Cela est particulièrement utile pour des problèmes comme la suite de Fibonacci, où la récursion simple conduit à une répétition d’appels inutiles.
Le calcul de Fibonacci récursif sans optimisation recalculera plusieurs fois les mêmes valeurs, ce qui est inefficace.
#include <stdio.h>
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int n = 40;
printf("Fibonacci(%d) = %d\n", n, fibonacci(n));
return 0;
}
fibonacci(40)
peut être extrêmement long.En utilisant un tableau pour mémoriser les résultats déjà calculés, on évite de recalculer les mêmes sous-problèmes plusieurs fois. Cette technique améliore significativement les performances.
#include <stdio.h>
int memo[1000]; // Tableau pour mémoriser les résultats déjà calculés
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
if (memo[n] != -1) {
return memo[n]; // Si le résultat est déjà calculé, on le retourne
}
memo[n] = fibonacci(n - 1) + fibonacci(n - 2); // Calcul et mémorisation
return memo[n];
}
int main() {
int n = 40;
// Initialiser le tableau memo à -1 pour indiquer que rien n'a encore été calculé
for (int i = 0; i <= n; i++) {
memo[i] = -1;
}
printf("Fibonacci(%d) = %d\n", n, fibonacci(n));
return 0;
}
La récursion terminale est une forme d’optimisation où l’appel récursif est la dernière instruction d’une fonction. Certains compilateurs optimisent automatiquement ce type de récursion, car ils peuvent remplacer l’appel récursif par une simple boucle, évitant ainsi l’accumulation d’appels récursifs sur la pile d’exécution.
int factorielle(int n) {
if (n == 0) {
return 1;
}
return n * factorielle(n - 1); // Appel récursif non terminal
}
Ici, l’appel récursif n’est pas optimisé car la multiplication n *
doit être effectuée après le retour de l’appel récursif, donc chaque appel reste sur la pile jusqu’à la fin.
En transformant la fonction pour que l’appel récursif soit la dernière opération, la pile d’appels n’a plus besoin de conserver les états intermédiaires.
#include <stdio.h>
int factorielle_helper(int n, int acc) {
if (n == 0) {
return acc;
}
return factorielle_helper(n - 1, acc * n); // Appel récursif terminal
}
int factorielle(int n) {
return factorielle_helper(n, 1);
}
int main() {
int n = 5;
printf("Factorielle de %d = %d\n", n, factorielle(n));
return 0;
}
acc
(accumulateur) qui stocke le résultat intermédiaire. L’appel récursif est la dernière instruction dans la fonction, ce qui permet aux compilateurs de réduire les appels empilés.Dans certains cas, il est possible de transformer une fonction récursive en une version itérative, en utilisant une boucle pour éviter les appels récursifs et économiser l’espace mémoire.
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
#include <stdio.h>
int fibonacci_iterative(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}
int main() {
int n = 40;
printf("Fibonacci(%d) = %d\n", n, fibonacci_iterative(n));
return 0;
}
Pour des problèmes où la profondeur de la récursion peut être très grande, comme le tri rapide (quicksort) sur des tableaux de grande taille, il est important de limiter la profondeur de la récursion pour éviter les erreurs de dépassement de pile (stack overflow).
La stratégie diviser pour régner est une méthode d’optimisation utilisée dans de nombreux algorithmes récursifs, comme quicksort et mergesort. Elle consiste à diviser un problème en sous-problèmes plus petits, à les résoudre de manière récursive, puis à combiner les résultats pour obtenir la solution finale.
Voici la suite de l’exemple de l’algorithme Mergesort avec la fonction de fusion complétée et la fonction récursive pour trier le tableau.
#include <stdio.h>
// Fonction pour fusionner deux sous-tableaux
void fusion(int tableau[], int gauche, int milieu, int droite) {
int i, j, k;
int n1 = milieu - gauche + 1;
int n2 = droite - milieu;
// Créer des tableaux temporaires pour stocker les deux moitiés
int G[n1], D[n2];
// Copier les données dans les tableaux temporaires
for (i = 0; i < n1; i++)
G[i] = tableau[gauche + i];
for (j = 0; j < n2; j++)
D[j] = tableau[milieu + 1 + j];
// Fusionner les tableaux temporaires dans le tableau d'origine
i = 0; // Indice initial du premier sous-tableau (G)
j = 0; // Indice initial du second sous-tableau (D)
k = gauche; // Indice initial du tableau fusionné
// Fusionner les deux sous-tableaux en ordre croissant
while (i < n1 && j < n2) {
if (G[i] <= D[j]) {
tableau[k] = G[i];
i++;
} else {
tableau[k] = D[j];
j++;
}
k++;
}
// Copier les éléments restants du sous-tableau G[]
while (i < n1) {
tableau[k] = G[i];
i++;
k++;
}
// Copier les éléments restants du sous-tableau D[]
while (j < n2) {
tableau[k] = D[j];
j++;
k++;
}
}
// Fonction récursive pour trier le tableau avec l'algorithme Mergesort
void mergesort(int tableau[], int gauche, int droite) {
if (gauche < droite) {
// Calculer l'indice du milieu
int milieu = gauche + (droite - gauche) / 2;
// Appliquer récursivement Mergesort aux deux moitiés
mergesort(tableau, gauche, milieu);
mergesort(tableau, milieu + 1, droite);
// Fusionner les deux moitiés triées
fusion(tableau, gauche, milieu, droite);
}
}
int main() {
int tableau[] = {12, 11, 13, 5, 6, 7};
int taille = sizeof(tableau) / sizeof(tableau[0]);
printf("Tableau avant tri :\n");
for (int i = 0; i < taille; i++) {
printf("%d ", tableau[i]);
}
printf("\n");
// Appliquer l'algorithme Mergesort
mergesort(tableau, 0, taille - 1);
printf("Tableau après tri par Mergesort :\n");
for (int i = 0; i < taille; i++) {
printf("%d ", tableau[i]);
}
printf("\n");
return 0;
}
fusion()
s’occupe de fusionner deux sous-tableaux triés en un seul tableau trié.L’optimisation des fonctions récursives en C est essentielle pour améliorer les performances, surtout lorsque la profondeur de récursion devient grande ou que des sous-problèmes sont recalculés plusieurs fois. Les techniques d’optimisation incluent la mémorisation, la récursion terminale, la conversion en itération, et l’approche diviser pour régner.
Ces techniques permettent non seulement d’améliorer l’efficacité en termes de temps d’exécution, mais aussi de réduire la consommation de mémoire et d’éviter les problèmes tels que les erreurs de dépassement de pile. Le choix de la méthode d’optimisation dépend de la nature du problème que vous résolvez et des ressources disponibles.
1. Pourquoi apprendre le français ? / Why Learn French? Langue internationale : Le français…
1. Définition / Definition En français, chaque nom a un genre : masculin ou féminin.…
Le mot "français" peut être masculin ou féminin selon le contexte dans lequel il est…
Emprunter une somme aussi importante que 250 000 euros sur une 25 ans est une…
Ce guide explore les nombreux usages des verbes pronominaux passifs, offrant une classification claire et…
Dans le monde dynamique et compétitif des affaires, le suivi des achats joue un rôle…
This website uses cookies.