La Méthode Diviser et Régner

publicité
Chapitre 4 : La Méthode Diviser et Régner
1. Introduction La stratégie diviser-et-régner consiste :
a. à briser un problème en sous-problèmes plus simples de même type,
b. à trouver d’une manière indépendante la solution de chacun de ces sous-problèmes
c. ensuite à fusionner les résultats obtenus pour apporter une solution au problème posé.
Il s'agit donc d'une démarche essentiellement récursive. Les algorithmes de ce type apparaissent
comme composés de deux algorithmes; le premier partage le problème en sous-problèmes, le
second algorithme fusionne les résultats partiels en le résultat global.
Cette approche conduit, de façon naturelle (mais pas nécessairement), à un algorithme récursif.
Seulement, la question qui pourrait se poser est de savoir comment obtenir la solution des sousproblèmes. La réponse est d’appliquer la même approche diviser-pour-regner descendante et ce,
jusqu’à ce que le problème deviennent trivial.
Il est utile de signaler que pour que l’approche diviser-et-regner puisse conduire à un algorithme
récursif, il est nécessaire que les sous-problèmes résultants soient de même type au problème
initial. Si ce n’est pas le cas, on peut quand même considérer qu’on utilise une approche diviseret-régner, mais sans récursivité. Le graphe résultant de cette technique est comme suit :
1
Problème
P1
Sous-problème
P11
Sous-problème
P111
Sous-problème
P12
Sous-problème
P112
Sous-problème
P1k
Sous-problème
P1k1
Sous-problème
P1k2
Sous-solution
P1k
Sous-soulition
P11
Solution
problème P1
2
2. Déterminer le moment de cesser les appels recursifs
Dans la mise en oeuvre directe de la stratégie diviser-et-régner avec récursivité, on cesse les appels récursifs
(la décomposition en sous-problèmes) lorsque le problème à résoudre est vraiment trivial, c’est-à-dire qu’il ne
peut plus être décomposé ou qu’il n’est plus intéressant de le décomposer encore (cela est vrai pour les
problèmes typiquement de taille 1).
En théorie les surcoûts (overhead) associés à la gestion des appels récursifs (par exemple, l’allocation et copie
des arguments de la fonction dans la pile) peuvent être ignores. En pratique, ces surcoûts peuvent devenir
significatifs et il peut devenir plus efficace, à partir d’une certaine taille de problème, d’utiliser une approche
asymptotiquement moins efficace, mais avec un coefficient plus faible au niveau des surcoûts. La stratégie
diviser-et-régner avec seuil (avec coupure) devient donc, en gros, la suivante (en supposant une
décomposition dichotomique) :
L’algorithme générique de diviser-etrégner peut être résumé come suit :
Solution resoudre_dpr( Probleme p )
{
if (taille(p) <= TAILLE_MIN)
return( resoudre_algo_simple(p) ); // Solution non-récursive
else {
// décompser le problème p en sous-problèmes p1, p2, …, pk;
(p1, p2, …pk) = decomposer(p);
// Appels récursifs
solution s1 = resoudre_dpr(p1);
solution s2 = resoudre_dpr(p2);
// etc …
solution sk = resoudre_dpr(pk);
return( combiner(p, p1, p2,.., pk) );
}
La sélection de la valeur seuil (threshold) à partir de laquelle la récursivité doit se terminer
dépend de nombreux facteurs, comme par exemple :
• Mise en oeuvre exact de l’algorithme.
• Langage et compilateur utilises.
• Machine et système d’exploitation sur lesquels s’exécute le programme.
• Données sur lesquelles le programme est exécuté.
En d’autres mots, l’analyse théorique de l’algorithme ne suffit plus. On doit plutôt utiliser diverses techniques
et outils plus pratiques, par exemple, on pourrait exécuter le programme avec différentes données et mesurer
son temps d’exécution, utiliser l’outil profile (sur Unix/Linux) pour déterminer les fonctions et procédures les
plus fréquemment appelées et déterminer les points chauds du programme, etc.
3
3. Diviser et régner et la récursivité : Pour que l’approche diviser-et-régner avec récursivité
conduise à une solution efficace, il ne faut pas que l’une ou l’autre des conditions suivantes
survienne :
1. Un problème de taille n est décomposé en deux ou plusieurs sous-problèmes eux- même
de taille presque n (par exemple de taille n − 1).
2. Un problème de taille n est décomposé en n sous-problèmes de taille n/c (pour une
constante c ≥ 2). Rappelons aussi que l’approche diviser-et-régner avec recursivité ne
peut être utilisée que si les sous-problèmes sont de même type que le problème initial (par
exemple, le tri d’un tableau se fait en triant ses sous-tableaux). Toutefois, dans de
nombreux cas, on peut dire qu’une approche diviser-pour-regner est utilisée et ce, même
si aucune récursivité n’est nécessaire.
Dans le reste de ce chapitre, il sera question de l’application de cette méthode sur des exemples
divers afin de mieux l’apprécier et voir également comment approcher la résolution d’un
problème d’une manière récursive
4. Applications
4.1. Problème de recherche binaire : le problème de la recherche dichotomique consiste à
rechercher un élément dans un tableau de n éléments, procédant de la manière suivante :
1. on partitionne le tableau en deux parties égales
2. si l‘élément du milieu est celui qu’on cherche, on arrête la recherche
3. sinon, si l‘élément du milieu est supérieur à celui qu’on cherche, alors, comme le tableau
est déjà trié, ce dernier ne peut être que dans la partie gauche de l’élément du milieu
4. autrement l‘élément du milieu est inférieur supérieur à celui qu’on cherche. Dans ce cas,
ce dernier ne peut être que dans la partie droite de l’élément du milieu.
Le cas de base est celui où l’espace de recherche est nul.
Remarque : Nous avons discuté, dans le premier chapitre, une autre méthode pour résoudre ce
problème : la méthode séquentielle. Cette méthode ignore le fait que les éléments du tableau sont
déjà triées. Elle consiste donc à balayer le tableau du début à la fin jusqu’à éventuellement
trouver l’élément en question. Nous avons vu que la complexité de cette méthode, dans le pire
cas et cas moyen, est en O(n).
Implantation
int recherche(int *tab, int c,s,u){
if (sup < inf)
return(‘erreur’)
else {
4
milieu = (s-u+1) div 2;
if (c = = tab[milieu])
return(milieu);
else if (c < tab[milieu])
return( recherche(tab, c, milieu –1,u);
else return( recherché(tab,c,s,milieu+1);
}
Complexité: Voyons de plus prés le pire cas: si le paramètre n est pair, il y a respectivement n/2
éléments précédant et (n/2 –1) éléments suivant l’élément du milieu. En revanche, si n est impair,
il a (n-1)/2 éléments précédant et suivant l’élément du milieu. Autrement dit, dans le pire,
l’espace de recherche est réduit à chaque itération à n / 2 éléments.
Si t(n) représente la complexité , alors nous avons :
1; n = 1
t ( n) = 
t n / 2 + b, n > 1
On a vu au chapitre 2 que la solution de cette équation est :
t (n) = log n + b
Concernant la complexité en moyenne, en principe, nous l’avons discutée lors du chapitre 2 et
des travaux dirigés. Je vous suggère de la refaire tout maintenant.
4.2.Problèmes de tri
Dans cette section, deux méthode de tri sont présentées pour illustrer la technique de diviser-etrégner. Rappelons, avant, la définition du problème de tri.
Définition: Le problème de tri consiste à ordonner un ensemble d’éléments dans un ordre
croissant ou décroissant selon une clef propre à ces éléments muni d’un ordre total.
4.2.1. Le tri par fusion Cet algorithme fournit un exemple clair de cette stratégie. L'algorithme
fonctionne comme suit :
- on partitionne la liste en deux demi-listes;
- on trie chaque demi-liste;
- on fusionne les deux demi-listes triées.
Le cas de base de l'algorithme est celui où la liste à trier ne comporte qu'une clé.
L'écriture de l'algorithme dépend de la structure informatique choisie pour représenter les
données; nous supposons que l'on représente les listes par des tableaux. On pourrait tout aussi
bien utiliser des listes chaînées. Dans une première version qui est un pseudo-algorithme on
5
fusionne deux listes triées en une troisième. Ceci a l'inconvénient de créer des listes en pagaille et
par une transformation de programme on crée la liste fusionnée en place, moyennant l'utilisation
d'une liste auxiliaire. en pratique il suffit de pouvoir fusionner deux listes triées représentées par
des sous-intervalles d'un tableau, ces sous-intervalles étant contigus de la forme [i,j[ et [j,k[.
Remarque : Cet algorithme divise en deux parties égales le tableau de données en question. De
plus, la fusion doit tenir compte du fait que ces parties soient déjà triées.
Implantation
void mergesort(Elem* array, Elem* temp, int left, int right)
{
int i, j, k, mid = (left+right)/2;
if (left == right) return;
mergesort(array, temp, left, mid); // la première moitié
mergesort(array, temp, mid+1, right);// Sort 2nd half
// l’opération de fusion. Premièrement, copier les deux moitiés dans temp.
for (i=left; i<=mid; i++)
temp[i] = array[i];
for (j=1; j<=right-mid; j++)
temp[right-j+1] = array[j+mid];
// fusionner les deux moités dans array
for (i=left,j=right,k=left; k<=right; k++)
if (key(temp[i]) < key(temp[j]))
array[k] = temp[i++];
else array[k] = temp[j--];
}
6
Complexité: La complexité est donnée par la relation suivante:
n étant le nombre d’éléments dans le tableau.
Exercice: pourquoi les fonctions plancher et plafond comme paramètres dans T(.)?
Question : Peut-on parler des trois différentes complexités pour cet algorithme?
pour un
Dans le but de simplifier la résolution de l’équation 15.10, nous supposons que
entier k ≥ 0 . En remplaçant O(n) par n, on obtient (en principe, on doit la remplacer par cn):
7
La résolution par substitution donne ce qui suit :
:
La complexité temporelle de tri par fusion est donc en O(n log n) .
B. Le tri rapide
La stratégie de l’algorithme de tri rapide (quicksort) consiste, dans un premier temps, à diviser le
tableau en deux parties séparées par un élément (appelé pivot) de telle manière que les éléments
de la partie de gauche soient tous inférieurs ou égaux à cet élément et ceux de la partie de droite
soient tous supérieurs à ce pivot (dans l’algorithme donné ci-dessus, la partie qui effectue cette
tâche est appelée partition). Ensuite, d’une manière récursive, ce procédé est itéré sur les deux
parties ainsi crées. Notez que qu’au départ, le pivot est choisi dans la version de ci-dessus,
comme le dernier élément du tableau.
Implantation
void tri_rapide_bis(int tableau[],int debut,int fin){
if (debut<fin){
int pivot=partition(tableau,debut,fin);
tri_rapide_bis(tableau,debut,pivot-1);
tri_rapide_bis(tableau,pivot+1,fin);
}
}
void tri_rapide(int tableau[],int n)
{
tri_rapide_bis(tableau,0,n-1);
}
void echanger(int tab[], int i, int j) {
int memoire;
memoire=tab[i];
tab[i]=tab[j];
tab[j]=memoire;
}
8
int partition(int tableau[], int deb, int fin){
int pivot = tableau[fin];
int i = debut ; j = fin;
do{
do i++
while (tableau[i] < pivot)
do j-while (tableau[j] > pivot)
if (i < j)
echanger (tableau,i,j)
}
while(i < j)
tableau[deb] = a[j]; tableau[j] = pivot;
return(j)
}
Choix du pivot : Le choix idéal serait que ça coupe le tableau exactement en deux parties égales.
Mais cela n’est pas toujours possible. On peut prendre le premier élément. Mais il existe plusieurs
autres stratégies!
Partitionnement :
on parcourt le tableau de gauche à droite jusqu'à rencontrer un élément supérieur au pivot
on parcourt le tableau de droite à gauche jusqu'à rencontrer un élément inférieur au pivot
9
on échange ces deux éléments
et on recommence les parcours gauche-droite et droite-gauche jusqu'à a avoir :
il suffit alors de mettre le pivot à la frontière (par un échange)
Exemple
[26 5 37
1 61 11 59 15 48 19]
[11 5 19
1 15] 26 [59 61 48 37]
[ 1 5] 11 [19 15] 26 [59 61 48 37]
1 5 11 [19 15] 26 [59 61 48 37]
1 5 11 15 19 26 [59 61 48 37]
1 5 11 15 19 26 [48 37] 59 [61]
1 5 11 15 19 26 37 48 59 [61]
1 5 11
l5 l9 26 37 48 59
10
6l
Complexité
À l’appel de QSORT (1,n), le pivot se place en position i. Ceci nous laisse avec un problème de
tri de deux sous parties de taille i-1 et n-i.. L’algorithme de partition clairement a une complexité
au plus de cn pour une constante c.
T (n) = T (i − 1) + T (n − i ) + cn
T (1) = 1
Voyons les trois cas possibles de complexité:
Cas défavorable
Le pivot est à chaque fois le plus petit élément. La relation de récurrence devient
T(n) = T(n – 1) + cn (pourquoi ?)
T(n -1) = T(n - 2) + c(n - 1)
T(n - 2) = T(n - 3) + c(n - 2)
... …………….
T(2) = T(1) + c(2)
En ajoutant membre à membre, on obtient :
T(n) = O( n 2 )
11
Cas favorable
Dans le meilleur des cas, le pivot est, à chaque fois, situé au milieu de la parti à trier.
T(n) = 2T(n/2) + cn
Ce développement s’arrête dès qu’on atteint T(1). Autrement dit, dès que
n / 2k = 1
n = 2 k ⇒ k = log n
Solution finale: T(n) = O(n log n)
Question : déterminer la relation de récurrence exacte de la complexité dans ce cas de figure et
résoudre l’équation qui en résulte à l’aide de la méthode de l’équation secondaire?
Complexité dans le cas moyen
Nous savons que t (n) = t (i − 1) + t (n − i ) + cn (voir plus haut). Supposons que toutes les
permutations des éléments du tableau soient équiprobables. Autrement dit, la probabilité que la
case i soit choisie comme pivot est 1/n. Comme i peut varier entre 1 et n, alors on obtient
facilement la récurrence suivante:
t (n) = cn +
1 n
∑ t (i − 1) + t (n − i)
n i =1
12
Il n’est pas difficile de constater que cette équation ne peut être résolue par l’une des méthode
discutées au chapitre 2. Néanmoins, on peut contourner cette difficulté comme suit :
Comme
n
n
i =1
i =1
n
2
∑ t (i − 1) = ∑ t (n − i) , on obtient la relation: t (n) = cn + n ∑ t (i) qui peut s’écrire aussi:
i =1
n
(1)
nt (n) = cn 2 + 2∑ t (i )
i =1
qui est aussi vraie pour n+1. Autrement dit,
(2)
n +1
(n + 1)t (n + 1) = c(n + 1) 2 + 2∑ t (i )
i =1
en soustrayant (1) de (2), on obtient:
(n + 1)t (n + 1) − nt (n) = c(2n + 1) + 2t (n + 1)
(n − 1)t (n + 1) − nt (n) = c(2n + 1)
2
1
(2n + 1)
t (n + 1) t (n)
= c(
− )
−
=c
n(n − 1)
n −1 n
n
n −1
Cette équation est aussi vraie pour n, n-1, n-2, …, 4
t (n) t (n − 1)
2
1
−
)
= c(
−
n −1 n − 2
n − 2 n −1
t (n − 1) t (n − 2)
2
1
−
= c(
−
)
n−2
n−3
n−3 n−2
t (n − 2) t (n − 3)
2
1
−
= c(
−
)
n−3
n−4
n−4 n−3
………
t (3) t (2)
2 1
−
= c( − )
2
1
1 2
En additionnant membre à membre pour n, n-1,…., on obtient après simplifications:
t ( n ) t ( 2)
1
1 
 1
 
−
= c
+
+ ... + 1 + c1 −

n −1
1
n−2 n−3
  n −1
13
Or, on sait que :
log(n − 2) < 1 +
1
1
+ ... +
< log(n − 1)
2
n−2
on obtient donc :
1 

t (n) < c(n − 1) log(n − 1) + 1 −
 + t (2)
n −1

t(2) étant une constante, on obtient : t (n) = O(n log n) .
4.3. Multiplication de matrices
On sait que chaque élément de la matrice C = A × B de dimension n par n est donnée par
l’expression ci-dessous. Ce produit est une matrice de dimension n par n.
n
C ij = ∑ Aik × Bkj
k =1
Comme la matrice C possède donc n2 éléments, il est clair que le nombre de multiplications
engendrées en effectuant ce produit comme ci-dessus est en n3.
Maintenant, essayons de subdiviser la matrice A et B en sous matrices de dimension n/2 par n/2
comme suit :
.
Les éléments C11 , C12 , C 21 et C 22 , en utilisant la même formule que ci-dessus sont donnés
comme suit :
C11 = A11 × B11 + A12 × B21
C12 = A11 × B12 + A12 × B22
C 21 = A21 × B11 + A22 × B21
C 22 = A21 × B12 + A22 × B22
14
Même en procédant comme ci-dessus, en fait, rien ne change sur le plan de la complexité,
puisque le nombre de multiplications reste toujours en n 3 .
Exercice : montrer que la complexité temporelle de l’algorithme utilisant cette subdivision d,une
manière répétitive est en O( n 3 ).
Maintenant procédons d’une autre manière, en calculant avant les sept produit suivant.
Il est facile de montrer que nous avons bien
De cette manière, il est facile de constater que seule 7 multiplications de matrice de dimension
n/2 par n/2 sont utilisées au lieu de 8 comme c’est le cas dans la stratégies de ci-dessus.
L’algorithme ci-dessous utilise deux fonctions: addmat pour additionner deux matrices et submat
pour soustraire deux matrices. Il est clair que la soustraction et l’addition de deux matrice se fait
en O( n 2 ).
void funtion prodmat(*matrice A,B; int N) {
if N = 1
prodmat = A * B; // multiplication de deux nombres
else {
découper en 4 A la matrice A et 4 B la matrice B;
P1 = prodmat(A11, submat(B12,B22,N/2);
P2 = prodmat(addmat(A11,A12,N/2), B22,N/2);
……………..
P7 = prodmat(submat(A11,A21,N/2),addmat(B11,B12,N/2),N/2);
C11 = submat(Addmat(Addmat(P4,P5,N/2),P6,N/2),P1,N/2);
…….. …………..
………………….
C22 = submat(Addmat(Addmat(P1,P5,N/2),P7,N/2),P3,N/2);
}
}
15
Complexité de l’algorithme : la complexité de l’algorithme utilisant ce procédé de calculs est
n
log 7
log 2
= n 2.81
Preuve : Soit t(n) la complexité de la fonction prodmat. Cette fontion fait 7 appels à elle-même,
mais en dimension n/2; et 18 appels à addmat et submat en dimension n/2. Le fait d’appeler ellemême en dimension n/2 correspond à t(n/2). Comme on l’a remarqué ci-dessous, la complexité
d’une addition/soustraction de matrice se fait en O( (n / 2) 2 ). Autrement dit, t(n) est donné par la
relation suivante :
b; n = 1
t ( n) = 
2
7t (n / 2) + an , n > 1
Résolvant cette équation par l’une des méthodes du chapitre 2, en supposant que n est une
puissance de 2, on obtient :
t (n) = O(7 log 2 n ) = O(n log 2 7 ) = O(n 2.81 )
4.4. Multiplication de grand nombres
Alors que la multiplication élémentaire ne coûte guère plus chère qu’une addition dans la plupart
des ordinateurs, il n’en est plus de même lorsque les opérandes deviennent de plus en plus
grands. Il n’est pas difficile de voir que l’algorithme classique (qu’on nous appris à l’école
primaire) se fait en temps quadratique. Il en est de même de l’algorithme de multiplication à la
russe (documentez-vous sur cet algorithme pour plus de détails). Dans cette section, nous allons
voir qu’il est possible en effet de faire mieux que cette complexité.
Avant de procéder, prenons l’exemple suivant pour illustrer la méthode de diviser-et-régner que
nous présentons plus loin:
Proposons-nous de faire la multiplication de 981 par 1234. Avant, mettons à la même grandeur
ces deux nombres, soit : 0981 et 1234. Ensuite, on divise ces nombres en deux parties. Ainsi :
0981 va donner w = 09 et x = 81
1234 va donner y = 12 et z = 34
Par conséquent, le produit est calculé comme suit :
981 × 1234 = (10 2 w + x)(10 2 y + z )
981 × 1234 = 10 4 wy + 10 2 ( wz + xy ) + xz
981 × 1234 = 1080000 + 127800 + 2754 = 1210554
16
Comme on peut le constater, nous avons effectuer 4 multiplications de parties, soit: wy, wz, xy et
xz.
Considérons maintenant le produit suivant :
r = ( w + x) × ( y + z ) = wy + ( wz + xy) + xz
Après une seule multiplication des parties, on obtient la somme de toutes les parties pour calculer
notre produit. Cela nous amène au calcul suivant :
p = wy = 09 × 12 = 108
q = xz = 81 × 34 = 2754
r = ( w + x) × ( y + z ) = 90 × 46 = 4140
Et finalement, on a ce qui suit :
981 × 1234 = 10 4 p + 10 2 (r − p − q) + q
981 × 1234 = 1080000 + 127800 + 2754.
Autrement dit, la multiplication de 981 par 1234 peut être réduite à 3 multiplications de deux
nombres ( 09 × 12 , 81× 34 et 90 × 46 ) ensemble avec des un certain nombre de décalages
(puissance de 10), des additions et des soustractions.
Il est utile de signaler que pour les grands nombres, le coût des additions, soustractions et
décalages devient négligeable devant celui d’une multiplication. Il apparaît donc la réduction de
quatre multiplications à trois multiplications nous permet de gagner 25% de temps nécessaire à la
multiplications de grands nombres.
L’algorithme diviser-et-régner suggère donc de séparer chacun des deux nombres u et v à
multiplier en deux section aussi égales que possible : u = 10 s w + x et v = 10 s y + z. Bien entendu,
n
nous avons bien 0 ≤ u , z < 10 s et s =  
2
Le produit qui nous intéresse est donc :
u × v = 10 2 s wy + 10 s ( wz + xy ) + xy
Bien entendu, comme on l’a vu plus haut, si on ne fait attention, on obtient la fonction suivante :
long int function multip(int u,v){
n = plus petit entier telle que u et v soient de taille n
17
si n petit alors
{
multiplier u et v par l’algorithme classique
return le résultat
}
sinon
{
s = n div 2;
w = u div 10s; x = u % 10s;
y = v div 10s; z = v % 10s;
return(multip(w,y) × 102s + (multip( w,z)+multip(x,y))) × 10s +multip(x,z))
}
} //fin de la fonction
Si on procède comme cela, il n’est pas difficile de montrer que la complexité de cette fonction est
O(n2).
L’astuce nous permettant d’aller plus rapidement consiste, comme nous l’avons vu
précédemment, de calculer wy, wz + xy et xy en faisant moins de 4 multiplications de demilongueur, quitte à faire plus d’additions/soustractions.
Soit donc le produit :
r = ( w + x) × ( y + z ) = wy + ( wz + xy) + xz
Cette astuce suggère donc la fonction suivante :
long int function multip(int u,v)
{
n = plus petit entier telle que u et v soient de taille n
si n petit alors
{
multiplier u et v par l’algorithme classique
return le résultat
}
sinon
{
s = n div 2;
w = u div 10s; x = u % 10s;
y = v div 10s; z = v % 10s;
q = multip(x,z);
r = multip(w+x,y+z);
p = multip(x,z);
return(p × 102s + (r – p-q) × 10s +q
}
} //fin de la fonction
18
Complexité de cette fonction : on fera ce calcul en classe !!! on doit trouver la solution :
t (n) = O(n log 3 ) = O(n1.59 )
4.5. Problème du maximum et minimum
Le problème consiste à déterminer le minimum et le maximum d’un tableau de n éléments.
Supposons que le nombre de comparaisons entre les éléments du tableau est pris comme unité
pour mesurer la complexité d’un algorithme. Une solution facile consiste à balayer tous les
éléments pour trouver le minimum et une deuxième fois pour trouver le maximum. À chacune
des deux fois, il est facile de voir que (n-1) comparaisons sont effectuées. Par conséquent, cet
algorithme va induira 2(n-1) = 2n-2 comparaisons.
L’approche diviser-et-régner pour ce problème consiste à diviser le tableau en deux parties plus
de n/2 éléments (nous pouvons supposer que n est une puissance de 2). L’algorithme va alors
trouver le maximum et le minimum pour chacune des deux parties, d’une manière récursive.
Ensuite, une fois cette partie terminée, on détermine le minimum et le maximum global en
comparant entre eux les deux maximums et les deux minimums ainsi trouvés.
void minimax(tab S; int i,j,max,min) \{
int min1,min1, max1, max2, milieu;
if (i-j = 0) { // un seul élément
min = S[i];
max = S[i];
}
else if (i-j ==1) {// deux elements
if S[i] < S[j] {
min = S[i];
max = S[j]; }
else { min = S[j];
max = S[i]; }
} // fin du if
else {
milieu = (i+j-1) div 2;
minimax(S,i,milieu,min1,max1);
minimax(S,milieu+1,j,min2,max2);
if min1 > min2
min = min2;
else min = min1;
if max1 < max2
max = max2;
else max = max1;
19
}
t(n), le nombre de comparaisons l’algorithme ci-dessus, est alors comme suit :
1; n = 2
t ( n) = 
2t (n / 2) + 2, n > 2
On obtient alors la solution suivante:
t (n) = 3n / 2 − 2 .
Exercice: obtenez la relation de récurrence exacte de la fonction ci-dessus. Ensuite, résoudre
cette équation à l’aide de la méthode d’induction.
20
Téléchargement