Programmation récursive



Documents pareils
Les structures de données. Rajae El Ouazzani

Java Licence Professionnelle CISII,

Travaux pratiques. Compression en codage de Huffman Organisation d un projet de programmation

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile

Les arbres binaires de recherche

Initiation à la programmation en Python

Initiation à l algorithmique

Chapitre 2. Classes et objets

Application 1- VBA : Test de comportements d'investissements

Programmation C++ (débutant)/instructions for, while et do...while

MISE A NIVEAU INFORMATIQUE LANGAGE C - EXEMPLES DE PROGRAMMES. Université Paris Dauphine IUP Génie Mathématique et Informatique 2 ème année

I. Introduction aux fonctions : les fonctions standards

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère

Projet ISN - dossier réalisé par Randrianarimanana Stéphanie. Titre du projet : Site de rencontre. le nom de notre site de rencontre : Linkymeet

Arbres binaires de recherche

Cours d initiation à la programmation en C++ Johann Cuenin

Cours de Programmation Impérative: Zones de mémoires et pointeurs

Algorithme. Table des matières

Algorithmique et Programmation, IMA

Centre CPGE TSI - Safi 2010/2011. Algorithmique et programmation :

Licence Bio Informatique Année Premiers pas. Exercice 1 Hello World parce qu il faut bien commencer par quelque chose...

UEO11 COURS/TD 1. nombres entiers et réels codés en mémoire centrale. Caractères alphabétiques et caractères spéciaux.

INF2015 Développement de logiciels dans un environnement Agile. Examen intra 20 février :30 à 20:30

Chapitre 10. Les interfaces Comparable et Comparator 1

ARBRES BINAIRES DE RECHERCHE

1. Création d'un état Création d'un état Instantané Colonnes Création d'un état Instantané Tableau... 4

1 de 46. Algorithmique. Trouver et Trier. Florent Hivert. Mél : Florent.Hivert@lri.fr Page personnelle : hivert

Java Licence Professionnelle Cours 7 : Classes et méthodes abstraites

Correction TD algorithmique

Polymorphisme, la classe Object, les package et la visibilité en Java... 1

Seance 2: En respectant la méthode de programmation par contrat, implémentez les autres fonctions de jeu.

Compression de Données - Algorithme de Huffman Document de Conception

IN Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C

Initiation. àl algorithmique et à la programmation. en C

Bases de programmation. Cours 5. Structurer les données

4. Groupement d objets

introduction Chapitre 5 Récursivité Exemples mathématiques Fonction factorielle ø est un arbre (vide) Images récursives

Prénom : Matricule : Sigle et titre du cours Groupe Trimestre INF1101 Algorithmes et structures de données Tous H2004. Loc Jeudi 29/4/2004

Complexité. Licence Informatique - Semestre 2 - Algorithmique et Programmation

Algorithmes de recherche

TD3: tableaux avancées, première classe et chaînes

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP

ACTIVITÉ DE PROGRAMMATION

TP3 : Manipulation et implantation de systèmes de fichiers 1

Généralités sur le Langage Java et éléments syntaxiques.

Licence Sciences et Technologies Examen janvier 2010

Chapitre 4 Pierre, papier, ciseaux

Algorithmique I. Algorithmique I p.1/??

Premiers Pas en Programmation Objet : les Classes et les Objets

Cours 1 : Introduction. Langages objets. but du module. contrôle des connaissances. Pourquoi Java? présentation du module. Présentation de Java

Algorithmique avec Algobox

Recherche dans un tableau

TP, première séquence d exercices.

Représentation d un entier en base b

Tp 1 correction. Structures de données (IF2)

modélisation solide et dessin technique

INTRODUCTION AUX SYSTEMES D EXPLOITATION. TD2 Exclusion mutuelle / Sémaphores

STAGE IREM 0- Premiers pas en Python

Projet de programmation (IK3) : TP n 1 Correction

C++ COURS N 2 : CLASSES, DONNÉES ET FONCTIONS MEMBRES Classes et objets en C++ Membres d'une classe Spécification d'une classe Codage du comportement

BANQUES DE DONNÉES PÉDAGOGIQUES

Page 1 sur 5 TP3. Thèmes du TP : l la classe Object. l Vector<T> l tutorial Interfaces. l Stack<T>

Les différents types de données et leurs opérations de base

PROBLEMES D'ORDONNANCEMENT AVEC RESSOURCES

Introduction à MATLAB R

Les chaînes de caractères

1.6- Génération de nombres aléatoires

SHERLOCK 7. Version du 01/09/09 JAVASCRIPT 1.5

LMI 2. Programmation Orientée Objet POO - Cours 9. Said Jabbour. jabbour@cril.univ-artois.fr

1 Recherche en table par balayage

Chapitre 1 : Introduction aux bases de données

Organigramme / Algorigramme Dossier élève 1 SI

RAPPELS SUR LES METHODES HERITEES DE LA CLASSE RACINE Object ET LEUR SPECIALISATION (i.e. REDEFINITION)

Cours de Systèmes d Exploitation

Cours d Algorithmique et de Langage C v 3.0

LE PROBLEME DU PLUS COURT CHEMIN

Cours Informatique Master STEP

Algorithmique, Structures de données et langage C

Conventions d écriture et outils de mise au point

chapitre 4 Nombres de Catalan

P r ob lé m a t iq u e d e la g é n é r icit é. Pr in cip e d e la g é n é r icit é e n Ja v a ( 1 /3 )

SOMMAIRE. Travailler avec les requêtes... 3

Java Licence Professionnelle CISII, Cours 2 : Classes et Objets

INTRODUCTION A JAVA. Fichier en langage machine Exécutable

1 Définition et Appel d une fonction. V. Phan Luong. Cours 4 : Fonctions

Algorithmique & programmation

1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3.

Chapitre 1 I:\ Soyez courageux!

1. Introduction Création d'une requête...2

Programmer en JAVA. par Tama

Sub CalculAnnuite() Const TITRE As String = "Calcul d'annuité de remboursement d'un emprunt"

TD Objets distribués n 3 : Windows XP et Visual Studio.NET. Introduction à.net Remoting

Série TD 3. Exercice 4.1. Exercice 4.2 Cet algorithme est destiné à prédire l'avenir, et il doit être infaillible! Exercice 4.3. Exercice 4.

Chapitre VI- La validation de la composition.

Propagation sur réseau statique et dynamique

Quelques Algorithmes simples

l'ordinateur les bases

Algorithmique avec Algobox

Transcription:

Année 2004-2005 F. Lévy IUT De Villetaneuse Dép t informatique Cours d'algorithmique 2 éme Année Cours 8 Programmation récursive 1. Qu'est-ce que la programmation récursive Définition : la programmation récursive est une technique de programmation qui remplace les instructions de boucle (while, for, etc.) par des appels de fonction. (Ne pas confondre avec la notion de récursivité en mathématiques) 1.1. Premier exemple Le mécanisme le plus simple pour faire boucler une fonction : elle se rappelle elle-même. Exemple de définition de fonction qui se rappelle elle même : void boucle() { boucle(); On peut en profiter pour faire quelque chose : void boucle() { printf ("Je tourne \n") ; boucle() ; C'est ce qu'on appelle une récursivité simple. Il faut encore ajouter un mécanisme de test d'arrêt. Ex. écrire 100 fois "Je tourne" : on a besoin d'un compteur. On choisit ici de le passer d'un appel de fonction à l'autre comme un paramètre. void boucle(int i) { if (i < 100) { printf ("Je tourne \n") ; boucle(i+1) ; //sinon on ne relance pas => fin du programme et il faut appeler boucle(0). 1.2. Apprendre à programmer récursivement avec des variables Calculer la somme des n premiers nombres avec une boucle while : int somme(int n) { int s = 0 ; int i = 1 ; while (i <= n) { s = s+i ; i=i+1 ; return s ; page 1/8

par une procédure récursive, méthode très naïve : int n = 100 ; int s = 0 ; void sommerec(int i) { if (i <= n) { s = s+i ; sommerec(i+1) ; //si i = n, fin du programme et on lance sommerec(0) ; C'est extrêmement maladroit parce que : 1. on ne contrôle pas la valeur de s au début 2. on ne contrôle pas la valeur terminale N. Pour éviter cela, il faut accéder à s et n sans qu'elles soient en variables globales. Une solution pour construire une procédure récursive sérieuse est d'utiliser pour passer s et n les paramètres et la valeur de retour de la fonction int sommerec(int i, int s, int n) { if (i <= n) { return sommerec(i+1, s+i, n) ; return (s) ; //sommerec(n,s,n), on a fini et on lance sommerec(0, 0, 100) ; On aurait pu éviter de passer à la fois i et n, en comptant à l'envers de n à 0 : int sommerec(int s, int n) { if (n > 0) { return sommerec(s + n, n - 1 ); return (s) ; //Pour sommerec(0, s), le calcul est immédiat On lance x = sommerec(0, 100). Et on pouvait même éviter de passer s, en gérant plus efficacement la valeur de retour : int sommerec(int n) { if (n > 0) { return sommerec(n - 1 ) + n; return (0) ; //Pour sommerec(0), le calcul est immédiat On lance x = sommerec(100). Enfin, on peut s'apercevoir qu'il est plus astucieux de programmer une fonction plus générale : sommerec(début, fin), c'est "faire la somme des nombres de début jusqu'à fin ". La programmation récursive, c'est : on appelle la même fonction avec un nombre de moins dans la liste, puis on ajoute ce nombre au résultat. D'où deux versions : Page 2/8

Année 2004-2005 F. Lévy int sommerec(int deb, int fin) { if (fin >= deb) { return sommerec(deb, fin - 1 ) + fin; return (0) ; //Pour sommerec(x,x), le calcul est immédiat ou int sommerec(int deb, int fin) { if (fin >= deb) { return deb + sommerec(deb + 1, fin ); return (0) ; //Pour sommerec(x,x), le calcul est immédiat Danns les deux cas, on lance sommerec(0, 100), sommerec(10, 50), etc. 2. Différents types de récursivité 2.1. Récursivité simple Définition : une fonction simplement récursive, c'est une fonction qui s'appelle elle-même une seule fois, comme c'était le cas pour sommerec() ci dessus. Le résultat dépend de l'ordre dans lequel on fait les opérations dans le corps de la fonction. Par exemple, on a un tableau de mots que l'on veut afficher par une procédure récursive, on peut faire l'appel récursif avant ou après l'affichage : public class StringRec { String tabmots [] ; StringRec(String mots[]){ tabmots = mots ; void afficherecap(int i) { if (i < tabmots.length) { System.out.print(tabmots[i] + " ") ; afficherecap(i+1) ; void afficherecav(int i) { if (i < tabmots.length) { afficherecav(i+1) ; System.out.print(tabmots[i] + " ") ; Définition : un appel récursif dans lequel la fonction n'exécute aucune instruction après l'appel est un appel récursif terminal. page 3/8

Ex : afficherecap utilise un appel récursif terminal, alors que afficherecav utilise un appel non terminal. 2.2. Quelques explications afficherecap et afficherecav ne produisent pas le même résultat. Avec tabmots initialisé à {"Dernier", "cours", "algo", "avancee", on obtient pour afficherecap : "Dernier cours algo avancee", pour afficherecav : "avancee algo cours Dernier". afficherecap() écrit donc le tableau à l'endroit, alors que afficherecav() l'écrit à l'envers. Dans les deux cas, on a les appels (X = Ap ou Av) : afficherecx(0) afficherecx(1) afficherecx(2) afficherecx(3). afficherecap(i) écrit le mot i, puis lance afficherecap(i+1) qui écrit le mot i+1 après le mot i. Au contraire, afficherecav(i+1) se termine (après avoir écrit le mot i+1) avant que afficherecav(i) n'écrive le mot i. Dans les deux cas, la fonction appelante se termine après la fonction appelée. Dans un appel terminal, elle se termine immédiatement après, sans rien faire de plus. Remarque : ça ressemble beaucoup aux parcours d'arbres en profondeur, préfixés ou postfixés. On va voir pourquoi un peu plus loin. 2.3. Récursivité multiple Une fonction peut exécuter plusieurs appels récursifs typiquement deux, parfois plus. Par exemple : void afficherec2(int i) { if (i < tabmots.length) { afficherec2(i+1) ; System.out.print(tabmots[i] + " ") ; afficherec2(i+1) ; Avec tabmots initialisé à {"Dernier", "cours", "algo", "avancee", on obtient pour afficherec2(0) : avancee algo avancee cours avancee algo avancee Dernier avancee algo avancee cours avancee algo avancee Que s'est-il passé? Chaque fonction afficherec2(i) appelle deux fois afficherec2(i+1). Si l'on dessine les appels (le premier appel à gauche), on a : Aff..Rec2(i) Aff..Rec2(i+1) Aff..Rec2(i+1) Page 4/8

Année 2004-2005 F. Lévy Ce qui, quand on regarde l'ensemble, constitue un arbre. Aff..Rec2(0) Aff..Rec2(1) Aff..Rec2(1) Aff..Rec2(2) Aff..Rec2(2) Aff..Rec2(2) Aff..Rec2(2) Aff..Rec2(3) Aff..Rec2(3) Aff..Rec2(3) Aff..Rec2(3) Aff..Rec2(3) Aff..Rec2(3) Aff..Rec2(3) Aff..Rec2(3) Il faut analyser un peu plus les appels récursifs pour comprendre l'affichage. Pour l'instant, notez qu'on a bien une fois "dernier", 2 fois "cours", 4 fois "algo" et 8 fois "avancee" A retenir : dans n'importe quel programme, les appels de fonction forment un arbre (à l'exécution). En C, la racine de l'arbre est. Chaque fonction appelle zéro, une, deux,.., n fonctions (ses fonctions filles), qui à leur tour appellent leurs fonctions filles, etc. Quand l'exécution d'une fonction est finie, on revient à sa fonction mère, puis celle-ci va appeler la prochaine de ses filles. L'exécution réalise donc un parcours en profondeur d'abord. 2.4. Appel récursif, arbre et pile Toute fonction "connaît" un certain nombre de variables et leurs valeurs. On appelle cet ensemble de couples (variable,valeur) le contexte de la fonction. Quand on appelle une fonction, ses arguments sont les modifications du contexte existant pour constituer le contexte de la fonction. Ils lui sont passés sur la pile. Quand la fonction se termine, elle dépile les arguments, et l'on retourne à la fonction appelante qui retrouve son contexte. Quand on regarde l'arbre des appels ci dessus, le sous arbre sous le fils précédent est entièrement exécuté avant qu'on passe au fils suivant. Autrement dit, comme dans le cas standard, l'exécution d'un appel récursif est un parcours d'arbre en profondeur d'abord. Nous avons appris que pour un parcours en profondeur, il faut une pile. Ici, on a utilisé implicitement la pile système (la pile des arguments de fonction) pour revenir au père (à la fonction appelante). Si l'on condidère afficherec2(), elle affiche entre les deux appels récursifs i.e. entre ses fils. On retrouve donc une énumération infixée. Sachant que afficherec2(0) affiche dernier, afficherec2(1) affiche cours,, on retrouve le texte produit par l'énumération infixée. Note : on peut transformer une procédure récursive terminale en procédure itérative sans pile, alors que ce n'est pas possible pour une procédure récursive non terminale. page 5/8

3. Quelques exemples 3.1. Récursivité simple : parcours de liste Il faut avoir implanté la liste par une définition récursive (une liste contient une liste en variable membre). public class ListeRec { Object valeurtete ; ListeRec reste ; String tostring(){ return(valeurtete.tostring() + reste.tostring()) ; String toreversestring(){ return(reste.tostring() + valeurtete.tostring()) ; 3.2. Récursivité simple : recherche dichotomique On stocke des paires (index, objet) dans un tableau. Le tableau est rangé par index (des chaînes en ordre alphabétique). L'utilisateur fournit un index et on doit lui renvoyer l'objet associé. public class DichoMap { MapObject table[] ; MapObject getobject(string index) { return (getobject(index,0,table.length)) ; MapObject getobject(string index, int deb, int fin) { if(deb > fin) return(null) ; int milieu = (deb + fin)/2 ; MapObject mo = table[milieu] ; int order = mo.getindex().compareto(index) ; if(order == 0) return (mo) ; if (order < 0) return (getobject(index, milieu+1, fin)) ; return(getobject(index, deb, milieu-1)) ; Notez bien que c'est une récursivité simple (un seul des deux appels est exécuté) et terminale (quel que soit l'appel exécuté, c'est la dernière instruction de la fonction return n'est pas une instruction) Page 6/8

Année 2004-2005 F. Lévy 3.3. Récursivité double C'est typiquement une situation de parcours d'arbre binaire. Par ex. pour calculer la valeur d'une expression qu'on a transformée en arbre (qu'on pourrait ajouter dans une classe Expression vue en TD). public class arbrecalcul{ String symbol ; // +, - *, / ou un nombre arbrecalcul filsg, filsd ; int getvalrec() { if (this.estfeuille()) return(integer.parseint(symbol)) ; int val1, val2, res ; val1 = filsg.getvalrec() ; val2 = filsd.getvalrec() ; switch(symbol.charat(0)) { case '+' : res=val1+val2 ; case '-' : res=val1-val2 ; // etc Une récursivité double n'est jamais terminale (le premier appel ne peut pas l'être). 4. Bilan sur la récursivité 4.1. A quoi ça sert Dans certains cas, on peut écrire un code très court et bien lisible en utilisant une procédure récursive. C'est commode pour prototyper un programme (faire un exemplaire d'essai, de mise au point, sur lequel on ne veut pas passer trop de temps) : on a une pile gratuitement, sans avoir besoin de l'écrire. Cela n'a d'intérêt que pour les opérations qui utilisent une pile, c'est à dire en gros les parcours d'arbres en profondeur d'abord. La procédure récursive n'est jamais plus rapide que la procédure itérative correspondante : fondamentalement, le temps de calcul est surtout déterminé par l'algorithme, et tout algorithme peut s'écrire en itératif ou en récursif. Par contre, la pile système passe en général plus de valeurs que la pile qu'on aurait implémentée (l'adresse de retour, mais aussi les arguments supplémentaires). Elle prend donc un peu plus de temps, et surtout nettement plus de place. Il est très rare qu'on utilise une procédure récursive dans la version définitive d'un programme professionnel. page 7/8

4.2. Les Warnings La programmation récursive, ça ne peut pas faire n'importe quoi. Par exemple, ça ne peut pas simplifier le codage d'un parcours en largeur : on est obligé de réimplémenter la file dans les arguments de fonction, et la pile de l'appel récursif est en plus. La plus grosse difficulté, c'est qu'il faut bien connaître les calculs de complexité, parce que le temps de calcul d'une procédure récursive naïve peut être catastrophique. Exemple classique : les nombres de Fibboncci, définis par Fib(0) = Fib(1) = 1, Fib(i) = Fib(i-1) + Fib(i 2) si i >2. int fibo(int n){ if(n<2) return(1) ; int i=1, f0=1, f1=1, tmp ; while(i<n) { tmp = f0 + f1 ; f0 = f1 ; f1 = tmp ; i++ ; return(f1) ; On est en O(n) int fiborec(int n) { if(n<2) return(1) ; return (fiborec(n-1) + fiborec(n-2)) ; On a gagné 4 lignes, mais on est en O(2 n ). Vous pouvez tester la différence. On peut aussi écrire une fonction fibonacci récursive en O(n) (encore une fois, tout ce qu'on peut faire en itératif peut être fait aussi en récursif). La difficulté est la même qu'en itératif question de goût, de style et de langage de programmation. Page 8/8