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