• Aucun résultat trouvé

Complexité

Dans le document Informatique pour tous deuxième année (Page 16-22)

Les complexités en temps s'expriment toujours en termes d'opérations élémentaires, en omettant éventuellement des opérations élémentaires dont le coût est négligeable devant celui d'autres opérations, si elles ne sont pas largement majoritaires.

Pour calculer la complexité d'une fonction récursive, on introduit une suite récurrente dont les indices sont les arguments de la fonction et les valeurs représentent les coûts des appels récursifs.

Par exemple, la complexité de la fonction factorielle se trouve ainsi :

Pour n < 0, on a une comparaison concernant n et une erreur sans calcul.

Pour n == 0, on a deux comparaisons et une valeur de retour sans calcul.

Pour n > 0, on a deux comparaisons, une addition (le calcul de n-1), une multiplication et le coût de l'appel de la fonction pour n-1.

En négligeant le coût des comparaisons à zéro (voire des additions), on introduit la suite cn des coûts, avecc0 = 0 (on oublie les négatifs) et cn+1 =cn+ 2, d'oùcn= 2n. En cas de doute sur les opérations élémentaires, on peut aussi exprimer la complexité en termes d'appels récursifs. Quant à la complexité en espace, elle se calcule sur le même principe, mais il s'agit de savoir si les opérations se font sur place ou non, c'est-à-dire si les données sont écrasées (on fait en quelque sorte le coût maximal parmi tous les appels) ou non (auquel cas on aura une somme).

On donne (ou rappelle) ici des complexités classiques à partir de relations de récur-rences entre le coût cn et le coût pourn−1ou n2 ou une autre valeur moindre.

7. Et c'est un problème indécidable, à la base de la théorie de la calculabilité en informatique !

2.3. COMPLEXITÉ 17 cn =cn−1+O(1) : cn=O(n).

cn =acn−1+O(1),a >1 : cn =O(an). cn =cn−1+O(n) : cn =O(n2).

cn =ncn−1+O(1) : cn =O(n!). cn =cn

2 +O(1) : cn =O(logn). cn =cn

2 +O(n) :cn=O(n). cn = 2cn

2 +O(1) :cn=O(n). cn = 2cn

2 +O(n): cn=O(nlogn). cn =acn

b, a et b étant des entiers strictement positifs : cn =O(nlogba).

Chapitre 3 Tris

Partant du principe que des structures de données ordonnées sont utiles dans bien des situations, notamment la recherche d'un élément1, nous allons nous intéresser dans ce cours à quelques-uns des nombreux algorithmes permettant de trier une liste d'éléments à valeurs dans un même ensemble totalement ordonné.

Les tris sont discriminés par leur complexité en termes de comparaisons et d'aecta-tions d'éléments de la liste (ou d'échanges, si le tri se fait en place).

Ils sont présentés avec leur principe, une implémentation en Python, une preuve du programme et la complexité.

En ce qui concerne la complexité en espace, quand les tris sont en place (au prix d'un eort de compréhension supplémentaire. . . et de rigueur dans les preuves de programme), aucune mémoire supplémentaire n'est utilisée. Sinon, un espace linéaire est généralement requis.

1. Imaginons ne serait-ce qu'une seconde un dictionnaire dans lequel les mots sont triés par ordre chronologique de leur ajout.

19

3.1 Tri par insertion et tri par sélection

Les deux algorithmes présentés dans cette section sont traités en parallèle, de par leur ressemblance et le fait qu'ils sont les deux tris les plus naturels possibles.

Ils consistent à construire la liste triée par ajout successif d'un élément, soit en insérant à chaque fois au bon endroit dans la liste en construction l'élément suivant de la liste de départ, soit en récupérant à chaque fois l'élément minimal (ou maximal) de la liste des éléments non encore triés.

def insertion_sort(l):

for i in range(1, len(l)): # on parcourt l j = i-1 # position après laquelle

# on va finalement insérer le i-ième élément x = l[i] # mémorisation de l'élément

while j >= 0 and l[j] > x: # recherche de la bonne position l[j+1] = l[j] # on décale le reste de la liste

j -= 1

l[j+1] = x # insertion proprement dite

Voyons ce qui se passe sur la liste [101,103,105,102] quand i vaut 3 dans la boucle principale.

La variable j est initialisée à 2 et la variable x à 102, puis la première comparaison est eectuée : 105 > 102, donc on met à jour l[3] à 105 et j à 1, puis 103 > 102, donc on met à jour l[2] à 103 et j à 0, mais 101 <= 102, donc on sort de la boucle puis on met à jour l[1] à 102, de sorte qu'aucun élément ne soit perdu.

Par la condition dans la boucle intérieure, on constate qu'un élément supplémentaire a été inséré dans l'ordre, ce qui permet de donner l'invariant de boucle suivant : après le i-ième tour de la boucle intérieure, les i+1 premiers éléments de la liste sont dans l'ordre croissant, et bien entendu la liste complète est une permutation de la liste de départ.

La complexité dans le pire des cas est atteinte quand l est initialement dans l'ordre inverse. Dans ce cas, il faudra faire toutes les comparaisons à chaque tour dans la boucle, soit un nombre quadratique et autant de mises à jour.

3.1. TRI PAR INSERTION ET TRI PAR SÉLECTION 21 On signale que la suppression et l'insertion de t[i] ne ferait pas formellement gagner de complexité. La complexité dans le meilleur des cas est atteinte pour une liste triée, où seules n-1 comparaisons sont nécessaires. Pour la majorité des algorithmes, les optima de complexité sont par ailleurs atteints quand la liste de départ est ordonnée.

def selection_sort(l):

for i in range(len(l)-1):

# si i = n-1, le minimum est le seul élément restant ind_min = i # où est le minimum a priori for j in range(i+1, len(l)):

if l[j] < l[ind_min]: # mise à jour du minimum ind_min = j

if ind_min != i:

# l'avantage du tri par sélection est le faible nombre d'échanges,

# on fait l'hypothèse qu'ils sont plus chers que les comparaisons l[i], l[ind_min] = l[ind_min], l[i]

En considérant la même liste qu'avant, pour i valant 0 on cherche le minimum de la liste, qui est 101, ce qui n'occasionne pas d'échange, puis pour i valant 1 on cherche le minimum du reste, qui est 102, échangé avec 103 en tant qu'élément en position i, puis pour i valant 2 c'est 103 qui est le minimum, occasionnant un dernier échange, et le dernier élément ne peut alors être que le maximum.

L'invariant de boucle est cette fois le suivant : après le i-ième tour de la boucle intérieure, les i premiers éléments de la liste sont les i plus petits éléments de la liste de départ dans l'ordre croissant, et là encore la liste complète est une permutation de la liste de départ.

La complexité du tri par sélection en nombre de comparaisons est forcément n(n−1)2 , car la recherche du minimum à chaque étape n'est pas optimisée et donc elle doit toujours être faite. A contrario, le nombre d'aectations est linéaire, ce qui est un plus par rapport aux autres tris.

En fait, le tri par sélection est celui qui sera souvent spontanément eectué par un humain devant par exemple classer toutes les cartes d'une seule couleur pour ranger un paquet : il cherchera d'abord la plus petite, puis la suivante, etc. jusqu'à les avoir toutes récupérées.

Dans le document Informatique pour tous deuxième année (Page 16-22)

Documents relatifs