Structures de données IFT-2000 Abder Alikacem La récursivité Semaine 5 Département d’informatique et de génie logiciel Édition Septembre 2009
Plan du cours Notions de base Exemple simple (fonction factorielle) Limitations (suite de Fibonacci) Sous-séquence de somme maximale Backtracking
Notions de base Définition Un objet est récursif s'il est défini à partir de lui-même. Une fonction est récursive si elle peut s'appeler elle-même de façon directe ou indirecte Quand ? Suite récurrentes (exemple : factorielle) Type récursifs Liste non vide = premier + liste Arbre binaire = racine + sous-arbre droit + sous-arbre gauche (semaine 10)
Récursivité Quatre règles régissent la récursivité: 1.Trouver la récursion 2.Présence de cas de base pouvant être résolu sans récursivité 3.Toujours progresser vers le cas de base à chaque appel récursif 4.« Ayez la Foi » mais vérifier que les appels récursifs progressent réellement vers le cas de base. 5.Intérêt composé: Ne jamais dédoubler du travail dans deux appels récursifs différents.
Fonction récursive factorielle Soit la fonction Factorielle, effectuant l’opération n! Règle récursive : fact(n) = n * fact (n-1) Condition d’arrêt : fact (0) =1 Condition de convergence: n 0
Avantages et inconvénients Fiabilité Solution naturelle et facile à concevoir : si la fonction est récursive quand la structure de données traitée est récursive Avantages Formulation compacte, claire et élégante. Maîtrise des problèmes dont la nature même est récursive. Désavantages Possibilité de grande occupation de la mémoire. Temps d'exécution peut être plus long. Estimation difficile de la profondeur maximale de la récursivité.
Limitations: Fonction récursive Fibonnacci long fibo (long valeur) { if (valeur <= 1) return valeur; else return(fibo(valeur-1) + fibo(valeur-2)); } Soit la fonction Fibo (Fibonacci), effectuant l’opération f(n) = f(n-1) + f(n-2)
Exécution de Fibonnacci Soit les appels effectués pour fibo(4) : fibo(4) fibo(3) fibo(2) fibo(1)fibo(0) fibo(1) fibo(2) fibo(0)fibo(1)
Danger de Fibonnacci Note : Cette fonction récursive effectue plusieurs appels au même calcul. Par exemple pour déterminer f(n), on calcule d’abord f(n-1), puis au retour de l’appel, f(n-2) est calculé. Or, dans le calcul de f(n-1), f(n-2) est déjà calculé! Ce problème s’aggrave en descendant l’arborescence, puisque f(n-3) sera appelé à 3 reprises : chaque appel récursif entraînera de plus en plus d’appel redondant. Règle 5: Ne jamais dupliquer le travail par la résolution d’une même instance d’un problème dans plusieurs appels récursifs.
Champs d'application des algorithmes récursifs Structures de données définies récursivement (listes, arbres, graphes, etc..) Équations de récurrence (algébriques, vectorielles, booléennes, formelles, ensemblistes, etc.); Rétroaction ("backtracking"). … Nous allons voir ensemble quelques exemples typiques impliquant la récursivité.
Sous-séquence de somme maximale Définition (rappel du laboratoire#1): Étant donnée des entiers (possiblement négatifs) A 1, A 2,..., A N, trouver et identifier la séquence correspondante à la valeur maximale de. La somme de la sous-séquence est nulle si tous les entiers sont négatifs. Résolution (algorithme#4): Application de la technique de diviser et conquérir (divide-and-conquer), une Méthode de résolution de problèmes basée sur la récursivité: Diviser en plusieurs sous problèmes résolus récursivement. Conquérir, où la solution du problème original est composé de la solution des sous-problèmes.
Sous-séquence de somme maximale Cas possible: Cas 1: La séquence est situé dans la première moitié. Cas 2: La séquence est situé dans la deuxième moitié. Cas 3: La séquence commence dans la première moitié et se termine dans la deuxième moitié. Sommaire de l’algorithme: 1.Déterminer récursivement la sous-séquence de somme maximale uniquement dans la première moitié. 2.Déterminer récursivement la sous-séquence de somme maximale uniquement dans la deuxième moitié. 3.Avec deux boucles consécutives, déterminer la sous-séquence de somme maximale qui débute dans la première moitié et qui termine dans la deuxième moitié. 4.Choisir la somme la plus élevée des trois.
SousSequence trancheMax4(Sequence t, int d, int f) { SousSequence rep = { 0, 0, t[0] }; int m = (d+f)/2; if(d==f){rep.debut=d; rep.fin=f; rep.somme= t[d]; return rep;} Sousequence sm1 = trancheMax4(t, d, m); Sousequence sm2 = trancheMax4(t, m+1, f); int s1=t[m]; int s1m=s1; int dmg=m; for(int k = m-1; k>=d; --k) { s1+=t[k]; if(s1>s1m){s1m=s1; dmg=k;} } int s2=t[m+1]; int s2m=s2; int dmd=m+1; for(int k = m+2; k<=f; ++k){ s2+=t[k]; if(s2>s2m){s2m=s2; dmd=k;} } int sm3 = s1m+s2m; if(sm1.somme>=sm2.somme && sm1.somme>=sm3){ return sm1;} if(sm2.somme>=sm1.somme && sm2.somme>=sm3){ return sm2;} rep.debut= dmg; rep.fin = dmd; rep.somme= sm3; return rep; }
Sous-séquence de somme maximale (Exemple) max(2,-5,2-5) 2 max(3,2,3+2) 3 0 max(-1,0,-1+0) 2 max(-1,2,-1+2) max(0,2,0+1) max(5,2,0+0 )=5
Algorithme à essais successifs (AES) Problématique Pour résoudre certains types de problèmes, il peut être souvent utile de procéder à l'exploration systématique des différentes solutions possibles. Il est donc essentiel de développer des techniques algorithmiques qui permettent une telle exploration. AES ou encore algorithme de back tracking idée : construire progressivement une solution à un problème donné en essayant toutes les possibilités à chaque étape de la construction. Remarque: la complexité de ce genre d ’algorithme est de nature exponentielle => à utiliser si l ’on n ’en connaît pas de meilleur.
Backtracking Exemple. Calcul du trajet du robot Il s'agit de concevoir un algorithme qui permet à un robot de trouver un chemin menant à la sortie d'un labyrinthe. Pour ce faire le robot explore systématiquement les quatre directions vers lesquelles il peut se déplacer. De plus, afin d'éviter que le robot ne se retrouve sur des cases déjà explorées chacune de celles-ci seront marquées à mesure qu'elles sont visitées. C'est de façon récursive que les cases faisant partie du chemin seront identifiées.
Plan du site
Programmation du trajet d’un robot mobile #define Y 9 /* Les dimensions du labyrinthe */ #define X 7 char laby[X][Y] = { {'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'}, {'X', ' ', ' ', 'X', ' ', ' ', ' ', ' ', 'X'}, {'X', ' ', 'X', 'X', ' ', 'X', 'X', ' ', 'X'}, {'X', ' ', ' ', ' ', ' ', ' ', 'X', 'X', 'X'}, {'X', 'X', 'X', ' ', 'X', ' ', 'X', ' ', 'X'}, {'X', 's', ' ', ' ', ' ', ' ', ' ', ' ', 'X'}, {'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'}}; int main() { int i, j; bool Parcours(int x, int y); if (Parcours(1,1)) { // Imprimer le résultat de la recherche for (i = 0; i < X; i++, cout<<endl) for (j = 0; j < Y; j++) cout<<laby[i][j]; } else cout<<"Il n'existe aucun chemin.\n"; return 0; }
Programmation du trajet d’un robot mobile (suite) bool Parcours(int x, int y) { if (laby[x][y] == 's') { /* on a trouvé la sortie */ laby[x][y] = 'S'; return true; } else if (laby[x][y] == ' ') { /* la case est libre */ laby[x][y] = 'o'; /* marquer la case visitée */ if (Parcours(x-1, y)) /* Parcours nord */ return(true); else if (Parcours(x, y+1)) /* Parcours est */ return(true); else if (Parcours(x+1, y)) /* Parcours sud */ return(true); else if (Parcours(x, y-1)) /* Parcours ouest */ return(true); else laby[x][y] = '-'; /* on laisse une marque */ } return false; }
Retour en arrière - exercice Quel est le chemin parcouru? Combien de retours en arrière? Utiliser des appels récursifs et l’ordre d’appel suivant: Nord Sud Est Ouest A B S C D E F E G H I J
Retour en arrière - exercice A B S C D E F E G H I J bool Parcours(int x, int y) { if (laby[x][y] == 's') { laby[x][y] = 'S'; return true; } else if (laby[x][y] == ' ') { laby[x][y] = 'o'; if (Parcours(x-1, y)) return(true); else if (Parcours(x+1, y)) return(true); else if (Parcours(x, y+1)) return(true); else if (Parcours(x, y-1)) return(true); else laby[x][y] = '-'; } return false; }
Retour en arrière - réponse A B C D E F G H I J X E S oo o o ooX X o o o o o ooo o oX o oo o ooo o o o oo oX Xo o