n >= 0, x
Transcription
n >= 0, x
LPSIL Année 2007-2008 TD Algorithmique Feuille 3 Récursivité Correction 1. Inverse d’une chaı̂ne /** conséquent : renvoie s écrite de droite à gauche * version récursive */ public String reverse(String s) { if (s.length()==0) return s; return reverse(s.substring(1))+ s.charAt(0); } /** conséquent : renvoie s écrite de droite à gauche * version itérative */ public String reverseIter(String s) { String result = ""; for (int i=s.length()-1; i>=0;i--) { // A1 : result contient la sous-chaine de i à laChaine.length - 1 renversée result = result + s.charAt(i); } return result; } 2. Puissance Puissance récursive avec le schéma usuel des entiers : /** puissance récursive * antécedent : n >= 0, x un entier <br> * consequent : renvoie x^p */ public int puissanceRecursive(int x,int p) { if (p==0) return 1; return x * puissanceRecursive(p-1); } Puissance récursive dichotomique : /** puissance dichotomique récursive * antécédent : x est un entier, p est un entier positif ou nul * conséquent : renvoie x^p */ public int puissanceDichoRecursif(int x, int p) { if (p==0) return 1; if (p%2==0) return puissanceDichoRecursif(x*x, p/2); else return x * puissanceDichoRecursif(x*x, (p-1)/2); } 1 3. Parenthèses /** ANTECEDENT : f contient les délimiteurs ouvrants non refermés de texte[0..i-1] * renvoie vrai si le texte de i à size : * - contient les délimiteurs fermants correspondants à f * - est bien parenthésé */ private boolean correct(int i, String f) { // on est à la fin du texte if (i==size) // on a trouvé tous les délimiteurs if (f.length()==0) return true; // certains délimiteurs n’ont pas été fermés else return false; char c = texte.charAt(i); if (estFermant(c)) { // c est fermant et il n’y a plus de délimiteurs ouvrant à refermer if (f.length()==0) return false; // c est fermant et il ne ferme pas le bon délimiteur if (c!=ferme(f.charAt(0))) return false; // c correspond bien au délimiteur recherché; // on continue à chercher les autres délimiteurs return correct(i+1,f.substring(1)); } // c est ouvrant, on doit chercher son délimiteur fermant if (estOuvrant(c)) return correct(i+1,c+f); // on n’est pas sur un délimiteur; on continue return correct(i+1,f); } 4. Complexité des méthodes • Méthode “reverse” La relation de récurrence est du type : C(0) = θ(1) C(n) = C(n − 1) + θ(1) La méthode “reverse” est donc de complexité θ(n) (à condition que la méthode “concat” de la classe “String” soit en θ(1)). • Méthode “correct” La complexité dépend seulement du nombre d’éléments entre i et c.length − 1, que l’on note n. La relation de récurrence est du type : C(0) = θ(1) C(n) = C(n − 1) + θ(1) puisque on fait un appel à “correct” sur des données de taille n − 1 (on commence à l’indice i+1). La méthode “correct” est donc de complexité θ(size) ce qui est la complexité minimale puisqu’il faut au moins parcourir tout le texte. • Méthode “puissance” : même relation de récurrence et complexité que “reverse”. • Méthode “puissanceDichoRecursif” : Le nombre d’étapes ne dépend que de p. A l’étape p, si p est pair, on fait un appel récursif pour p/2, si p est impair, on fait un appel récursif pour (p − 1)/2. Dans les deux cas, on a fait appel à la partie entière de p/2. 2 On obtient donc : C(0) = 1 C(p) = C(p/2) + θ(1) et donc C(p) = θ(log2 (p)) 5. Le voleur /** renvoie le butin de plus grande valeur de poids maximum poidsMax, * pris dans les objets d’indice <= j * complexité dans le pire des cas : theta(2^j) */ private int volerAux(int poidsMax, int j) { int p; int v; if (j>=0) { p = magasin[j].poids(); v = magasin[j].valeur(); if (poidsMax< p) // CAS 1 // on ne peut pas emporter l’objet d’indice j return volerAux(poidsMax, j-1); else // CAS 2 // on peut emporter ou non l’objet d’indice j // le butin sera le max du butin qu’on obtient en emportant j, // et du butin qu’on obtient sans emporter j return Math.max(v + volerAux(poidsMax-p, j-1), volerAux(poidsMax, j-1)); } else return 0; } Dans le meilleur et le pire des cas, la complexité dépend uniquement de j. Complexité dans le meilleur des cas : Dans le meilleur des cas, on passe toujours par le CAS 1; cela correspond à poidsM ax qui est inférieur au poids de chaque objet du magasin. Dans ce cas, la relation de récurrence est : C(0) = 1 C(j) = C(j − 1) + θ(1) et donc C(j) = θ(j) Complexité dans le pire des cas : Dans le pire des cas, on passe toujours par le CAS 2; cela correspond à poidsM ax qui est supérieur à la somme des poids des objets du magasin. Dans ce cas, la relation de récurrence est : C(0) = 1 C(j) = 2C(j − 1) + θ(1). En déroulant cette relation on obtient C(j) = θ(2j ) 6. Le Sudoku /* CONSEQUENT : place une valeur en (i,j), renvoie false si c’est impossible */ private boolean placer(int i, int j) { // on a dépassé la dernière ligne if (i==taille) return true; // la grille a une valeur initiale, on continue sur la prochaine case 3 if (grille[i][j]!=0) return placer(ligneSuivante(i,j),colonneSuivante(i,j)); int k=1; boolean possible = false; // on essaie toutes les valeurs possibles while (!possible && k<=taille) { if (carrePossible(i,j,k)&&lignePossible(i,k)&&colonnePossible(j,k)){ // on place la valeur k grille[i][j]=k; // en choisissant k on peut placer les autres lignes et colonnes if (placer(ligneSuivante(i,j),colonneSuivante(i,j))) possible = true; // en choisissant k on n’a pas pu finir de remplir la grille, on passe au k suivant else { grille[i][j]=0; k++; } // la valeur k n’est pas possible pour (i,j), on passe au k suivant } else k++; } return possible; } Complexité : La complexité dépend de la taille de la grille n. Au pire on fait n appels à la méthode placer pour la case suivante. Il y a n∗n cases en tout. Chaque appel à carreP ossible, ligneP ossible, colonneP ossible est en O(n). Si on note k = n∗n le nombre de cases possibles, la relation de récurrence est de la forme : C(0) = 1 C(k) <= n ∗ C(k − 1) + Ø(n) Si on déroule cette relation de récurrence le terme majorant sera nk donc C(k) = Ø(nk ) donc 2 la complexité est majorée par O(nn ) Pour plus de détails sur le nombre possibles de solutions d’un sudoku voir wikipedia, sudoku. 4