29.10.2015 Views

Lycée Thiers mpsi 123

Correction - MPSI-3 - Accueil

Correction - MPSI-3 - Accueil

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Lycée</strong> <strong>Thiers</strong><br />

<strong>mpsi</strong> <strong>123</strong><br />

CORRECTION DS 2<br />

Exercice 1 - Diviser pour mieux régner ?<br />

1) Calcul de la somme des termes d’une liste d’entiers :<br />

let sum l = aux 0 l where<br />

rec aux sp = function (* sp pour Somme Partielle *)<br />

| [] -> sp<br />

| h::t -> aux (h+sp) t<br />

;;<br />

La fonction aux est récursive terminale, comme demandé.<br />

2) Partage d’une liste en deux demi-listes :<br />

let rec partage = function<br />

| [] -> [],[]<br />

| [x] -> [x],[]<br />

| x::y::r -> let (g,d) = partage r in (x::g,y::d)<br />

;;<br />

3) Si la liste est vide, la somme est nulle (par convention). Si la liste est réduite à un élément, la somme<br />

est donnée par cet élément. Enfin, si la liste est de longueur 2, on la partage en deux demi-listes (avec<br />

la fonction partage ci-dessus), dont on calcule récursivement la somme des termes, puis on ajoute les<br />

résultats.<br />

4) Calcul de la somme des termes d’une liste d’entiers, en divisant pour régner :<br />

let rec sumbis = function<br />

| [] -> 0<br />

| [x] -> x<br />

| l -> let (g,d) = partage l in (sumbis g) + (sumbis d)<br />

;;<br />

5) Montrons par récurrence forte que, pour tout n 1 :<br />

T (n) = n − 1<br />

C’est le cas pour n = 1. Supposons le résultat établi aux rangs inférieurs à n, pour un certain n 2. Deux<br />

cas se présentent ...<br />

Si n est pair, alors :<br />

et si n est impair, alors :<br />

( ) n − 1<br />

T (n) = T + T<br />

2<br />

L’hérédité est établie.<br />

( ) ( )<br />

n n<br />

T (n) = 2T + 1 = 2<br />

2 2 − 1 + 1 = n − 1<br />

( n + 1<br />

2<br />

) ( ) ( )<br />

n − 1 n + 1<br />

+ 1 = − 1 + − 1 + 1 = n − 1<br />

2<br />

2<br />

6) Si l’on note T (n) le nombre d’additions engendrées par l’appel sumbis l, lorsque l est une liste de longueur<br />

n, on a T (0) = T (1) = 0 et, pour tout n 2 :<br />

(⌊ ⌋) (⌈ ⌉)<br />

n n<br />

T (n) = T + T + 1<br />

2 2<br />

D’après le 5°), cette méthode dichotomique n’est donc pas meilleure que la méthode “naïve” de la question<br />

1°). On peut même affirmer qu’elle est moins performante, car la mise en œuvre de la récursion possède<br />

un coût ! La preuve en chiffres :


CORRECTION DS 2 2<br />

#open "sys";;<br />

let rec jusque = function<br />

| 0 -> []<br />

| n -> n :: (jusque (n - 1))<br />

;;<br />

let L = jusque 100000 in<br />

let t0 = time() in<br />

let s0 = sum L in<br />

let t1 = time() in<br />

let s1 = sumbis L in<br />

let t2 = time() in<br />

(t1 -. t0, t2 -. t1)<br />

;;<br />

- : float * float = 0.0, 0.533333333333<br />

Exercice 2 - Vecteurs “presque” triés<br />

1) Le tri d’un vecteur peut modifier physiquement le vecteur en question (sa structure initiale est perdue) ou<br />

bien s’effectuer sur une copie de celui-ci. Dans le premier cas, on parle de tri “en place” et dans le second,<br />

de tri “hors place”. Le principal avantage d’un tri en-place est évidemment l’économie de mémoire.<br />

2) Le tri par fusion est un tri récursif par comparaison. Une séquence vide ou de longueur 1 est déjà triée.<br />

Pour une séquence de longueur n 2, on partage celle-ci en deux séquences de longueur moitié (plus<br />

précisément, l’une de longueur ⌊ ⌋ ⌈ ⌉<br />

n<br />

2 et l’autre de longueur n<br />

2 ), ces deux séquences sont ensuite triées<br />

récursivement, et leurs versions triées sont alors fusionnées.<br />

La mise en œuvre de cet algorithme repose donc sur l’écriture de trois fonctions : la première partage<br />

une séquence quelconque en deux moitiés, la seconde fusionne deux séquences triées en une seule, la<br />

troisième effectue le tri proprement dit en faisant appel aux deux précédentes.<br />

La complexité (en nombre de comparaisons) est “quasi-linéaire” : elle vérifie T (n) = Θ (n ln (n)) .<br />

3) En mettant côte à côte le vecteur v et sa version triée v ′ :<br />

v : 3 1 2 0 5 4 9 8 7 6<br />

v ′ : 0 1 2 3 4 5 6 7 8 9<br />

on constate que la distance maximum entre un terme de v et le terme de même valeur dans v ′ est d = 3.<br />

Ainsi, le vecteur v est 3−quasi-trié. Il n’est pas 2−quasi-trié puisque, par exemple : v. (0) = v ′ . (3) .<br />

4) Dans la définition proposée, le problème vient du fait que plusieurs termes du vecteur peuvent être égaux.<br />

Il faut donc plutôt considérer qu’un vecteur v est d−quasi-trié lorsqu’en notant v ′ sa version triée :<br />

∀i ∈ {0, · · · , n − 1} , ∃j ∈ {0, · · · , n − 1} ; ( v. (i) = v ′ . ( j ) et ∣ ∣ ∣i − j<br />

∣ ∣∣ d<br />

)<br />

5) Une idée est de trier successivement (par fusion) les sous-vecteurs v [0 : 2d[ , v [d : 3d[ , etc ... en terminant<br />

éventuellement avec v [kd : n[ (si n n’est pas multiple de d et en posant k = ⌊ n<br />

d<br />

⌋<br />

).<br />

Lorsque v [0 : 2d[ est trié, on est sûr que chaque terme v [0 : d[ a trouvé sa place définitive.<br />

Puis, lorsque v [d : 3d[ est trié, même chose pour v [d : 2d[ et ainsi de suite.<br />

On effectue donc Θ ( n<br />

d<br />

)<br />

tris par fusion de vecteurs de longueur 2d (sauf éventuellement un dernier vecteur<br />

plus court) et le nombre total de comparaisons est donc Θ (n ln (d)) .


CORRECTION DS 2 3<br />

Exercice 3 - Suite de Conway<br />

1) Fonction separe :<br />

Type : ’a list → int * ’a * ’a list<br />

En entrée : un liste non vide h::...::h::y (h répété n fois)<br />

En sortie : le triplet n,h,y<br />

let separe = function<br />

| [] -> failwith "liste vide"<br />

| h::t -> aux 1 t where<br />

rec aux count = function<br />

| [] -> (count, h, [])<br />

| head::tail -> if h = head then aux (count + 1) tail<br />

else (count, h, head::tail)<br />

;;<br />

Exemple :<br />

separe [1;1;1;2;2;3];;<br />

- : int * int * int list = 3, 1, [2; 2; 3]<br />

2) Fonction lire :<br />

Type : int list → int list<br />

En entrée : une liste d’entiers l<br />

En sortie : la liste obtenue en “lisant” l<br />

let rec lire = function<br />

| [] -> []<br />

| l -> let (x,n,t) = separe l in x :: n :: lire t<br />

;;<br />

Exemple :<br />

lire [1;1;1;2;2;3];;<br />

- : int list = [3; 1; 2; 2; 1; 3]<br />

3) Fonction conway :<br />

Type : int → int list<br />

En entrée : un entier n 0<br />

En sortie : le n−ème terme de la suite de Conway, sous la forme d’une liste de chiffres<br />

let rec conway = function<br />

| 0 -> [1]<br />

| n -> lire (conway (n-1))<br />

;;<br />

Exemple :<br />

conway 5;;<br />

- : int list = [3; 1; 2; 2; 1; 1]<br />

Remarque. Cette suite possède de curieuses propriétés. Pour en savoir plus, on pourra consulter la page web<br />

http://fr.wikipedia.org/wiki/Suite_de_Conway


CORRECTION DS 2 4<br />

Exercice 4 - Somme maximale d’un sous-vecteur<br />

1) D’évidence, si tous les termes de v sont positifs ou nuls, alors σ ⋆ (v) est donné par σ (v) .<br />

2)<br />

2.a) Pour tout couple ( i, j ) tel que 0 i j n − 1, on doit calculer la somme<br />

j − i additions. Au total :<br />

⎛ ⎞ ⎛ ⎞<br />

∑n−1<br />

∑n−1<br />

∑n−1<br />

n−1−i ∑<br />

T (n) = j − i ⎜⎝ ⎟⎠ = ⎜⎝<br />

k⎟⎠ = 1 ∑n−1<br />

(n − 1 − i) (n − i) = 1 2<br />

2<br />

i=0<br />

j=i<br />

i=0<br />

k=0<br />

i=0<br />

j∑<br />

v. (k), ce qui représente<br />

k=i<br />

n∑<br />

k (k − 1)<br />

Comme k (k − 1) = 1 3<br />

[(k + 1) k (k − 1) − k (k − 1) (k − 2)] , on obtient (sommation télescopique) :<br />

Il est alors clair que T (n) = Θ ( n 3) .<br />

2.b) Implémentation de la méthode 0 :<br />

let mssp_0 v =<br />

let n = vect_length v in<br />

let left = ref 0 in<br />

let right = ref 0 in<br />

let smax = ref v.(0) in<br />

for i = 0 to n - 1 do<br />

for j = i to n - 1 do<br />

let s = ref 0 in<br />

for k = i to j do<br />

s := !s + v.(k)<br />

done;<br />

if !s > !smax then (<br />

left := i;<br />

right := j;<br />

smax := !s<br />

)<br />

done<br />

done;<br />

(!smax, !left, !right)<br />

;;<br />

T (n) = 1 (n + 1) n (n − 1)<br />

6<br />

3) Pour chaque i ∈ {0, · · · , n − 1} , l’algorithme ci-dessus calcule toutes les sommes v. (i) , v. (i) + v. (i + 1) , etc ...<br />

v. (i) + · · · + v. (n − 1) . De nombreuses additions sont donc effectuées plusieurs. On peut donc améliorer les<br />

choses ainsi :<br />

let mssp_1 v =<br />

let n = vect_length v in<br />

let left = ref 0 in<br />

let right = ref 0 in<br />

let smax = ref v.(0) in<br />

for i = 0 to n - 1 do<br />

let s = ref 0 in<br />

for j = i to n - 1 do<br />

s := !s + v.(j);<br />

if !s > !smax then (<br />

left := i;<br />

right := j;<br />

smax := !s<br />

)<br />

done<br />

done;<br />

(!smax, !left, !right)<br />

;;<br />

k=1


CORRECTION DS 2 5<br />

4)<br />

A présent, le nombre d’additions effectuées est :<br />

∑n−1<br />

∑n−1<br />

T (n) = (n − 1 − i) = k =<br />

et la complexité est donc passé à T (n) = Θ ( n 2) .<br />

4.a) On a s 0 = v. (0) et, pour :<br />

Il est par ailleurs clair que :<br />

i=0<br />

k=0<br />

⎧<br />

v. ( j ) si s j−1 < 0<br />

⎪⎨<br />

s j =<br />

⎪⎩ s j−1 + v. ( j ) sinon<br />

σ ⋆ (v) = max {s 0 , · · · , s n−1 }<br />

n (n − 1)<br />

2<br />

4.b) Ce qui précède montre qu’on peut calculer σ ⋆ (v) comme suit :<br />

let mssp_2 v =<br />

let n = vect_length v in<br />

let max_local = ref v.(0) in<br />

let max_global = ref v.(0) in<br />

for j = 1 to n - 1 do<br />

if !max_local < 0<br />

then max_local := v.(j)<br />

else max_local := !max_local + v.(j);<br />

;;<br />

if !max_local > !max_global then max_global := !max_local;<br />

done;<br />

!max_global<br />

Dans la fonction ci-dessus, max_global est une référence sur ce qui sera au final la plus grande somme,<br />

c’est-à-dire σ ⋆ (v) . Tandis que max_local est une référence sur la plus grande somme parmi celles qui<br />

se terminent par le terme d’indice courant (à savoir j).<br />

On constate que la complexité est à présent O (n) ; en effet, on compte une seule boucle de longueur<br />

n − 1 et au plus une addition par tour de boucle.<br />

On peut étoffer cette fonction pour qu’elle calcule aussi, comme les deux précédentes, les indices<br />

extrêmes d’un sous-vecteur qui réalise la somme maximale :<br />

let mssp_lin v =<br />

let n = vect_length v in<br />

let left_local = ref 0 in<br />

let left_global = ref 0 in<br />

let right = ref 0 in<br />

let max_local = ref v.(0) in<br />

let max_global = ref v.(0) in<br />

for j = 1 to n - 1 do<br />

if !max_local < 0 then (<br />

max_local := v.(j);<br />

left_local := j<br />

)<br />

else max_local := !max_local + v.(j);<br />

;;<br />

if !max_local > !max_global then (<br />

max_global := !max_local;<br />

left_global := !left_local;<br />

right := j<br />

)<br />

done;<br />

(!max_global, !left_global, !right)


CORRECTION DS 2 6<br />

5)<br />

5.a) Le principe “diviser pour régner” consiste, étant donné un problème de taille n, à le fractionner en un<br />

certain nombre a de problèmes de taille n/b.<br />

Ces derniers sont traités récursivement et leurs solutions respectives sont alors assemblées d’une<br />

manière appropriée, pour produire une solution au problème posé.<br />

5.b) Pour résoudre le MSSP via le principe diviser pour régner, on a besoin d’une fonction qui détermine<br />

la somme maximale pour les vecteurs “à cheval”.<br />

Si l’on note a, b les indices de début et de fin et i l’indice intermédiaire, il est clair que σ (v [a : i])<br />

doit être maximale parmi les sommes de sous-vecteurs qui se terminent avec le terme d’indice i et que<br />

σ (v [i : b]) doit être maximale parmi les sommes de sous-vecteurs qui démarrent avec ce même terme.<br />

En en déduit le code suivant :<br />

let mss_a_cheval v debut interm fin =<br />

let left = ref interm in<br />

let sLeftMax = ref 0 in<br />

let s = ref 0 in<br />

for i = interm downto debut do<br />

s := !s + v.(i);<br />

if !s > !sLeftMax then (<br />

left := i;<br />

sLeftMax := !s<br />

)<br />

done;<br />

let right = ref interm in<br />

let sRightMax = ref 0 in<br />

s := 0;<br />

for i = interm + 1 to fin do<br />

s := !s + v.(i);<br />

if !s > !sRightMax then (<br />

right := i;<br />

sRightMax := !s<br />

)<br />

done;<br />

(! sLeftMax + !sRightMax, !left, !right)<br />

;;


CORRECTION DS 2 7<br />

Ensuite, la fonction de résolution proprement dite :<br />

let mss_dpr v =<br />

let n = vect_length v in mss_aux 0 (n - 1) where<br />

rec mss_aux a b =<br />

if a = b then (v.(a), a, a)<br />

else if b = a + 1 then let s = v.(a) + v.(b) in (<br />

if v.(a) > v.(b) then (<br />

if v.(a) > v.(a) + v.(b) then (v.(a), a, a)<br />

else (s, a, b)<br />

)<br />

else (<br />

if v.(b) > v.(a) + v.(b) then (v.(b), b, b)<br />

else (s, a, b)<br />

)<br />

)<br />

else let m = (a + b) / 2 in<br />

let (smax0, left_0, right_0) = mss_aux a m in<br />

let (smax1, left_1, right_1) = mss_aux m b in<br />

let (smax01, left_01, right_01) = mss_a_cheval v a m b in<br />

if smax0 > smax1 then (<br />

if smax01 > smax0 then (smax01, left_01, right_01)<br />

else (smax0, left_0, right_0)<br />

)<br />

else (<br />

if smax01 > smax1 then (smax01, left_01, right_01)<br />

else (smax1, left_1, right_1)<br />

)<br />

;;<br />

5.c) On a la relation de récurrence suivante :<br />

(⌊ ⌋) (⌈ ⌉)<br />

n n<br />

T (n) = T + T + O (n)<br />

2 2<br />

d’où, d’après le second cas du Master Theorem :<br />

T (n) = Θ (n ln (n))<br />

Cet algorithme est quasi-linéaire. Il est donc moins performant que celui vu à la question 4° (qui est<br />

linéaire et optimal : il est connu sous le nom d’algorithme de Kadane).

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!