Chapitre 2 : Analyse des algorithmes

Transcription

Chapitre 2 : Analyse des algorithmes
Chapitre 2
Analyse des algorithmes
Analyser un algorithme consiste à calculer les ressources qui seront nécessaires
à son exécution. Mais avant cela, il faut d’abord s’assurer que l’algorithme
est correct, c’est-à-dire qu’il calcule bien ce pourquoi il a été écrit.
2.1
Preuves de la correction d’un algorithme
Comment s’assurer qu’un algorithme calcule bien ce qu’il est censé calculer ? Comment s’assurer qu’un algorithme termine quelle que soit les données
qui lui seront fournies en entrée ? Les algorithmes sont suffisamment formalisés pour qu’on puisse prouver qu’ils possèdent les propriétés attendues de
correction ou de terminaison. Les preuves sont facilitées lorsque les algorithmes sont bien écrits, et en particulier s’ils sont décomposés en de nombreux sous algorithmes courts et bien spécifiés.
Ce sont bien évidemment les structures itératives et les appels récursifs
qui nécessitent le plus de travail.
2.1.1
Preuve de la correction d’une structure itérative
Considérons le schéma d’algorithme suivant :
7
8
CHAPITRE 2. ANALYSE DES ALGORITHMES
Algorithme 4: Structure itérative générique
résultat : R
début
Initialisation
# Point A
tant que Condition faire
# Point B1
Instructions
# Point B2
fintq
# Point C
fin
Un invariant de boucle est une propriété ou une formule logique,
— qui est vérifiée après la phase d’initialisation,
— qui reste vraie après l’exécution d’une itération,
— et qui, conjointement à la condition d’arrêt, permet de montrer que
le résultat attendu est bien celui qui est calculé.
Exemple 1 Pour l’algorithme 2 du chapitre précédent, qui insère un
élément x dans un tableau trié T [1, n], considérons la propriété I suivante :
la juxtaposition des tableaux T [1, i] et T [i + 2, n + 1] 1 est égale
au tableau initial et x est plus petit que tous les éléments du
deuxième tableau 2 .
⌧
1. La propriété est vérifiée après l’initialisation (point A) puisque T [1, i]
est égal au tableau initial (i = n), et que le second tableau T [i+2, n+
1] est vide.
2. Si la propriété I est vraie au début d’une itération (point B1), elle est
vraie à la fin de cette itération (point B2) puisque le dernier élément
T [i] du premier tableau passe en tête du second (l’ordre n’est pas
modifié), et puisque l’on a x < T [i] et que l’indice est modifié en
conséquence.
Si la condition reste vraie, alors la propriété est donc vérifiée au début
de l’itération suivante.
3. Si la condition est fausse (point C), on peut montrer que
x est
à tous les éléments du premier tableau.
En e↵et,
1. si m > n, T [m, n] désigne un tableau vide.
2. propriété vraie si le deuxième tableau est vide.
2.1. PREUVES DE LA CORRECTION D’UN ALGORITHME
9
— c’est vrai si i = 0, car le premier tableau est vide,
— c’est vrai si i > 0 et x T [i], car le tableau T [1, i] est trié.
Comme l’invariant est vrai, x est à la fois
à tous les éléments de
T [1, i] et < à tous les éléments de T [i + 2, n + 1]. Il suffit donc d’écrire
T [i + 1] = x pour obtenir le résultat attendu.
Remarque : Pour prouver la correction d’une boucle Pour, on peut
remarquer que
Pour i:=1 à n faire
Instructions
équivaut à
i:=1
Tant que i<= n faire
Instructions
i:=i+1
et utiliser la technique précédente.
Exemple 2 Pour prouver la correction de l’algorithme 1, on considère
l’invariant de boucle suivant :
⌧
T [1, j
1] est trié
.
1. Après l’initialisation, la propriété est vraie puisque j = 2 et que le
tableau T [1, j 1] ne contient qu’un seul élément.
2. Supposons que T [1, j 1] soit trié au début d’une itération. Après appel de l’algorithme 2, le tableau T [1, j] est trié. Après l’incrémentation
de j, le tableau T [1, j 1] est trié.
3. Si la condition devient fausse, c’est que j = n + 1. Le tableau T [1, n]
est donc trié.
2.1.2
Preuve de correction d’un algorithme récursif
On prouve la correction d’un algorithme récursif au moyen d’un raisonnement par récurrence.
Exemple 3 Pour prouver la correction de l’algorithme 3, on e↵ectue une
récurrence sur le nombre N = n m + 1 d’éléments du tableau.
— si N = 1, alors m = n et on vérifie que l’algorithme est correct dans
ce cas ;
10
CHAPITRE 2. ANALYSE DES ALGORITHMES
— soit N
1. Supposons que le programme soit correct pour tout
tableau de longueur  N et considérons un tableau T [m, n] de N + 1
éléments : n m + 1 = N + 1. En particulier, m 6= n puisque N 1.
Soit k = b m+n
2 c. On a m  k < n. En e↵et,
m<n)m<
m+n
m+n
<n)mb
c = k < n.
2
2
Donc, les deux tableaux passés en argument des appels récursifs ont
au plus N éléments. En e↵et, le premier a k m + 1 éléments et
k m + 1  n m  N ; le second a n k éléments et n k 
n m  N . Par hypothèse de récurrence, quelque soit le résultat du
test T [k] < x, l’algorithme donnera une réponse correcte.
2.1.3
Exercices
1. Recherche du plus grand élément d’un tableau (version itérative) :
Algorithme 5: rechercheMaxIter
entrée : T [1, n] est un tableau d’entiers.
sortie : le plus grand élément de T [1, n].
début
M ax := T [1]
pour i := 2 à n faire
si T [i] > M ax alors
M ax := T [i]
retourner M ax
(a) Remplacez la structure itérative Pour par un Tant que.
(b) Trouvez l’invariant de boucle.
(c) Prouvez la correction de l’algorithme.
2. Recherche du plus grand élément d’un tableau (version récursive) :
Algorithme 6: rechercheMaxRec
entrée : T [1, n] est un tableau d’entiers.
sortie : le plus grand élément de T [1, n].
début
si n = 1 alors
retourner T [1]
sinon
retourner max(T [n], M axRec(T [1, n
1]))
2.2. COMPLEXITÉ DES ALGORITHMES
11
(a) Prouvez la correction de l’algorithme par récurrence sur le nombre
d’éléments du tableau.
2.2
Complexité des algorithmes
En plus d’être correct, c’est-à-dire d’e↵ectuer le calcul attendu, on demande à un algorithme d’être efficace, l’efficacité étant mesurée par le temps
que prend l’exécution de l’algorithme ou plus généralement, par les quantités de ressources nécessaires à son exécution (temps d’exécution, espace
mémoire, bande passante, . . . ).
Le temps d’exécution d’un algorithme dépend évidemment de la taille
des données fournies en entrée, ne serait-ce que parce qu’il faut les lire avant
de les traiter. La taille d’une donnée est mesurée par le nombre de bits
nécessaires à sa représentation en machine. Cette mesure ne dépend pas du
matériel sur lequel l’algorithme sera programmé.
Mais comment mesurer le temps d’exécution d’un algorithme puisque
les temps de calcul de deux implémentations du même algorithme sur deux
machines di↵érentes peuvent être très di↵érents ? En fait, quelle que soit la
machine sur laquelle un algorithme est implémenté, le temps de calcul du
programme correspondant est égal au nombre d’opérations élémentaires effectuées par l’algorithme, à un facteur multiplicatif constant près, et ce,
quelle que soit la taille des données d’entrée de l’algorithme. Par opérations
élémentaires on entend les évaluations d’expressions, les a↵ectations, et plus
généralement tous les traitements en temps constant ou indépendant de l’algorithme (entrées-sorties, . . . ). Le détail de l’exécution de ces opérations au
niveau du processeur par l’intermédiaire d’instructions machine donnerait
des résultats identiques à un facteur constant près.
Autrement dit, pour toute machine M, il existe des constantes m et M
telles que pour tout algorithme A et pour toute donnée d fournie en entrée de
l’algorithme, si l’on note T (d) le nombre d’opérations élémentaires e↵ectuées
par l’algorithme avec la donnée d en entrée et TM (d) le temps de calcul du
programme implémentant A avec la donnée d en entrée,
mT (d)  TM (d)  M T (d).
Soit, en utilisant les notations de Landau (voir section 8.2),
T = ⇥(TM ).
On peut donc définir la complexité en temps d’un algorithme comme le
nombre d’opérations élémentaires T (n) e↵ectuées lors de son exécution sur
des données de taille n. Il s’agit donc d’une fonction. On distingue
12
CHAPITRE 2. ANALYSE DES ALGORITHMES
— la complexité dans le pire des cas (worst case complexity) : Tpire (n)
est le nombre d’opérations maximal calculé sur toutes les données de
taille n ;
— la complexité dans le meilleur des cas (best case complexity) : Tmin (n)
est le nombre d’opérations minimal calculé sur toutes les données de
taille n ;
— la complexité en moyenne (average case complexity) : Tmoy (n) le
nombre d’opérations moyen calculé sur toutes les données de taille n,
en supposant qu’elles sont toutes équiprobables.
Analyser un algorithme consiste à évaluer la ou les fonctions de complexité qui lui sont associées. En pratique, on cherche à évaluer le comportement asymptotique de ces fonctions relativement à la taille des entrées (voir
section 8.2). En considérant le temps d’exécution, dans le pire des cas ou en
moyenne, on parle ainsi d’algorithmes
— en temps constant : T (n) = ⇥(1),
— logarithmiques : T (n) = ⇥(log n),
— polylogarithmiques : T (n) = ⇥((log n)c ) pour c > 1,
— linéaires : T (n) = ⇥(n),
— quasilinéaires : T (n) = ⇥(n log n),
— quadratiques : T (n) = ⇥(n2 ),
— polynomiaux : T (n) = ⇥(nk ) pour k 2 N,
— exponentiels : T (n) = ⇥(k n ) pour k > 1.
Si un traitement élémentaire prends un millionième de seconde, le tableau
suivant décrit les temps d’exécutions approximatifs de quelques classes de
problèmes en fonction de leurs tailles.
Taille n/ T (n)
10
100
1000
104
105
106
log2 n
0.003 ms
0.006 ms
0.01 ms
0.013 ms
0.016 ms
0.02 ms
n
0.01 ms
0.1 ms
1 ms
10 ms
100 ms
1s
n log2 n
0.03 ms
0.6 ms
10 ms
0.1 s
1.6 s
20 s
n2
0.1 ms
10 ms
1s
100 s
3 heures
10 jours
2n
1 ms
1014 siecles
D’un autre point de vue, le tableau ci-dessous donne la taille des problèmes
de complexité T que l’on peut traiter en une seconde en fonction de la
rapidité de la machine utilisée (F est le nombre d’opérations élémentaires
exécutées par secondes).
2.2. COMPLEXITÉ DES ALGORITHMES
F /T (n)
106
107
109
1012
2n
20
23
30
40
n2
1.000
3.162
31.000
106
13
n log2 n
63.000
600.000
4.107
3.1010
n
106
107
109
log2 n
10300.000
103.000.000
En pratique, un algorithme est constitué d’instructions agencées à l’aide
de structures de contrôle que sont les séquences, les embranchements et les
boucles (nous verrons plus tard comment traiter le cas particulier des fonctions récursives, mais de toutes façons toute fonction récursive s’écrit aussi
itérativement).
— Le coût d’une séquence de traitements est la somme des coûts de
chaque traitement.
— Le coût d’une structure itérative est la somme des coûts des traitements e↵ectués lors des passages successifs dans la boucle. Par
exemple il arrive fréquemment que l’on e↵ectue n fois une boucle
avec des coûts dégressifs (Cn, C(n 1),. . . , C). Dans ce cas
T (n) = C(n+(n 1)+(n 2)+. . .+1) = C
n
X
i=1
i = C
n(n + 1)
= ⇥(n2 ).
2
— Le coût d’un embranchement (si test alors traitement1 sinon
traitement2) est le coût du plus coûteux des deux traitements.
2.2.1
Exemples d’analyse d’algorithmes
Taille d’un tableau
Un tableau de n constantes de taille k (entiers, réels, . . .) a une taille
de kn. Mais on peut facilement démontrer que f (n) = ⇥(g(n)) ssi f (kn) =
⇥(g(kn)), lorsque g est constante, linéaire, quasilinéaire ou polynomiale.
Par ailleurs, si f (n) est exponentielle, c’est a fortiori aussi le cas de f (kn).
On pourra donc supposer, dans les calculs de complexité, que la taille d’un
tableau de n éléments constants est n.
L’algorithme d’insertion d’un élément dans un tableau trié
Dans le pire des cas, celui où l’entier à insérer est plus petit que tous les
éléments du tableau, l’algorithme 2 requiert
— 2n + 2 a↵ectations,
14
CHAPITRE 2. ANALYSE DES ALGORITHMES
— 2n + 1 comparaisons d’entiers,
— n soustractions et n + 1 addition,
— n + 1 évaluations d’une expression booléenne
pour un tableau de n éléments.
En supposant de plus que chaque instruction élémentaire s’exécute en un
temps constant, on en déduit que Tpire (n) = ⇥(n). L’algorithme d’insertion
ordonnée est linéaire dans le pire des cas : à des constantes additives et
multiplicatives près, le temps d’exécution est égal au nombre d’éléments du
tableau.
On voit facilement que Tmin (n) = ⇥(1) : en e↵et, dans le meilleur des
cas, l’élement à insérer est plus grand que tous les éléments du tableau et la
boucle n’est pas exécutée.
Si l’on suppose que l’élément à insérer et un élément pris au hasard dans
le tableau, son insertion nécessitera en moyenne n/2 comparaisons. On aura
donc, Tmoy (n) = ⇥(n), soit une complexité moyenne du même ordre que la
complexité du pire des cas.
L’algorithme de tri par insertion
Dans le pire des cas, celui où le tableau en entrée est déjà trié, mais par
ordre décroissant, l’algorithme nécessite
— n 1 comparaisons et a↵ectations pour la condition de la boucle Pour
— n 1 appels à l’algorithme d’insertion ordonnée, pour des tailles de
tableaux variant de 1 à n 1.
À des constantes additives et multiplicatives près, le temps d’exécution
dans le pire des cas de l’algorithme de tri par insertion est donc égal à
(n
1) + 1 + 2 + . . . + (n
1) = n
1+
n(n
1)
2
= ⇥(n2 )
soit en appliquant la remarque sur l’égalité à des constantes additives et
multiplicatives près de la taille des données et du nombre d’éléments du
tableau,
Tpire (n) = ⇥(n2 ).
On voit facilement que Tmin (n) = ⇥(n), cas où le tableau est déjà ordonné par ordre croissant. En e↵et, chaque appel à l’algorithme d’insertion
ordonnée est exécuté en temps constant.
Si tous les éléments du tableau en entrée sont tirés selon la même loi
de probabilités, on peut montrer que Tmoy (n) = ⇥(n2 ). Autrement dit, s’il
peut arriver que le temps d’exécution soit linéaire, il y a beaucoup plus de
chance qu’il soit quadratique en la taille des données en entrée.
2.2. COMPLEXITÉ DES ALGORITHMES
15
L’algorithme de recherche dichotomique
Si le tableau contient un seul élément, l’algorithme exécutera 2 comparaisons (coût total : c1 )
Si le tableau contient n > 1 éléments, l’algorithme exécutera
— 3 opérations arithmétiques, une a↵ectation et une comparaison entre
2 entiers (coût total : c2 ), et
— un appel récursif sur un tableau possédant dn/2e ou bn/2c éléments,
soit dn/2e dans le pire des cas.
On en déduit que si n > 1,
Tpire (n) = Tpire (dn/2e) + c2 .
Cette équation est simple à résoudre lorsque n est égal à une puissance
de 2 :
Tpire (2h ) = Tpire (2h
1
) + c2 = Tpire (2h
2
) + 2c2 = . . . = c1 + hc2 .
Dans le cas général, soit
n = a h 2h + . . . + a 1 21 + a 0
l’écriture de n en base 2 : ah = 1 et 0  ai  1 pour 0  i < h. On a
2h  n < 2h+1 et donc 2h
1
 dn/2e  2h .
On en déduit que
c1 + hc2  Tpire (n)  c1 + (h + 1)c2 .
En remarquant que h = ⇥(log2 n), on en déduit que Tpire (n) = ⇥(log2 n).
La recherche dichotomique d’un élément dans un tableau trié se fait donc
en temps logarithmique.
L’analyse d’algorithmes conduit souvent à des équations récursives de la
forme
T (n) = aT (dn/be) + f (n) ou aT (bn/bc) + f (n)
(2.1)
où b > 1, a > 0 et f est une fonction. Le théorème 2 de la section 8.4 donne
la solution de ces équations dans le cas général. Dans l’exemple précédent,
on se trouve dans le cas (2) du théorème, avec a = 1 et b = 2 : on retrouve
que Tpire (n) = ⇥(log2 n).
Il est essentiel de savoir utiliser ce théorème mais il est aussi utile de
pouvoir retrouver rapidement le résultat cherché. Nous décrivons ci-dessous
quelques calculs approximatifs, à partir de l’équation simplifiée
T (n) = aT (n/b) + f (n),
qui recouvrent un grand nombre de cas courants.
(2.2)
16
CHAPITRE 2. ANALYSE DES ALGORITHMES
Calculs “à la main” de la complexité d’une fonction définie par
une relation de récurrence
— Nombre d’itérations : si l’on itère k fois l’équation 2.2, l’argument de
T devient n/bk . On a n/bk = 1 ssi k = logb n. Il faut donc environ
logb n itérations pour trouver la constante de référence T (1).
— On en déduit le développement suivant :
T (n) = f (n) + af (n/b) + · · · + ak
1
f (n/bk
1
T (n) = f (n) + f (n/b) + · · · + f (n/bk
1
) + ⇥(alogb n ).
Si a = 1, on a
).
— Si f (n) = c,
T (n) = kc = ⇥(logb n).
— Si f (n) = n,
T (n) = n(1 +
1
bk
b
1
1
+ ··· + k 1) = n
b
b
b
1
= ⇥(n).
Si a = b, on a
T (n) = f (n) + bf (n/b) + · · · + bk
1
f (n/bk
1
) + ⇥(n).
— Si f (n) = c,
T (n) = c(1 + b + · · · + bk
1
) + ⇥(n) =
n
b
1
+ ⇥(n) = ⇥(n).
1
— Si f (n) = n,
T (n) = n(1 + 1 + · · · + 1) + ⇥(n) = nk + ⇥(n) = ⇥(n logb n).
Si a > b, on peut écrire a = bh avec h > 1. On a
T (n) = f (n) + af (n/b) + · · · + ak
1
f (n/bk
1
) + ⇥(nh ).
— Si f (n) = c,
T (n) = 1 + a + · · · + ak
1
+ ⇥(nh ) =
nh 1
+ ⇥(nh ) = ⇥(nh ).
a 1
2.2. COMPLEXITÉ DES ALGORITHMES
17
— Si f (n) = n,
T (n) = n(1+a/b+· · ·+(a/b)k
1
)+⇥(nh ) = n
(a/b)k 1
+⇥(nh ) = ⇥(nh ).
a/b 1
Ce que l’on peut résumer dans le tableau suivant,
T (n)
f (n) = c
f (n) = n
a=1
logb n
n
a=b
n
n logb n
a = bh
nh
nh
Pour les autres cas, utilisez le théorème.
Le tri par fusion
Le tri par fusion est un algorithme qui applique la stratégie “diviser pour
régner ”. Le tableau à trier est divisé en deux sous-tableau de même taille
(à un élément près), l’algorithme est appelé récursivement sur chacun de ces
sous-tableaux, et les tableaux résultants sont alors fusionnés.
Algorithme 7: TriFusion
entrée : T [1, n] est un tableau d’entiers, n 1.
résultat : les éléments de T sont ordonnés par ordre croissant.
début
si n > 1 alors
T1 := triF usion(T [1, bn/2c])
T2 := triF usion(T [bn/2c + 1, n])
T := F usion(T1 , T2 )
On peut montrer que l’algorithme de fusion est linéaire en fonction du
nombre n d’éléments. Soit T (n) la complexité de l’algorithme en fonction de
n. L’équation de récurrence correspondante est
T (n) = T (dn/2e) + T (bn/2c) + n + c.
Méthode générale (voir le théorème 2 de la section 8.4). On est
dans le cas 2 puisque : f (n) = n + c, a = b = 2, logb a = 1 et f (n) = O(n).
Donc T (n) = ⇥(n log2 n).
Solution en développant directement la récurrence.
18
CHAPITRE 2. ANALYSE DES ALGORITHMES
On peut considérer l’équation plus simple suivante : T (n) = 2T (n/2)+n.
On a :
T (n) = 2T (n/2) + n = 4T (n/4) + 2n = · · · = 2k T (n/2k ) + kn.
Les appels récursifs se terminent lorsque n ' 2k , soit k ' log2 (n) : il y a
au plus log2 (n) appels récursifs imbriqués. On en déduit que
T (n) ' nc + n log2 (n).
Le terme dominant étant n log2 (n), on en déduit que
T (n) = ⇥(n log2 (n)).
n
2x n
n/2
hauteur : dlog2 ne
2x n
n/2
log n
n/4
n/8
1
n/4
n/8
1 1
n/8
n/4
n/8 n/8
2x n
n/4
n/8 n/8
n/8
....
Figure 2.1 – Arbre des appels récursifs pour l’algorithme de tri par fusion.
Solution intuitive.
Si l’on pressent que la complexité de la fonction T vérifiant T (n) =
2T (n/2) + n est ⇥(n log2 n), on peut essayer de vérifier que cette intuition
est juste, c’est-à-dire, s’il existe une fonction T vérifiant à la fois l’équation
de récurrence et T (n) = ⇥(n log2 n).
Posons
T (n) = a · n log2 n + bn + c
et essayons ensuite de fixer les constantes au moyen de l’équation de récurrence.
On a :
T (n) = a · n log2 n + bn + c
= 2T (n/2) + n
= 2[a · n/2 log2 n/2 + bn/2 + c] + n
= a · n log2 n
an + bn + 2c + n.
On en déduit que a = 1 et c = 0, b n’étant pas contraint. La fonction
T (n) = n log2 n vérifie donc l’équation T (n) = 2T (n/2) + n.
2.2. COMPLEXITÉ DES ALGORITHMES
2.2.2
19
Exercices
Exercice 1 Montrez que
1. n log n = O(n2 ),
2. n log n = ⌦(n),
3. n + sin(n) ⇠ n.
4. A t-on 2n = ⇥(en ) ?
5. A t-on elog2 (n+1) = !(n) ?
Exercice 2 Quelles sont les classes de complexité des fonctions suivantes :
1. f (n) = 2n3 + 4n2 + 23 ,
2. f (n) = log (n2 + 3n 1),
3. f (n) = 2
log2 (n2 +1)
2
.
Exercice 3 Quelle est la complexité de l’algorithme suivant :
Algorithme 8:
début
pour i := 1 à n ⇤ n faire
u := i
tant que u > 1 faire
u := u/2
Exercice 4 Calculez la complexité de la fonction qui calcule le produit de
deux matrices carrées A et B de n lignes et n colonnes.
Exercice 5 Prouvez la correction de l’algorithme récursif suivant :
Algorithme 9: expo
entrée : x est un réel et n est un entier
sortie : xn .
début
si x = 0 alors
retourner 0
si n = 0 alors
retourner 1
si n est pair alors
retourner expo(x ⇤ x, n/2)
sinon
retourner x ⇤ expo(x ⇤ x, (n 1)/2)
20
CHAPITRE 2. ANALYSE DES ALGORITHMES
Combien de multiplications faut-il e↵ectuer pour calculer x20 ? pour calp
p
culer x2 ? pour calculer x2 1 ?
Quelle est la complexité de l’algorithme - en terme de nombre de multiplications ?
Exercice 6 Écrivez un algorithme qui prend en entrée une matrice carrée
n ⇥ n et retourne la liste des coordonnées des cases dont la valeur est à
la fois maximale sur leur ligne et minimale sur leur colonne. Quelle est la
complexité de cet algorithme ?
Exercice 7 : Calcul de l’élément majoritaire d’un tableau.
Etant donné un ensemble E de n éléments, on dit qu’un élément e est
majoritaire dans E si le nombre d’occurrences de e est strictement supérieur
à n/2.
1. Quel est la complexité d’un algorithme qui teste si un élément e est
majoritaire ou non dans E ?
On cherche maintenant à savoir s’il existe un élément majoritaire, et
si oui, à le déterminer.
2. Proposez un algorithme de complexité quadratique.
3. On considère maintenant l’approche suivante, basée sur le principe
diviser pour régner : pour rechercher l’élément majoritaire éventuel
dans l’ensemble E, on répartit les n éléments de E dans deux ensembles (approximativement) de même taille E1 et E2 . Montrez que
si un élément est majoritaire dans E, alors il est majoritaire dans E1
ou dans E2 .
Ecrivez une procédure de calcul correspondant à cette idée. Quelle
est sa complexité ?
Une troisième méthode sera proposée au prochain TD.