• Aucun résultat trouvé

BROUILLON CorrigéduTP1:récursion

N/A
N/A
Protected

Academic year: 2022

Partager "BROUILLON CorrigéduTP1:récursion"

Copied!
6
0
0

Texte intégral

(1)

BR

OUILLON

Corrigé du TP 1 : récursion

Judicaël Courant 20 septembre 2017

Exercice 1 : Recherche séquentielle

Nous présentons une implantation impérative dechercheindex(t,x), appeléechercheindex_imp (t,n,x), dans le listing ci-dessous. Pour ce qui est de la fonction récursive, nous introdui-

sons une fonction récursivechercheindex_rec_aux(t,n,x)qui cherchexdans la portion de tableaut[:n]. Nous donnons ensuite une implantationchercheindex_rec(t,x)de la fonctionchercheindex(t,x)demandée, qui fait appel àchercheindex_rec_aux(t,n,x ).

# V e r s i o n imp é r a t i v e de l a f o n c t i o n demand é e :

def chercheindex_imp(t, x) :

" " " t e s t un t a b l e a u , x une v a l e u r .

Retourne un i n d e x i t e l que t [ i ] == x s i un t e l i n d e x e x i s t e . Retourne −1 s i n o n . " " "

f o r i in range(len(t) ) : i f t[i] == x:

return i

# i c i , on a f a i t t o u s l e s t o u r s de b o u c l e s a n s s u c c è s : return −1

# V e r s i o n r é c u r s i v e . On commence p a r d é f i n i r une f o n c t i o n a u x i l i a i r e :

def chercheindex_rec_aux(t, n, x) :

" " " t e s t un t a b l e a u , x une v a l e u r e t n un e n t i e r .

Retourne un i n d e x i dans [ 0 , n [ t e l que t [ i ] == x s i un t e l i e x i s t e .

Retourne −1 s i n o n . " " "

i f n == 0 : return −1 e l i f: t[n−1] == x:

return n−1 e l s e:

return chercheindex_rec_aux(t, n−1 , x)

Judicaël Courant - 20 septembre 2017 1/6 Document sous licence Art Libre (http://artlibre.org)

(2)

BR

OUILLON

# p u i s on é c r i t l a f o n c t i o n p r i n c i p a l e : def chercheindex_rec(t, x) :

" " " t e s t un t a b l e a u , x une v a l e u r .

Retourne un i n d e x i t e l que t [ i ] == x s i un t e l i n d e x e x i s t e . Retourne −1 s i n o n . " " "

return chercheindex_rec_aux(t, len(t) , x)

Pour ce qui est des questions de complexité, l’énoncé ne précise pas la complexité de la comparaison des éléments du tableau avec celle de l’élément recherché. Nous supposerons que celle-ci est enO(1) en temps et en espace.

La complexité temporelle de la fonction chercheindex_imp(t, x) est O(len(t)). En effet, l’exécution de cette fonction demande l’exécution d’une part de quelques opérations en temps constant (calcul de range(len(t))au début et return−1 ou returnià la fin), ainsi que de len(t)tours de boucle au plus, chaque tour s’effectuant en temps constant car ne comportant que des opérations de temps constant (comparaison de t[i] avec x).

La complexité spatiale de cette fonction est constante : seul est nécessaire l’espace pour calculer range(len(t))et stocker les valeurs successives dei.

Pour ce qui est de la complexité temporelle de chercheindex_rec_aux(t,x,n), on peut remarquer tout d’abord qu’à chaque appel récursif, les seules opérations qui sont faites, en dehors de l’éventuel appel récursif, sont, au plus :

— la comparaison de nà 0 ;

— le calcul det[n−1], sa comparaison avecx

— l’exécution de returnn−1

Ces opérations étant toutes en temps constant, on en déduit que la complexité temporelle de l’appel dechercheindex_rec_aux(t,x,n)dans le cas le pire est enO(A(n)) oùA(n) est le nombre total d’appels pour le pire choix des paramètrestetx.

Or, on a évidemment A(0) = 1 (un seul appel, zéro appel récursif dans le cas où n= 0) et pour tout entier n > 0, A(n) = A(n−1) + 1. On en déduit immédiatement

∀n∈NA(n) =n+ 1.

La complexité temporelle de l’exécution de chercheindex_rec_aux(t,x,n)est donc en O(n).

La complexité spatiale se calcule aisément : d’une part, en dehors de l’appel récursif, on n’utilise à chaque appel qu’un espace constant (pour stocker les variables et l’adresse de retour de la fonction). D’autre part, la profondeur totale des appels est n+ 1 pour l’exécution dechercheindex_rec_aux(t,x, n). Donc la complexité spatiale est enO(n).

Cet consommation d’espace est d’ailleurs uniquement de l’espace de pile.

On déduit les complexités temporelles et spatiales de chercheindex_rec(t,x)dans le cas le pire : il s’agit d’un O(n) dans les deux cas, ainsi que dans le cas de la complexité en espace de pile.

On constate que, si l’on cherche un élément absent d’un tableau de grande taille (plus de 1000 éléments) en utilisant chercheindex_rec(t,x), la pile déborde (message

«RecursionError : maximum recursion depth exceeded»). Ce qui n’est guère étonnant : on vient de voir que le nombre d’appels imbriqués pouvait aller jusqu’à n+ 1 pour un tableau de taille n.

Judicaël Courant - 20 septembre 2017 2/6 Document sous licence Art Libre (http://artlibre.org)

(3)

BR

OUILLON

Exercice 2 : Recherche dichotomique dans un tableau trié

Nous donnons, dans le listing ci-dessous une première implantation récursive dechercheindexord (t,x), appelée chercheindexord_rec(t,x). Plus précisément, cette fonction n’est pas

récursive mais fait appel à une fonction auxiliaire récursivechercheindexord_rec_aux (t,x,i,n)qui cherche la valeurxparmi lesnvaleurs du tableau commençant à l’indice i, c’est-à-dire parmit[i], . . . ,t[i+n−1].

Nous donnons ensuite une implantation impérative dechercheindexord(t,x), appelée chercheindexord_imp(t,x).

def chercheindexord_rec_aux(t, x, i, n) :

" " " Retourne un i n d i c e k t e l que t [ k ] == x e t i <= k < i+n s ’ i l en e x i s t e un ; s i n o n r e t o u r n e −1.

Pr é c o n d i t i o n : on s u p p o s e t t r i é p a r o r d r e c r o i s s a n t , 0 <= i <= i +n <= l e n ( t ) . " " "

i f n == 1 and t[i] == x: return i

e l i f n <= 1 :

# n == 0 ou ( n == 1 e t t [ i ] != x ) return −1

e l s e:

# n >= 2 n2 = n // 2 j = i + n2

i f x < t[j] :

# x n ’ e s t p a s dans t [ j : i+n ] ,

# on l e c h e r c h e donc dans t [ i : j ] :

return chercheindexord_rec_aux(t, x, i, n2) e l s e:

# x n ’ e s t p a s dans t [ i : j ] ,

# on l e c h e r c h e donc dans t [ j : i+n ] .

# t [ i : j ] c o n t i e n t n2 é l é ments , t [ i : i+n ] en c o n t i e n t n

# donc t [ j : i+n ] en c o n t i e n t n−n2

return chercheindexord_rec_aux(t, x, j, n n2)

def chercheindexord_rec(t, x) :

return chercheindexord_rec_aux(t, x, 0 , len(t) )

def chercheindexord_imp(t, x) : n = len(t)

i = 0

while n >= 2 :

# i n v a r i a n t : i e t n s o n t d e s e n t i e r s n a t u r e l s e t

# 0 <= i <= i + n <= l e n ( t )

# De p l u s , s i x a p p a r a î t dans t , i l a pp a r a î t au moins à un

# i n d i c e k t e l que i <= k < i + n .

# v a r i a n t : n n2 = n//2 j = i + n2

Judicaël Courant - 20 septembre 2017 3/6 Document sous licence Art Libre (http://artlibre.org)

(4)

BR

OUILLON

i f t[j] <= x:

# s i x e s t dans t , i l e s t dans t [ j : i+n ] .

# t [ i : j ] c o n t i e n t n2 é l é ments , t [ i : i+n ] en c o n t i e n t n

# donc t [ j : i+n ] en c o n t i e n t n−n2 i = j

n = n n2 e l s e:

# x n ’ e s t p a s dans t [ j : i+n ] ,

# on l e c h e r c h e donc dans t [ i : j ] : n = n2

# n <= 1 , 0 <= i <= i + n <= l e n ( t ) e t s i x a p p a r a î t dans t ,

# i l a p p a r a î t au moins à un i n d i c e k t e l que i <= k < i + n . i f n == 1 and t[i] == x: return i

e l s e: return −1

Justifions la correction de la fonction chercheindexord_rec_aux(t,x, i,n). Les va- leurs de t et x étant fixées, montrons par récurrence forte sur n que pour tout entieri tel que 0≤iet i+nlen(t), l’appel à chercheindexord_rec_aux(t,x, i,n)termine et retourne un indicek tel quet[k] =x etik < i+ns’il en existe un, et −1 sinon.

Le résultat est évident pour n= 0 : l’intervalle [[i, i+n[[ est alors vide et la fonction retourne bien−1.

Pourn= 1, en distinguant les cast[i] =xett[i]6=x, on voit également que la fonction retourne bien un résultat correct.

Soit alorsn≥2 tel que pour toutn0 < net tout entieri0tel que 0≤i0eti0+n0len(t), l’appel àchercheindexord_rec_aux(t,x, i’, n’)termine et retourne un indicektel que t[k] =x eti0k < i0+n0 s’il en existe un, et−1 sinon.

Soit alorsitel que 0≤ieti+nlen(t). Montrons que l’appel àchercheindexord_rec_aux (t,x,i,n)termine et retourne un indicektel quet[k] =x etik < i+ns’il en existe un, et−1 sinon. Commen≥2, on calculen2 =bn/2c. Remarquons tout d’abord qu’on a 0< n2 < n. En effet, d’une partn≥2 doncn/2≥1, doncbn/2c ≥ b1c= 1 et d’autre part,n2n=b−n/2c ≤ b−2/2c =−1<0, donc n2 < n. Donc en posant j =i+n2, on a en particulier iji+n <len(t), doncj est un indice valide dans le tableau t.

On a alors deux cas :

— Dans le cas oùx < t[j],xne peut être situé qu’à gauche dej(strictement) danst: pour chercher six apparaît à un indice k de [[i, i+n[[, il suffit en fait de chercher dans [[i, j[[, avec j = i+n2. Comme n2 < n, d’après l’hypothèse de récurrence, l’appel àchercheindexord_rec_aux(t,x,i,n2)retourne bien un tel indice s’il en existe un, et−1 sinon.

— Dans le cas où xt[j], x ne peut être situé qu’à droite de j (au sens large) dans t: pour chercher si x apparaît à un indice k de [[i, i+n[[, il suffit en fait de chercher dans [[j, i+n[[= [[j, j+ (n−n2)[[. Commen2 >0, on ann2 < n donc, d’après l’hypothèse de récurrence, l’appel à chercheindexord_rec_aux(t,x,j,n

n2)retourne bien un tel indice s’il en existe un, et−1 sinon.

On en déduit la correction de la fonction chercheindexord_rec_aux(t,x, i,n), puis celle dechercheindexord_rec(t,x).

Pour ce qui est de la correction de la fonctionchercheindexord_imp(t,x), montrons

Judicaël Courant - 20 septembre 2017 4/6 Document sous licence Art Libre (http://artlibre.org)

(5)

BR

OUILLON

tout d’abord la correction partielle de la fonction. Pour cela, montrons tout d’abord que l’invariant annoncé dans le code de la fonction est bien vérifié.

Il l’est lors de l’entrée dans la boucle, puisqu’alors on a 0 = ii+n = len(t). De plus, si xapparaît dans t, il apparaît au moins à un indice ktel que 0≤k < n.

Supposons maintenant qu’il est vérifié au début d’un tour de boucle, et montrons alors qu’il est vérifié à la fin de ce même tour :

— À la fin de ce tour, la valeur de la variable nest remplacée par la valeur debn/2c ou par celle den−bn/2c=dn/2eet la valeur deiest soit inchangée, soit remplacée par celle dei+bn/2c.ietn sont donc encore des entiers naturels à la fin du tour et on a bien 0≤ii+n.

— De plus, à la fin du tour, soit la valeur dei+nest celle dei+bn/2c+n− bn/2cau début du tour, c’est-à-dire qu’elle est inchangée, soit sa valeur est celle dei+bn/2c au début du tour. Dans les deux cas, puisqu’au début du tour on ai+nlen(t), c’est encore le cas à la fin du tour.

ietnsont bien des entiers naturels.

— Enfin, en notant j la valeur de i+bn/2c au début de ce tour, on peut remarquer que si x apparaît danst[i:n] au début du tour, il y a trois situations possibles :

x est égal à t[j] et il apparaît alors dans t[j : i+n] (et peut-être aussi dans t[i:j]).

— ou t[j] < x et, t étant trié, x apparaît nécessairement dans t[j+ 1 :i+n] et seulement là.

— oux < t[j] et, tétant trié, xapparaît nécessairement danst[i:j] et seulement là.

Dans chacun de ces trois cas, à la fin du tour,x apparaît dans t[i:n].

Donc si l’invariant est vérifié au début d’un tour de boucle, il est vérifié à la fin de ce même tour.

On en déduit que l’invariant est vrai à la fin du dernier tour de la boucle. De plus, à ce moment,n6≥2 puisqu’on est sorti de la boucle. Doncn≤1, si x apparaît dans t, il est présent dans t[i:i+n]. On alors deux possibilités :

— Ou bien xapparaît dans t, et alorsn= 1 ett[i] =x.

— Ou bien xn’apparaît pas dans tet alors n= 0 ou t[i]6=x.

La conditionnelle située à la fin de la fonction traite donc ces deux cas correctement.

Nous avons donc montré la correction partielle de la fonction chercheindexord_imp (t,x).

Montrons maintenant que la fonctionchercheindexord_imp(t,x)termine. Il suffit de montrer pour cela que la variablen, qui est un entier naturel, décroît strictement à chaque tour de boucle. La condition de boucle assure qu’on a bienn≥2 à chaque début de tour de la boucle. Or pourn≥2, on an/2≥1, doncbn/2c ≤ dn/2e ≤n− bn/2c ≤n−1< n.

La variable n étant affectée à la valeur qu’avait bn/2c ou n− bn/2c =dn/2e en début de boucle, on en déduit que la valeur de n décroît strictement lors de l’exécution d’un tour de boucle.

nest donc bien un variant de boucle, donc la boucle termine. La fonctionchercheindexord_imp (t,x)termine donc.

Judicaël Courant - 20 septembre 2017 5/6 Document sous licence Art Libre (http://artlibre.org)

(6)

BR

OUILLON

Or nous avons montré la correction partielle de la fonction chercheindexord_imp(t, x). Nous en déduisons donc la correction totale de cette fonction.

Pour étudier la complexité de ces deux fonctions, on remarque que pour tout entier naturelk non nul :

— Pour toutes valeurs de t, x, i, n telles que n ≤ 2k, l’évaluation du corps de la fonctionchercheindexord_rec_aux(t,x,i,n)conduit à un appel récursif sur une nouvelle valeur deninférieure ou égale à 2k−1.

— Pour toutes valeurs de t, x, i, n vérifiant l’invariant de boucle et telles que n ≤ 2k, l’exécution d’un tour de la boucle while remplace n par une nouvelle valeur inférieure ou égale à 2k−1.

Après avoir étudié le cas où n ≤ 1, on en déduit par récurrence que pour tout entier naturelk :

— Pour toutes valeurs de t, x, i, n telles que n ≤ 2k, l’évaluation de la fonction chercheindexord_rec_aux(t,x,i,n)conduit au plus àk+ 1 appels de cette fonc- tion (l’appel initial plus k appels récursifs). Or à chaque appel, on n’effectue que des opérations se faisant en temps constant, en dehors de l’éventuel appel récursif.

— Pour toutes valeurs det,x,i,nvérifiant l’invariant de boucle et telles que n≤2k, l’exécution de la bouclewhileeffectue au plus k+ 1 tours. Or à chaque appel, on n’effectue que des opérations se faisant en temps constant.

On en déduit alors que la complexité de la recherche d’une valeur dans un tableau de taille ns’effectue en temps O(dlog2ne) =O(logn).

Pour ce qui est de la complexité en espace, la version itérative s’exécute en espace constant.

Pour ce qui est de la version récursive, à chaque appel récursif, seul un espace constant est utilisé à chaque appel récursif (nouvelles variables prenant un espace constant et adresse de retour). Elle a donc une complexité en espaceO(logn).

Dans les deux cas, on constate expérimentalement que ces deux fonctions code, exécu- tées sur un tableau de taille 10000, retournent le bon résultat en un temps plus petit que ce qui est mesurable avec une montre, ce qui n’est guère étonnant : le nombre d’itéra- tions/d’appels à effectuer est au plus 1 +dlog210000e= 15. L’espace de pile consommé est donc celui de 15 appels, ce qui est largement au-dessous de l’espace de pile disponible en Python (de l’ordre de mille appels).

Judicaël Courant - 20 septembre 2017 6/6 Document sous licence Art Libre (http://artlibre.org)

Références

Documents relatifs

Initialement proposée pour rendre compte de la distribution des consonnes dans les racines trilitères ainsi que de la gémination au thème de l’inaccompli (Lahrouchi

le coût de l’exécution cout A (d ) = nombre d’exécution de chaque opération de l’algorithme A sur la donnée d. En général, on restreint le nombre d’opérations

Mettre en œuvre une démarche d'aménagement en prennant en compte la commande, les aspects. réglementaires

Le niveau de complexité renvoie à une diversité de savoirs à mobiliser en lien avec les capacités visées.. En aménagement paysager, il a été décidé de dissocier la démarche

Il d´emontre aussi que le processus d’inf´erence EXI-E est polynomial dans le cas de d´efauts unaires, normaux et sans pr´e-requis et avec un ensemble W ne contenant que des

La complexité d’un algorithme ne parle pas du temps de calcul absolu des implémentations de cet algorithme mais de la vitesse avec laquelle ce temps de calcul augmente quand la

On cherche à définir une notion de compléxité robuste : indépendante de l’ordinateur, du compilateur, du langage de programmation, etc... Exprimée en fonction de la Taille de

La complexité en temps d’une fonc- tion (donnée sous la forme d’un algorithme ou d’un programme P YTHON ) représente le nombre d’opérations « élémentaires » nécessaires