1. Introduction
Il est possible de trier une liste ou un tableau à l’aide de l’algorithme de tri par sélection ou de celui par insertion. Mais ces algorithmes permettent-ils de trier de grandes quantité de données ou demandent-ils trop de temps pour pouvoir être réellement mis en œuvre ?
2. Objectif de la séance :
Comprendre la notion de coût d’un algorithme et calculer le coût de quelques algorithmes caractéristiques.
3. Qu’est ce que la complexité ?
3.1. Définition
Lorsqu’on doit étudier une grande quantité de données (moyenne, tri, …), il devient important de savoir le temps qui va être nécessaire à l’algorithme pour effectuer ce tri. On parle de la complexité de l’algorithme. Même si de nombreux facteurs influencent ce temps (processeur, capacité mémoire etc..), un élément important est le nombre d’opérations élémentaires que doit effectuer l’algorithme (affectation, comparaison par exemple). Lorsqu’on trie de grandes quantités de données, ce critère est important en effet avec le tri par insertion pour trier \( 1000 \) valeurs, il faut effectuer environ \( 1000000 \) opérations ! Plus généralement, pour trier \( n \) valeurs, on effectue environ \( n^{2} \) __opérations. |
3.2. Travail à faire : Comparaison de deux alogrithmes de tri
Consultez l’article de Wikipédia sur les algorithmes de tri et en particulier le paragraphe sur la complexité et celui sur la comparaison des algorithmes puis répondez aux questions suivantes :
Avec l’algorithme de tri par tas, combien d’opérations effectue-t-on pour trier \( 10^{6} \) valeurs ?
Dans le pire des cas, avec l’algorithme de tri par sélection, combien d’opérations effectue-t-on pour trier \( 10^{6} \) valeurs ?
L’algorithme de tri par tas vous semble-t-il plus efficace ?
4. Notion de complexité
4.1. Travail à faire : Un peu d’histoire
Consultez les paragraphes Histoire et Différentes approches l’article de Wikipédia la complexité des algorithmes puis répondez aux questions suivantes :
Quel informaticien a, le premier, proposé un calcul du coût indépendant de la machine utilisée ?
Quelle est l’approche la plus classique de calcul du temps ?
4.2. Le saviez-vous ?
Actuellement, les quantités de données analysées augmentent très rapidement, on parle maintenant de big data.
5. Complexité de l’algorithme de tri par sélection
5.1. Principe
Nous allons donc calculer le coût de l’algorithme de tri par sélection dans le pire des cas en comptant le nombre d’opérations élémentaires qui doivent être effectuées.
L’algorithme de tri par sélection est donc basé sur la répétition des instructions :
-
Trouver l’élément maximum de la partie du tableau restant à trier, c’est-à-dire le maximum de
table[i]
àtable[n-1]
-
Echanger cet élément avec l’élément
table[i]
si nécessaire. Comme on se place dans le pire des cas, on suppose que cet échange a toujours lieu.
5.2. Travail à faire : Exemple
Nous allons évaluer ce nombre d’opérations pour l’algorithme de tri par sélection.
10 |
22 |
3 |
8 |
15 |
26 |
24 |
5.3. Travail à faire : Cas général
On va maintenant évaluer le nombre d’opérations nécessaires dans le cas général d’un tableau comprenant \( n \) éléments. Pour simplifier, on va supposer que les affectations sont négligeables et ne compter que les comparaisons :
5.4. Résultats mathématiques à retenir
|
6. Calcul et classe de complexité
Pour calculer la complexité d’un algorithme, on recherche le nombre maximum d’intructions qui seront exécutées dans le pire des cas. On généralise ensuite en recherchant l’ordre de grandeur de la complexité ou classe de complexité noté \( \mathcal{O}(n) \) :
|
6.1. Travail à faire : Recherche d’un élément dans une liste
Soit l’algorithme de recherche d’un élément dans une liste :
fonction trouver(x, liste):
a_trouver ← Faux
n ← longeur(liste)
Pour i allant de 0 à n:
Si liste[i]=x alors
a_trouver ← Vrai
FinSi
Fin Pour
retourner a_trouver
Fin
Un peu d’aide
|
Soit l’algorithme de recheche d’un élément dans une liste :
fonction trouver(x, liste):
a_trouver ← Faux
Tant que a_trouver = Faux
Si liste[i]=x alors
a_trouver ← Vrai
FinSi
Fin Tant que
retourner a_trouver
Fin
Un peu d’aide
|
6.2. Travail à faire : Tri à bulle
Soit l’algorithme suivant :
def tri_bulle(tab):
n = len(tab)
# Traverser tous les éléments du tableau
for i in range(n):
for j in range(0, n-i-1):
# échanger si l'élément trouvé est plus grand que le suivant
if tab[j] > tab[j+1] :
tab[j], tab[j+1] = tab[j+1], tab[j]
Un peu d’aide
|
6.3. Travail à faire : Un programme bizarre
Soit la fonction python suivante :
def fonction(n):
c = 0
while n>0:
c+=1
n = n//2
return c
for i in range(1, 1001):
print(i, " : ", fonction(i))
La variable c
compte le nombre de fois ou la boucle est répétée. A quelques instructions près, elle donne donc le résultat de la complexité de cet algorithme.
-
Coder et tester ce programme. Que représente les variables
i
etc
? -
Quelle est sa classe de complexité ?
\(\mathcal{O}(1)\) \(\mathcal{O}(\log(n))\) \(\mathcal{O}(n)\) \(\mathcal{O}(n\log(n))\) \(\mathcal{O}(n^{2})\)
Un peu d’aide
|
7. Complexité du tri par insertion
7.1. Travail à faire : Rappel
C’est le tri du joueur de cartes. On fait comme si les éléments à trier étaient donnés un par un, le premier élément constituant, à lui tout seul, une liste triée de longueur 1. On range ensuite le second élément pour constituer une liste triée de longueur 2, puis on range le troisième élément pour avoir une liste triée de longueur 3 et ainsi de suite… Le principe du tri par insertion est donc d’insérer à la \( n_{ième} \) itération le \( n_{ième} \) élément à la bonne place. |
7.2. Algorithme
fonction tri_insertion(liste)
n ← longueur(liste)
Pour i de 1 à n - 1 :
# mémoriser liste[i] dans x
x ← liste[i]
# décaler vers la droite les éléments de liste[0]..liste[i-1] qui sont plus grands que x en partant de liste[i-1]
j ← i
Tant que j > 0 et liste[j - 1] > x :
liste[j] ← liste[j - 1]
j ← j - 1
# placer x dans le "trou" laissé par le décalage
liste[j] ← x
retourner liste
7.3. Travail à faire : Calcul
Le pire des cas consiste à trier une liste inversement triée :
|
Quelle est la classe de complexité de cet algorithme ?
Un peu d’aide
|
7.4. Travail à faire : Programmation
-
Coder et tester la fonction
tri_insertion(liste)
pour trier dans l’ordre croissant les éléments de laliste
donnée en argument -
Modifier le code pour que la fonction retourne le nombre de permutations effectuées
-
Tester la fonction avec les listes suivantes :
-
[6, 5, 4, 3, 2, 1]
doit retourner \( 15 \) -
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
doit retourner \( 45 \) -
[*range(20,0,-1)]
doit retourner \( 190 \) -
[*range(50,0,-1)]
doit retourner \( 1225 \) -
[*range(100,0,-1)]
doit retourner \( 4950 \)
-
-
Vérifier que la complexité de cet algorithme est de \(n \frac{(n-1)}{2} \) et que sa classe de complexité est \(\mathcal{O}(n^{2})\)
Correction
|
8. Recherche dichotomique
La recherche dichotomique, ou recherche par dichotomie, est un algorithme de recherche pour trouver la position d’un élément dans un tableau trié. Le principe est le suivant :
Par exemple, on recherche
|
8.1. Programme
def dichotomie(liste, x):
find = False
start = 0
end = len(liste)-1
m = (end - start)//2
while end>=start and not find:
#print(m)
if liste[m] == x:
find = True
elif x > liste[m]:
start = m+1
elif x < liste[m]:
end = m-1
m = (end - start)//2 + start
if find:
return m
return -1
liste = [*range(1,50,4)]
print(liste)
pos = dichotomie(liste, 17)
print("Recherche de l'élément 17 :")
if pos!=1:
print("Trouvé à la position ", pos)
else:
print("Pas trouver")
8.2. Travail à faire : Calcul
-
Calculer la complexité de l’algorithme de recherche dichotomique dans le pire des cas
Quelle est la classe de complexité de cet algorithme ?
Un peu d’aide
|
8.3. Travail à faire : Programmation
-
Coder et tester la fonction
dichotomie(liste, x)
pour rechercher la valeurx
parmi les éléments de laliste
donnée en argument -
Modifier le code pour que la fonction retourne le nombre d’itérations effectuées lors de la recherche
-
Tester la fonction avec le programme suivant qui affiche le nombre d’itérations nécessaire pour rechercher chaque élément de la liste ainsi que le nombre maximum d’itérations :
def dichotomie(liste, x):
...
return c
liste = [*range(1,100)]
print(liste)
max_iterations = 0
for i in liste:
nb = dichotomie(liste, i)
if nb>max_iterations:
max_iterations = nb
print(f"Recherche de l'élément {i} :")
print(" Touver en ", nb, " coups")
print("nombre maximum d'itérations : ", max_iterations)
-
Vérifier que la complexité de cet algorithme est \( \log(n) \) et sa classe de complexité \(\mathcal{O}(\log(n))\)
Correction
Ce programme retourne \( 7 \) pour le maximum d’itérations lorsque la taille des données est de \( 100 \). On vérifie bien ici que la complexité est logarithmique car \( \log_{2}(100) = 6,64 \approx 7 \) Pour s’en assurer, vous pouvez modifier la taille de la liste et vérifier que pour \( n \) éléments, le maximum d’itérations est toujours égal à \( \log_{2}(n) \) |