From 92e0221e759ac3c7efcf294cdac10d108f979018 Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Sat, 16 Nov 2024 10:37:49 +0100 Subject: [PATCH] added cours 8 --- slides/cours_7.md | 870 -------------------------------------------- slides/cours_8.md | 900 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 900 insertions(+), 870 deletions(-) create mode 100644 slides/cours_8.md diff --git a/slides/cours_7.md b/slides/cours_7.md index 9e2d21b..caefc5e 100644 --- a/slides/cours_7.md +++ b/slides/cours_7.md @@ -556,873 +556,3 @@ int partition(int size, int array[size], int first, int last) { ``` -# Le tri par base (radix sort) - -\Huge Le tri par base (radix sort) - -# Tri par base (radix sort) - -* N'utilise pas la notion de comparaisons, mais celle de classement successif dans des catégories (alvéoles). -* Pour simplifier - * Tri de nombre entiers dans un tableau. - * On considère que des nombres $\ge 0$ (sans perte de généralité). - * On considère ensuite la représentation binaire de ces nombres. - -# Principe de l'algorithme - -1. On considère le bit le moins significatif. -2. On parcourt une 1ère fois le tableau et on place à la suite dans un 2ème tableau les éléments dont le bit est 0; - puis on répète l'opération 2 pour les éléments dont le bit est 1. -3. On répète l'étape 2 en regardant le bit suivant et en permutant le rôle des deux tableaux. - -On utilise donc deux tableaux pour réaliser ce tri. -A noter qu'à chaque étape, l'ordre des éléments dont le bit est à 0 (respectivement à 1) reste identique dans le 2ème tableau par rapport au 1er tableau. - -# Illustration sur un exemple (1/6) - -Soit la liste de nombre entier: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 | - -Le plus petit élément est -9. On commence donc par décaler les valeurs de 9. - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| 14 | 4 | 10 | 15 | 13 | 3 | 11 | 0 | 11 | - -# Illustration sur un exemple (2/6) - -* Écrivons les éléments en représentation binaire. -* La valeur maximale est 15, on a besoin de 4 bits. - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| -| 14 | 4 | 10 | 15 | 13 | 3 | 11 | 0 | 11 | -| 1110 | 0100 | 1010 | 1111 | 1101 | 0011 | 1011 | 0000 | 1011 | - -# Illustration sur un exemple (3/6) - -* On considère le bit de poids faible - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| -| 111**0** | 010**0** | 101**0** | 111**1** | 110**1** | 001**1** | 101**1** | 000**0** | 101**1** | - -. . . - -* On obtient le tableau: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| -| 111\textcolor{red}{0} | 010\textcolor{red}{0} | 101\textcolor{red}{0} | 111\textcolor{green}{1} | 110\textcolor{green}{1} | 001\textcolor{green}{1} | 101\textcolor{green}{1} | 000\textcolor{red}{0} | 101\textcolor{green}{1} | -| \textcolor{red}{1110} | \textcolor{red}{0100} | \textcolor{red}{1010} | \textcolor{red}{0000} | \textcolor{green}{1111} | \textcolor{green}{1101} | \textcolor{green}{0011} | \textcolor{green}{1011} | \textcolor{green}{1011} | - -# Illustration sur un exemple (4/6) - -* On passe au 2ème bit et on obtient le tableau: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| -| 11\textcolor{green}{1}0 | 01\textcolor{red}{0}0 | 10\textcolor{green}{1}0 | 00\textcolor{red}{0}0 | 11\textcolor{green}{1}1 | 11\textcolor{red}{0}1 | 00\textcolor{green}{1}1 | 10\textcolor{green}{1}1 | 10\textcolor{green}{1}1 | -| \textcolor{red}{0100} | \textcolor{red}{0000} | \textcolor{red}{1101} | \textcolor{green}{1110} | \textcolor{green}{1010} | \textcolor{green}{1111} | \textcolor{green}{0011} | \textcolor{green}{1011} | \textcolor{green}{1011} | - -. . . - -* On passe au 3ème bit et on obtient le tableau: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| -| 0\textcolor{green}{1}00 | 0\textcolor{red}{0}00 | 1\textcolor{green}{1}01 | 1\textcolor{green}{1}10 | 1\textcolor{red}{0}10 | 1\textcolor{green}{1}11 | 0\textcolor{red}{0}11 | 1\textcolor{red}{0}11 | 1\textcolor{red}{0}11 | -| \textcolor{red}{0000} | \textcolor{red}{1010} | \textcolor{red}{0011} | \textcolor{red}{1011} | \textcolor{red}{1011} | \textcolor{green}{0100} | \textcolor{green}{1101} | \textcolor{green}{1110} | \textcolor{green}{1111} | - - -# Illustration sur un exemple (5/6) - -4. On passe au dernier bit et on obtient le tableau final: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| -| \textcolor{red}{0}000 | \textcolor{green}{1}010 | \textcolor{red}{0}011 | \textcolor{green}{1}011 | \textcolor{green}{1}011 | \textcolor{red}{0}100 | \textcolor{green}{1}101 | \textcolor{green}{1}110 | \textcolor{green}{1}111 | -| \textcolor{red}{0000} | \textcolor{red}{0011} | \textcolor{red}{0100} | \textcolor{green}{1010} | \textcolor{green}{1011} | \textcolor{green}{1011} | \textcolor{green}{1101} | \textcolor{green}{1110} | \textcolor{green}{1111} | - -. . . - -* En revenant à la représentation décimale, on a le tableau trié: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:| -| 0 | 3 | 4 | 10 | 11 | 11 | 13 | 14 | 15 | - -# Illustration sur un exemple (6/6) - -* Pour revenir aux valeurs initiales, il faut décaler de 9 dans l'autre sens. - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| -9 | -6 | -5 | 1 | 2 | 2 | 4 | 5 | 6 | - -* Et alors? - -. . . - -* Et alors rien. C'est fini. - - -# Pseudo-code - -```python -rien radix_sort(entier taille, entier tab[taille]): -# initialisation - entier val_min = valeur_min(taille, tab) - entier val_max = valeur_max(taille, tab) - decaler(taille, tab, val_min) - entier nb_bits = nombre_de_bits(val_max - val_min) -# algo - entier tab_tmp[taille] - pour pos de 0 à nb_bits: - alveole_0(taille, tab, tab_tmp, pos) # 0 -> taille - alveole_1(taille, tab, tab_tmp, pos) # taille -> 0 - echanger(tab, tab_tmp) -# post-traitement - decaler(taille, tab, -val_min) -``` -<!-- ```C -void radix_sort(int size,int tab[size]) { - int val_min = tab[index_min(size,tab)]; - int val_max = tab[index_max(size,tab)]; - - decaler(size, tab,val_min); - int nb_bits = get_nb_bits(val_max-val_min); - - int tab_tmp[size]; - for (int pos=0;pos<nb_bits;pos++) { - bucket_0(size,tab,tab_tmp,pos); - bucket_1(size,tab,tab_tmp,pos); - swap(tab,tab_tmp); - } - decaler(size,tab,-val_min); -} -``` --> - -# Un peu plus de détails (1/2) - -## La fonction `decaler()` - -```python -rien decaler(entier taille, entier tab[taille], entier val): - pour i de 0 à taille-1: - taille[i] -= val -``` - -. . . - -## La fonction `echanger()` - -```python -rien echanger(entier tab[], entier tab2[]) -# échanger les tableaux (sans copier les valeurs) -``` - -# Un peu plus de détails (2/2) - -## La fonction `alveole_0()` - -```python -rien alveole_0(entier taille, entier tab[taille], - entier tab_tmp[taille], entier pos): - entier k = 0 - pour i de 0 à taille-1: - si bit(tab[i], pos) == 0: - tab_tmp[k] = tab[i] - k = k + 1 -``` - -. . . - -## La fonction `alveole_1()` - -```python -rien alveole_1(entier taille, entier tab[taille], - entier tab_tmp[taille], entier pos): - # pareil que alveole_0 mais dans l'autre sens -``` - -# Le tri par fusion (merge sort) - -\Huge Le tri par fusion (merge sort) - -# Tri par fusion (merge sort) - -* Tri par comparaison. -* Idée: deux listes triées, sont fusionnées pour donner une liste triée plus longue. -* Itérativement, on trie d'abord les paires de nombres, puis les groupes de 4 nombres, ensuite de 8, et ainsi de suite jusqu'à obtenir un tableau trié. -<!-- * On simplifie ici: le tableau a une longueur de puissance de 2. --> - -<!-- Pour son implémentation, le tri par fusion nécessite d'utiliser une zone temporaire de stockage des données de taille égale à celle de la liste de nombres à trier. On considère le cas du tri d'une liste de nombres entiers stockés dans un tableau. --> - -# Principe de l'algorithme - -* Soit `taille` la taille du tableau à trier. -* Pour `i = 0` à `entier(log2(taille))-1`: - * Fusion des paires de sous-tableaux successifs de taille `2**i` (ou moins pour l'extrémité) - -. . . - -* Remarques: - * Pour l'étape `i`, les sous-tableaux de taille `2**i` sont triés. - * La dernière paire de sous-tableaux peut être incomplète (vide ou avec moins que `2**i` éléments). - -# Exemple de tri par fusion - -* Soit la liste de nombres entiers stockés dans un tableau de taille 9: - -| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 | - -. . . - -* Fusion des éléments successifs (ce qui revient à les mettre dans l'ordre): - -| étape | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| 0 | \textcolor{red}{5} | \textcolor{green}{-5} | \textcolor{red}{1} | \textcolor{green}{6} | \textcolor{red}{4} | \textcolor{green}{-6} | \textcolor{red}{2} | \textcolor{green}{-9} | \textcolor{red}{2} | -| 1 | \textcolor{red}{-5} | \textcolor{red}{5} | \textcolor{green}{1} | \textcolor{green}{6} | \textcolor{red}{-6} | \textcolor{red}{4} | \textcolor{green}{-9} | \textcolor{green}{2} | \textcolor{red}{2} | -| 2 | \textcolor{red}{-5} | \textcolor{red}{1} | \textcolor{red}{5} | \textcolor{red}{6} | \textcolor{green}{-9} | \textcolor{green}{-6} | \textcolor{green}{2} | \textcolor{green}{4} | \textcolor{red}{2} | -| 3 | \textcolor{red}{-9} | \textcolor{red}{-6} | \textcolor{red}{-5} | \textcolor{red}{1} | \textcolor{red}{2} | \textcolor{red}{4} | \textcolor{red}{5} | \textcolor{red}{6} | \textcolor{green}{2} | -| 4 | -9 | -6 | -5 | 1 | 2 | 2 | 4 | 5 | 6 | - - -# Pseudo-code (autrement) - -```python -rien tri_fusion(entier taille, entier tab[taille]) - entier tab_tmp[taille]; - entier nb_etapes = log_2(taille) + 1; - pour etape de 0 a nb_etapes - 1: - entier gauche = 0; - entier t_tranche = 2**etape; - tant que (gauche < taille): - fusion( - tab[gauche..gauche+t_tranche-1], - tab[gauche+t_tranche..gauche+2*t_tranche-1], - tab_tmp[gauche..gauche+2*t_tranche-1]); - #bornes incluses - gauche += 2*t_tranche; - echanger(tab, tab_tmp); -``` - - -# Algorithme de fusion possible - -## Une idée? - -. . . - -* Parcourir les deux tableaux jusqu'à atteindre la fin d'un des deux - * Comparer l'élément courant des 2 tableaux - * Écrire le plus petit élément dans le tableau résultat - * Avancer de 1 dans les tableaux du plus petit élément et résultat -* Copier les éléments du tableau restant dans le tableau résultat - -# La fonction de fusion (pseudo-code autrement) - -\footnotesize - -## Une idée? - -. . . - -```python -# hyp: tab_g et tab_d sont triés -rien fusion(entier tab_g[], entier tab_d[], entier res[]): - entier g = taille(tab_g) - entier d = taille(tab_d) - entier i_g = 0, i_d = 0 - pour i = 0 à g + d: - si i_g < g et i_d < d: - si tab_g[i_g] < tab_d[i_d]: - res[i] = tab_g[i_g] - i_g = i_g + 1 - sinon: - res[i] = tab_d[i_d] - i_d = i_d + 1 - sinon si i_g < g: - res[i] = tab_g[i_g] - i_g = i_g + 1 - sinon si i_d < d: - res[i] = tab_d[i_d] - i_d = i_d + 1 - -``` - -<!-- ## Complexité -L'algorithme présenté précédemment nécessite un certain nombre d'opérations lié à la taille $N$ du tableau. - -Il y a essentiellement $\log_2(N)$ étapes. - -A chaque étape, le tableau est parcouru une fois avec un nombre constant effectué pour chacune des cases du tableau. En effet, l'opération de fusion implique de ne parcourir qu'une seule fois chacun des deux tableaux qu'on fusionne dans un 3ème tableau. - -Ainsi, la complexité du tri par fusion est $\mathcal{O}(N\cdot \log_2(N)$. --> - -# L'efficacité d'un algorithmique - -\Huge L'efficacité d'un algorithmique - -# Efficacité d'un algorithmique - -Comment mesurer l'efficacité d'un algorithme? - -. . . - -* Mesurer le temps CPU, -* Mesurer le temps d'accès à la mémoire, -* Mesurer la place prise mémoire, - -. . . - -Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc! - -## Mesure du temps CPU - -```C -#include <time.h> -struct timespec tstart={0,0}, tend={0,0}; -clock_gettime(CLOCK_MONOTONIC, &tstart); -// some computation -clock_gettime(CLOCK_MONOTONIC, &tend); -printf("computation about %.5f seconds\n", - ((double)tend.tv_sec + 1e-9*tend.tv_nsec) - - ((double)tstart.tv_sec + 1e-9*tstart.tv_nsec)); -``` - -# Programme simple: mesure du temps CPU - -## Preuve sur un [petit exemple](../source_codes/complexity/sum.c) - -```bash -source_codes/complexity$ make bench -RUN ONCE -O0 -the computation took about 0.00836 seconds -RUN ONCE -O3 -the computation took about 0.00203 seconds -RUN THOUSAND TIMES -O0 -the computation took about 0.00363 seconds -RUN THOUSAND TIMES -O3 -the computation took about 0.00046 seconds -``` - -Et sur votre machine les résultats seront **différents**. - -. . . - -## Conclusion - -* Nécessité d'avoir une mesure indépendante du/de la - matériel/compilateur/façon de mesurer/météo. - -# Analyse de complexité algorithmique (1/4) - -* On analyse le **temps** pris par un algorithme en fonction de la **taille de - l'entrée**. - -## Exemple: recherche d'un élément dans une liste triée de taille N - -```C -int sorted_list[N]; -bool in_list = is_present(N, sorted_list, elem); -``` - -* Plus `N` est grand, plus l'algorithme prend de temps sauf si... - -. . . - -* l'élément est le premier de la liste (ou à une position toujours la même). -* ce genre de cas pathologique ne rentre pas en ligne de compte. - -# Analyse de complexité algorithmique (2/4) - -## Recherche linéaire - -```C -bool is_present(int n, int tab[], int elem) { - for (int i = 0; i < n; ++i) { - if (tab[i] == elem) { - return true; - } else if (elem < tab[i]) { - return false; - } - } - return false; -} -``` - -* Dans le **meilleurs des cas** il faut `1` comparaison. -* Dans le **pire des cas** (élément absent p.ex.) il faut `n` comparaisons. - -. . . - -La **complexité algorithmique** est proportionnelle à `N`: on double la taille -du tableau $\Rightarrow$ on double le temps pris par l'algorithme. - -# Analyse de complexité algorithmique (3/4) - -## Recherche dichotomique - -```C -bool is_present_binary_search(int n, int tab[], int elem) { - int left = 0; - int right = n - 1; - while (left <= right) { - int mid = (right + left) / 2; - if (tab[mid] < elem) { - left = mid + 1; - } else if (tab[mid] > elem) { - right = mid - 1; - } else { - return true; - } - } - return false; -} -``` - -# Analyse de complexité algorithmique (4/4) - -## Recherche dichotomique - -](figs/Binary_search_complexity.svg){width=80%} - -. . . - -* Dans le **meilleurs de cas** il faut `1` comparaison. -* Dans le **pire des cas** il faut $\log_2(N)+1$ comparaisons - -. . . - -## Linéaire vs dichotomique - -* $N$ vs $\log_2(N)$ comparaisons logiques. -* Pour $N=1000000$: `1000000` vs `21` comparaisons. - -# Notation pour la complexité - -## Constante de proportionnalité - -* Pour la recherche linéaire ou dichotomique, on a des algorithmes qui sont $\sim N$ ou $\sim \log_2(N)$ -* Qu'est-ce que cela veut dire? - -. . . - -* Temps de calcul est $t=C\cdot N$ (où $C$ est le temps pris pour une comparaisons sur une machine/compilateur donné) -* La complexité ne dépend pas de $C$. - -## Le $\mathcal{O}$ de Leibnitz - -* Pour noter la complexité d'un algorithme on utilise le symbole $\mathcal{O}$ (ou "grand Ô de"). -* Les complexités les plus couramment rencontrées sont - -. . . - -$$ -\mathcal{O}(1),\quad \mathcal{O}(\log(N)),\quad \mathcal{O}(N),\quad -\mathcal{O}(\log(N)\cdot N), \quad \mathcal{O}(N^2), \quad -\mathcal{O}(N^3). -$$ - -# Ordres de grandeur - -\begin{table}[!h] -\begin{center} -\caption{Valeurs approximatives de quelques fonctions usuelles de complexité.} -\medskip -\begin{tabular}{|c|c|c|c|c|} -\hline -$\log_2(N)$ & $\sqrt{N}$ & $N$ & $N\log_2(N)$ & $N^2$ \\ -\hline\hline -$3$ & $3$ & $10$ & $30$ & $10^2$ \\ -\hline -$6$ & $10$ & $10^2$ & $6\cdot 10^2$ & $10^4$ \\ -\hline -$9$ & $31$ & $10^3$ & $9\cdot 10^3$ & $10^6$ \\ -\hline -$13$ & $10^2$ & $10^4$ & $1.3\cdot 10^5$ & $10^8$ \\ -\hline -$16$ & $3.1\cdot 10^2$ & $10^5$ & $1.6\cdot 10^6$ & $10^{10}$ \\ -\hline -$19$ & $10^3$ & $10^6$ & $1.9\cdot 10^7$ & $10^{12}$ \\ -\hline -\end{tabular} -\end{center} -\end{table} - - -# Quelques exercices (1/3) - -## Complexité de l'algorithme de test de primalité naïf? - -```C -for (i = 2; i < sqrt(N); ++i) { - if (N % i == 0) { - return false; - } -} -return true; -``` - -. . . - -## Réponse - -$$ -\mathcal{O}(\sqrt{N}). -$$ - -# Quelques exercices (2/3) - -## Complexité de trouver le minimum d'un tableau? - -```C -int min = MAX; -for (i = 0; i < N; ++i) { - if (tab[i] < min) { - min = tab[i]; - } -} -return min; -``` - -. . . - -## Réponse - -$$ -\mathcal{O}(N). -$$ - -# Quelques exercices (3/3) - -## Complexité du tri par sélection? - -```C -int ind = 0; -while (ind < SIZE-1) { - min = find_min(tab[ind:SIZE]); - swap(min, tab[ind]); - ind += 1; -} -``` - -. . . - -## Réponse - -### `min = find_min` - -$$ -(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2). -$$ - -## Finalement - -$$ -\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{swaps})=\mathcal{O}(N^2). -$$ - - -# Tri à bulle (1/4) - -## Algorithme - -* Parcours du tableau et comparaison des éléments consécutifs: - - Si deux éléments consécutifs ne sont pas dans l'ordre, ils sont échangés. -* On recommence depuis le début du tableau jusqu'à avoir plus d'échanges à - faire. - -## Que peut-on dire sur le dernier élément du tableau après un parcours? - -. . . - -* Le plus grand élément est **à la fin** du tableau. - * Plus besoin de le traiter. -* A chaque parcours on s'arrête un élément plus tôt. - -# Tri à bulle (2/4) - -## Exemple - - - - -# Tri à bulle (3/4) - -## Exercice: écrire l'algorithme (poster le résultat sur matrix) - -. . . - -```C -rien tri_a_bulles(entier tableau[]) - pour i de longueur(tableau)-1 à 1: - trié = vrai - pour j de 0 à i-1: - si (tableau[j] > tableau[j+1]) - échanger(array[j], array[j+1]) - trié = faux - - si trié - retourner -``` - -# Tri à bulle (4/4) - -## Quelle est la complexité du tri à bulles? - -. . . - -* Dans le meilleurs des cas: - * Le tableau est déjà trié: $\mathcal{O}(N)$ comparaisons. -* Dans le pire des cas, $N\cdot (N-1)/2\sim\mathcal{O}(N^2)$: -$$ -\sum_{i=1}^{N-1}i\mbox{ comparaison et }3\sum_{i=1}^{N-1}i \mbox{ affectations -(swap)}\Rightarrow \mathcal{O}(N^2). -$$ -* En moyenne, $\mathcal{O}(N^2)$ ($N^2/2$ comparaisons). - -# L'algorithme à la main - -## Exercice *sur papier* - -* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]` - -```C - - - - - - - - - - - - - -``` - -# Tri par insertion (1/3) - -## But - -* trier un tableau par ordre croissant - -## Algorithme - -Prendre un élément du tableau et le mettre à sa place parmi les éléments déjà -triés du tableau. - - - -# Tri par insertion (2/3) - -## Exercice: Proposer un algorithme (en C) - -. . . - -```C -void tri_insertion(int N, int tab[N]) { - for (int i = 1; i < N; i++) { - int tmp = tab[i]; - int pos = i; - while (pos > 0 && tab[pos - 1] > tmp) { - tab[pos] = tab[pos - 1]; - pos = pos - 1; - } - tab[pos] = tmp; - } -} -``` - -# Tri par insertion (3/3) - -## Question: Quelle est la complexité? - -. . . - -* Parcours de tous les éléments ($N-1$ passages dans la boucle) - * Placer: en moyenne $i$ comparaisons et affectations à l'étape $i$ -* Moyenne: $\mathcal{O}(N^2)$ - -. . . - -* Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$ -* Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$ - -# L'algorithme à la main - -## Exercice *sur papier* - -* Trier par insertion le tableau `[5, -2, 1, 3, 10]` - -```C - - - - - - - - - - - - - -``` - -# Complexité algorithmique du radix-sort (1/2) - -## Pseudo-code - -```python -rien radix_sort(entier taille, entier tab[taille]): -# initialisation - entier val_min = valeur_min(taille, tab) - entier val_max = valeur_max(taille, tab) - decaler(taille, tab, val_min) - entier nb_bits = nombre_de_bits(val_max - val_min) -# algo - entier tab_tmp[taille] - pour pos de 0 à nb_bits: - alveole_0(taille, tab, tab_tmp, pos) # 0 -> taille - alveole_1(taille, tab, tab_tmp, pos) # taille -> 0 - echanger(tab, tab_tmp) -# post-traitement - decaler(taille, tab, -val_min) -``` - -# Complexité algorithmique du radix-sort (2/2) - -\footnotesize - -<!-- Voici une liste de parcours utilitaires de tableau: - -1. Recherche de la valeur minimum ```val_min``` -2. Recherche de la valeur maximum ```val_max``` -3. Décalage des valeurs dans l'intervalle ```0..val_max-val_min``` -4. Décalage inverse pour revenir dans l'intervalle ```val_min..val_max``` -5. Copie éventuelle du tableau temporaire dans le tableau originel - -On a donc un nombre de parcours fixe (4 ou 5) qui se font en $\mathcal{O}(N)$ où $N$ est la taille du tableau. - -La partie du tri à proprement parler est une boucle sur le nombre de bits *b* de ```val_min..val_max```. - -A chaque passage à travers la boucle, on parcourt 2 fois le tableau: la 1ère fois pour s'occuper des éléments dont le bit courant à 0; la 2ème pour ceux dont le bit courant est à 1. - -A noter que le nombre d'opérations est de l'ordre de *b* pour la lecture d'un bit et constant pour la fonction ```swap_ptr()```. - -Ainsi, la complexité du tri par base est $\mathcal{O}(b\cdot N)$. --> - -## Pseudo-code - -```python -rien radix_sort(entier taille, entier tab[taille]): -# initialisation - entier val_min = valeur_min(taille, tab) # O(taille) - entier val_max = valeur_max(taille, tab) # O(taille) - decaler(taille, tab, val_min) # O(taille) - entier nb_bits = - nombre_de_bits(val_max - val_min) # O(nb_bits) -# algo - entier tab_tmp[taille] - pour pos de 0 à nb_bits: # O(nb_bits) - alveole_0(taille, tab, tab_tmp, pos) # O(taille) - alveole_1(taille, tab, tab_tmp, pos) # O(taille) - echanger(tab, tab_tmp) # O(1) -# post-traitement - decaler(taille, tab, -val_min) # O(N) -``` - -. . . - -* Au final: $\mathcal{O}(N\cdot (b+4))$. - -# Complexité algorithmique du merge-sort (1/2) - -## Pseudo-code - -```python -rien tri_fusion(entier taille, entier tab[taille]) - entier tab_tmp[taille]; - entier nb_etapes = log_2(taille) + 1; - pour etape de 0 a nb_etapes - 1: - entier gauche = 0; - entier t_tranche = 2**etape; - tant que (gauche < taille): - fusion( - tab[gauche..gauche+t_tranche-1], - tab[gauche+t_tranche..gauche+2*t_tranche-1], - tab_tmp[gauche..gauche+2*t_tranche-1]); - gauche += 2*t_tranche; - echanger(tab, tab_tmp); -``` - -# Complexité algorithmique du merge-sort (2/2) - -## Pseudo-code - -```python -rien tri_fusion(entier taille, entier tab[taille]) - entier tab_tmp[taille] - entier nb_etapes = log_2(taille) + 1 - pour etape de 0 a nb_etapes - 1: # O(log2(taille)) - entier gauche = 0; - entier t_tranche = 2**etape - tant que (gauche < taille): # O(taille) - fusion( - tab[gauche..gauche+t_tranche-1], - tab[gauche+t_tranche..gauche+2*t_tranche-1], - tab_tmp[gauche..gauche+2*t_tranche-1]) - gauche += 2*t_tranche - echanger(tab, tab_tmp) -``` - -. . . - -* Au final: $\mathcal{O}(N\log_2(N))$. - -# Complexité algorithmique du quick-sort (1/2) - -## Pseudocode: quicksort - -```python -rien quicksort(entier tableau[], entier ind_min, entier ind_max) - si (longueur(tab) > 1) - ind_pivot = partition(tableau, ind_min, ind_max) - si (longueur(tableau[ind_min:ind_pivot-1]) != 0) - quicksort(tableau, ind_min, pivot_ind - 1) - si (longueur(tableau[ind_pivot+1:ind_max-1]) != 0) - quicksort(tableau, ind_pivot + 1, ind_max) -``` - - - -# Complexité algorithmique du quick-sort (2/2) - -## Quelle est la complexité du tri rapide? - -. . . - -* Pire des cas: $\mathcal{O}(N^2)$ - * Quand le pivot sépare toujours le tableau de façon déséquilibrée ($N-1$ - éléments d'un côté $1$ de l'autre). - * $N$ boucles et $N$ comparaisons $\Rightarrow N^2$. -* Meilleur des cas (toujours le meilleur pivot): $\mathcal{O}(N\cdot \log_2(N))$. - * Chaque fois le tableau est séparé en $2$ parties égales. - * On a $\log_2(N)$ partitions, et $N$ boucles $\Rightarrow N\cdot - \log_2(N)$. -* En moyenne: $\mathcal{O}(N\cdot \log_2(N))$. - - diff --git a/slides/cours_8.md b/slides/cours_8.md new file mode 100644 index 0000000..d998076 --- /dev/null +++ b/slides/cours_8.md @@ -0,0 +1,900 @@ +--- +title: "Tris, et complexité" +date: "2024-11-18" +header-includes: | + \usepackage{xcolor} +--- + +# L'algorithme à la main + +## Exercice *sur papier* + +* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]` + +```C + + + + + + + + + + + + + +``` + +# Le tri par base (radix sort) + +\Huge Le tri par base (radix sort) + +# Tri par base (radix sort) + +* N'utilise pas la notion de comparaisons, mais celle de classement successif dans des catégories (alvéoles). +* Pour simplifier + * Tri de nombre entiers dans un tableau. + * On considère que des nombres $\ge 0$ (sans perte de généralité). + * On considère ensuite la représentation binaire de ces nombres. + +# Principe de l'algorithme + +1. On considère le bit le moins significatif. +2. On parcourt une 1ère fois le tableau et on place à la suite dans un 2ème tableau les éléments dont le bit est 0; + puis on répète l'opération 2 pour les éléments dont le bit est 1. +3. On répète l'étape 2 en regardant le bit suivant et en permutant le rôle des deux tableaux. + +On utilise donc deux tableaux pour réaliser ce tri. +A noter qu'à chaque étape, l'ordre des éléments dont le bit est à 0 (respectivement à 1) reste identique dans le 2ème tableau par rapport au 1er tableau. + +# Illustration sur un exemple (1/6) + +Soit la liste de nombre entier: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 | + +Le plus petit élément est -9. On commence donc par décaler les valeurs de 9. + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 14 | 4 | 10 | 15 | 13 | 3 | 11 | 0 | 11 | + +# Illustration sur un exemple (2/6) + +* Écrivons les éléments en représentation binaire. +* La valeur maximale est 15, on a besoin de 4 bits. + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| +| 14 | 4 | 10 | 15 | 13 | 3 | 11 | 0 | 11 | +| 1110 | 0100 | 1010 | 1111 | 1101 | 0011 | 1011 | 0000 | 1011 | + +# Illustration sur un exemple (3/6) + +* On considère le bit de poids faible + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| +| 111**0** | 010**0** | 101**0** | 111**1** | 110**1** | 001**1** | 101**1** | 000**0** | 101**1** | + +. . . + +* On obtient le tableau: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| +| 111\textcolor{red}{0} | 010\textcolor{red}{0} | 101\textcolor{red}{0} | 111\textcolor{green}{1} | 110\textcolor{green}{1} | 001\textcolor{green}{1} | 101\textcolor{green}{1} | 000\textcolor{red}{0} | 101\textcolor{green}{1} | +| \textcolor{red}{1110} | \textcolor{red}{0100} | \textcolor{red}{1010} | \textcolor{red}{0000} | \textcolor{green}{1111} | \textcolor{green}{1101} | \textcolor{green}{0011} | \textcolor{green}{1011} | \textcolor{green}{1011} | + +# Illustration sur un exemple (4/6) + +* On passe au 2ème bit et on obtient le tableau: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| +| 11\textcolor{green}{1}0 | 01\textcolor{red}{0}0 | 10\textcolor{green}{1}0 | 00\textcolor{red}{0}0 | 11\textcolor{green}{1}1 | 11\textcolor{red}{0}1 | 00\textcolor{green}{1}1 | 10\textcolor{green}{1}1 | 10\textcolor{green}{1}1 | +| \textcolor{red}{0100} | \textcolor{red}{0000} | \textcolor{red}{1101} | \textcolor{green}{1110} | \textcolor{green}{1010} | \textcolor{green}{1111} | \textcolor{green}{0011} | \textcolor{green}{1011} | \textcolor{green}{1011} | + +. . . + +* On passe au 3ème bit et on obtient le tableau: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| +| 0\textcolor{green}{1}00 | 0\textcolor{red}{0}00 | 1\textcolor{green}{1}01 | 1\textcolor{green}{1}10 | 1\textcolor{red}{0}10 | 1\textcolor{green}{1}11 | 0\textcolor{red}{0}11 | 1\textcolor{red}{0}11 | 1\textcolor{red}{0}11 | +| \textcolor{red}{0000} | \textcolor{red}{1010} | \textcolor{red}{0011} | \textcolor{red}{1011} | \textcolor{red}{1011} | \textcolor{green}{0100} | \textcolor{green}{1101} | \textcolor{green}{1110} | \textcolor{green}{1111} | + + +# Illustration sur un exemple (5/6) + +4. On passe au dernier bit et on obtient le tableau final: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| +| \textcolor{red}{0}000 | \textcolor{green}{1}010 | \textcolor{red}{0}011 | \textcolor{green}{1}011 | \textcolor{green}{1}011 | \textcolor{red}{0}100 | \textcolor{green}{1}101 | \textcolor{green}{1}110 | \textcolor{green}{1}111 | +| \textcolor{red}{0000} | \textcolor{red}{0011} | \textcolor{red}{0100} | \textcolor{green}{1010} | \textcolor{green}{1011} | \textcolor{green}{1011} | \textcolor{green}{1101} | \textcolor{green}{1110} | \textcolor{green}{1111} | + +. . . + +* En revenant à la représentation décimale, on a le tableau trié: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| 0 | 3 | 4 | 10 | 11 | 11 | 13 | 14 | 15 | + +# Illustration sur un exemple (6/6) + +* Pour revenir aux valeurs initiales, il faut décaler de 9 dans l'autre sens. + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| -9 | -6 | -5 | 1 | 2 | 2 | 4 | 5 | 6 | + +* Et alors? + +. . . + +* Et alors rien. C'est fini. + + +# Pseudo-code + +```python +rien radix_sort(entier taille, entier tab[taille]): +# initialisation + entier val_min = valeur_min(taille, tab) + entier val_max = valeur_max(taille, tab) + decaler(taille, tab, val_min) + entier nb_bits = nombre_de_bits(val_max - val_min) +# algo + entier tab_tmp[taille] + pour pos de 0 à nb_bits: + alveole_0(taille, tab, tab_tmp, pos) # 0 -> taille + alveole_1(taille, tab, tab_tmp, pos) # taille -> 0 + echanger(tab, tab_tmp) +# post-traitement + decaler(taille, tab, -val_min) +``` +<!-- ```C +void radix_sort(int size,int tab[size]) { + int val_min = tab[index_min(size,tab)]; + int val_max = tab[index_max(size,tab)]; + + decaler(size, tab,val_min); + int nb_bits = get_nb_bits(val_max-val_min); + + int tab_tmp[size]; + for (int pos=0;pos<nb_bits;pos++) { + bucket_0(size,tab,tab_tmp,pos); + bucket_1(size,tab,tab_tmp,pos); + swap(tab,tab_tmp); + } + decaler(size,tab,-val_min); +} +``` --> + +# Un peu plus de détails (1/2) + +## La fonction `decaler()` + +```python +rien decaler(entier taille, entier tab[taille], entier val): + pour i de 0 à taille-1: + taille[i] -= val +``` + +. . . + +## La fonction `echanger()` + +```python +rien echanger(entier tab[], entier tab2[]) +# échanger les tableaux (sans copier les valeurs) +``` + +# Un peu plus de détails (2/2) + +## La fonction `alveole_0()` + +```python +rien alveole_0(entier taille, entier tab[taille], + entier tab_tmp[taille], entier pos): + entier k = 0 + pour i de 0 à taille-1: + si bit(tab[i], pos) == 0: + tab_tmp[k] = tab[i] + k = k + 1 +``` + +. . . + +## La fonction `alveole_1()` + +```python +rien alveole_1(entier taille, entier tab[taille], + entier tab_tmp[taille], entier pos): + # pareil que alveole_0 mais dans l'autre sens +``` + +# Le tri par fusion (merge sort) + +\Huge Le tri par fusion (merge sort) + +# Tri par fusion (merge sort) + +* Tri par comparaison. +* Idée: deux listes triées, sont fusionnées pour donner une liste triée plus longue. +* Itérativement, on trie d'abord les paires de nombres, puis les groupes de 4 nombres, ensuite de 8, et ainsi de suite jusqu'à obtenir un tableau trié. +<!-- * On simplifie ici: le tableau a une longueur de puissance de 2. --> + +<!-- Pour son implémentation, le tri par fusion nécessite d'utiliser une zone temporaire de stockage des données de taille égale à celle de la liste de nombres à trier. On considère le cas du tri d'une liste de nombres entiers stockés dans un tableau. --> + +# Principe de l'algorithme + +* Soit `taille` la taille du tableau à trier. +* Pour `i = 0` à `entier(log2(taille))-1`: + * Fusion des paires de sous-tableaux successifs de taille `2**i` (ou moins pour l'extrémité) + +. . . + +* Remarques: + * Pour l'étape `i`, les sous-tableaux de taille `2**i` sont triés. + * La dernière paire de sous-tableaux peut être incomplète (vide ou avec moins que `2**i` éléments). + +# Exemple de tri par fusion + +* Soit la liste de nombres entiers stockés dans un tableau de taille 9: + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 | + +. . . + +* Fusion des éléments successifs (ce qui revient à les mettre dans l'ordre): + +| étape | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 0 | \textcolor{red}{5} | \textcolor{green}{-5} | \textcolor{red}{1} | \textcolor{green}{6} | \textcolor{red}{4} | \textcolor{green}{-6} | \textcolor{red}{2} | \textcolor{green}{-9} | \textcolor{red}{2} | +| 1 | \textcolor{red}{-5} | \textcolor{red}{5} | \textcolor{green}{1} | \textcolor{green}{6} | \textcolor{red}{-6} | \textcolor{red}{4} | \textcolor{green}{-9} | \textcolor{green}{2} | \textcolor{red}{2} | +| 2 | \textcolor{red}{-5} | \textcolor{red}{1} | \textcolor{red}{5} | \textcolor{red}{6} | \textcolor{green}{-9} | \textcolor{green}{-6} | \textcolor{green}{2} | \textcolor{green}{4} | \textcolor{red}{2} | +| 3 | \textcolor{red}{-9} | \textcolor{red}{-6} | \textcolor{red}{-5} | \textcolor{red}{1} | \textcolor{red}{2} | \textcolor{red}{4} | \textcolor{red}{5} | \textcolor{red}{6} | \textcolor{green}{2} | +| 4 | -9 | -6 | -5 | 1 | 2 | 2 | 4 | 5 | 6 | + + +# Pseudo-code (autrement) + +```python +rien tri_fusion(entier taille, entier tab[taille]) + entier tab_tmp[taille]; + entier nb_etapes = log_2(taille) + 1; + pour etape de 0 a nb_etapes - 1: + entier gauche = 0; + entier t_tranche = 2**etape; + tant que (gauche < taille): + fusion( + tab[gauche..gauche+t_tranche-1], + tab[gauche+t_tranche..gauche+2*t_tranche-1], + tab_tmp[gauche..gauche+2*t_tranche-1]); + #bornes incluses + gauche += 2*t_tranche; + echanger(tab, tab_tmp); +``` + + +# Algorithme de fusion possible + +## Une idée? + +. . . + +* Parcourir les deux tableaux jusqu'à atteindre la fin d'un des deux + * Comparer l'élément courant des 2 tableaux + * Écrire le plus petit élément dans le tableau résultat + * Avancer de 1 dans les tableaux du plus petit élément et résultat +* Copier les éléments du tableau restant dans le tableau résultat + +# La fonction de fusion (pseudo-code autrement) + +\footnotesize + +## Une idée? + +. . . + +```python +# hyp: tab_g et tab_d sont triés +rien fusion(entier tab_g[], entier tab_d[], entier res[]): + entier g = taille(tab_g) + entier d = taille(tab_d) + entier i_g = 0, i_d = 0 + pour i = 0 à g + d: + si i_g < g et i_d < d: + si tab_g[i_g] < tab_d[i_d]: + res[i] = tab_g[i_g] + i_g = i_g + 1 + sinon: + res[i] = tab_d[i_d] + i_d = i_d + 1 + sinon si i_g < g: + res[i] = tab_g[i_g] + i_g = i_g + 1 + sinon si i_d < d: + res[i] = tab_d[i_d] + i_d = i_d + 1 + +``` + +<!-- ## Complexité +L'algorithme présenté précédemment nécessite un certain nombre d'opérations lié à la taille $N$ du tableau. + +Il y a essentiellement $\log_2(N)$ étapes. + +A chaque étape, le tableau est parcouru une fois avec un nombre constant effectué pour chacune des cases du tableau. En effet, l'opération de fusion implique de ne parcourir qu'une seule fois chacun des deux tableaux qu'on fusionne dans un 3ème tableau. + +Ainsi, la complexité du tri par fusion est $\mathcal{O}(N\cdot \log_2(N)$. --> + +# L'efficacité d'un algorithmique + +\Huge L'efficacité d'un algorithmique + +# Efficacité d'un algorithmique + +Comment mesurer l'efficacité d'un algorithme? + +. . . + +* Mesurer le temps CPU, +* Mesurer le temps d'accès à la mémoire, +* Mesurer la place prise mémoire, + +. . . + +Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc! + +## Mesure du temps CPU + +```C +#include <time.h> +struct timespec tstart={0,0}, tend={0,0}; +clock_gettime(CLOCK_MONOTONIC, &tstart); +// some computation +clock_gettime(CLOCK_MONOTONIC, &tend); +printf("computation about %.5f seconds\n", + ((double)tend.tv_sec + 1e-9*tend.tv_nsec) - + ((double)tstart.tv_sec + 1e-9*tstart.tv_nsec)); +``` + +# Programme simple: mesure du temps CPU + +## Preuve sur un [petit exemple](../source_codes/complexity/sum.c) + +```bash +source_codes/complexity$ make bench +RUN ONCE -O0 +the computation took about 0.00836 seconds +RUN ONCE -O3 +the computation took about 0.00203 seconds +RUN THOUSAND TIMES -O0 +the computation took about 0.00363 seconds +RUN THOUSAND TIMES -O3 +the computation took about 0.00046 seconds +``` + +Et sur votre machine les résultats seront **différents**. + +. . . + +## Conclusion + +* Nécessité d'avoir une mesure indépendante du/de la + matériel/compilateur/façon de mesurer/météo. + +# Analyse de complexité algorithmique (1/4) + +* On analyse le **temps** pris par un algorithme en fonction de la **taille de + l'entrée**. + +## Exemple: recherche d'un élément dans une liste triée de taille N + +```C +int sorted_list[N]; +bool in_list = is_present(N, sorted_list, elem); +``` + +* Plus `N` est grand, plus l'algorithme prend de temps sauf si... + +. . . + +* l'élément est le premier de la liste (ou à une position toujours la même). +* ce genre de cas pathologique ne rentre pas en ligne de compte. + +# Analyse de complexité algorithmique (2/4) + +## Recherche linéaire + +```C +bool is_present(int n, int tab[], int elem) { + for (int i = 0; i < n; ++i) { + if (tab[i] == elem) { + return true; + } else if (elem < tab[i]) { + return false; + } + } + return false; +} +``` + +* Dans le **meilleurs des cas** il faut `1` comparaison. +* Dans le **pire des cas** (élément absent p.ex.) il faut `n` comparaisons. + +. . . + +La **complexité algorithmique** est proportionnelle à `N`: on double la taille +du tableau $\Rightarrow$ on double le temps pris par l'algorithme. + +# Analyse de complexité algorithmique (3/4) + +## Recherche dichotomique + +```C +bool is_present_binary_search(int n, int tab[], int elem) { + int left = 0; + int right = n - 1; + while (left <= right) { + int mid = (right + left) / 2; + if (tab[mid] < elem) { + left = mid + 1; + } else if (tab[mid] > elem) { + right = mid - 1; + } else { + return true; + } + } + return false; +} +``` + +# Analyse de complexité algorithmique (4/4) + +## Recherche dichotomique + +](figs/Binary_search_complexity.svg){width=80%} + +. . . + +* Dans le **meilleurs de cas** il faut `1` comparaison. +* Dans le **pire des cas** il faut $\log_2(N)+1$ comparaisons + +. . . + +## Linéaire vs dichotomique + +* $N$ vs $\log_2(N)$ comparaisons logiques. +* Pour $N=1000000$: `1000000` vs `21` comparaisons. + +# Notation pour la complexité + +## Constante de proportionnalité + +* Pour la recherche linéaire ou dichotomique, on a des algorithmes qui sont $\sim N$ ou $\sim \log_2(N)$ +* Qu'est-ce que cela veut dire? + +. . . + +* Temps de calcul est $t=C\cdot N$ (où $C$ est le temps pris pour une comparaisons sur une machine/compilateur donné) +* La complexité ne dépend pas de $C$. + +## Le $\mathcal{O}$ de Leibnitz + +* Pour noter la complexité d'un algorithme on utilise le symbole $\mathcal{O}$ (ou "grand Ô de"). +* Les complexités les plus couramment rencontrées sont + +. . . + +$$ +\mathcal{O}(1),\quad \mathcal{O}(\log(N)),\quad \mathcal{O}(N),\quad +\mathcal{O}(\log(N)\cdot N), \quad \mathcal{O}(N^2), \quad +\mathcal{O}(N^3). +$$ + +# Ordres de grandeur + +\begin{table}[!h] +\begin{center} +\caption{Valeurs approximatives de quelques fonctions usuelles de complexité.} +\medskip +\begin{tabular}{|c|c|c|c|c|} +\hline +$\log_2(N)$ & $\sqrt{N}$ & $N$ & $N\log_2(N)$ & $N^2$ \\ +\hline\hline +$3$ & $3$ & $10$ & $30$ & $10^2$ \\ +\hline +$6$ & $10$ & $10^2$ & $6\cdot 10^2$ & $10^4$ \\ +\hline +$9$ & $31$ & $10^3$ & $9\cdot 10^3$ & $10^6$ \\ +\hline +$13$ & $10^2$ & $10^4$ & $1.3\cdot 10^5$ & $10^8$ \\ +\hline +$16$ & $3.1\cdot 10^2$ & $10^5$ & $1.6\cdot 10^6$ & $10^{10}$ \\ +\hline +$19$ & $10^3$ & $10^6$ & $1.9\cdot 10^7$ & $10^{12}$ \\ +\hline +\end{tabular} +\end{center} +\end{table} + + +# Quelques exercices (1/3) + +## Complexité de l'algorithme de test de primalité naïf? + +```C +for (i = 2; i < sqrt(N); ++i) { + if (N % i == 0) { + return false; + } +} +return true; +``` + +. . . + +## Réponse + +$$ +\mathcal{O}(\sqrt{N}). +$$ + +# Quelques exercices (2/3) + +## Complexité de trouver le minimum d'un tableau? + +```C +int min = MAX; +for (i = 0; i < N; ++i) { + if (tab[i] < min) { + min = tab[i]; + } +} +return min; +``` + +. . . + +## Réponse + +$$ +\mathcal{O}(N). +$$ + +# Quelques exercices (3/3) + +## Complexité du tri par sélection? + +```C +int ind = 0; +while (ind < SIZE-1) { + min = find_min(tab[ind:SIZE]); + swap(min, tab[ind]); + ind += 1; +} +``` + +. . . + +## Réponse + +### `min = find_min` + +$$ +(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2). +$$ + +## Finalement + +$$ +\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{swaps})=\mathcal{O}(N^2). +$$ + + +# Tri à bulle (1/4) + +## Algorithme + +* Parcours du tableau et comparaison des éléments consécutifs: + - Si deux éléments consécutifs ne sont pas dans l'ordre, ils sont échangés. +* On recommence depuis le début du tableau jusqu'à avoir plus d'échanges à + faire. + +## Que peut-on dire sur le dernier élément du tableau après un parcours? + +. . . + +* Le plus grand élément est **à la fin** du tableau. + * Plus besoin de le traiter. +* A chaque parcours on s'arrête un élément plus tôt. + +# Tri à bulle (2/4) + +## Exemple + + + + +# Tri à bulle (3/4) + +## Exercice: écrire l'algorithme (poster le résultat sur matrix) + +. . . + +```C +rien tri_a_bulles(entier tableau[]) + pour i de longueur(tableau)-1 à 1: + trié = vrai + pour j de 0 à i-1: + si (tableau[j] > tableau[j+1]) + échanger(array[j], array[j+1]) + trié = faux + + si trié + retourner +``` + +# Tri à bulle (4/4) + +## Quelle est la complexité du tri à bulles? + +. . . + +* Dans le meilleurs des cas: + * Le tableau est déjà trié: $\mathcal{O}(N)$ comparaisons. +* Dans le pire des cas, $N\cdot (N-1)/2\sim\mathcal{O}(N^2)$: +$$ +\sum_{i=1}^{N-1}i\mbox{ comparaison et }3\sum_{i=1}^{N-1}i \mbox{ affectations +(swap)}\Rightarrow \mathcal{O}(N^2). +$$ +* En moyenne, $\mathcal{O}(N^2)$ ($N^2/2$ comparaisons). + +# L'algorithme à la main + +## Exercice *sur papier* + +* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]` + +```C + + + + + + + + + + + + + +``` + +# Tri par insertion (1/3) + +## But + +* trier un tableau par ordre croissant + +## Algorithme + +Prendre un élément du tableau et le mettre à sa place parmi les éléments déjà +triés du tableau. + + + +# Tri par insertion (2/3) + +## Exercice: Proposer un algorithme (en C) + +. . . + +```C +void tri_insertion(int N, int tab[N]) { + for (int i = 1; i < N; i++) { + int tmp = tab[i]; + int pos = i; + while (pos > 0 && tab[pos - 1] > tmp) { + tab[pos] = tab[pos - 1]; + pos = pos - 1; + } + tab[pos] = tmp; + } +} +``` + +# Tri par insertion (3/3) + +## Question: Quelle est la complexité? + +. . . + +* Parcours de tous les éléments ($N-1$ passages dans la boucle) + * Placer: en moyenne $i$ comparaisons et affectations à l'étape $i$ +* Moyenne: $\mathcal{O}(N^2)$ + +. . . + +* Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$ +* Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$ + +# L'algorithme à la main + +## Exercice *sur papier* + +* Trier par insertion le tableau `[5, -2, 1, 3, 10]` + +```C + + + + + + + + + + + + + +``` + +# Complexité algorithmique du radix-sort (1/2) + +## Pseudo-code + +```python +rien radix_sort(entier taille, entier tab[taille]): +# initialisation + entier val_min = valeur_min(taille, tab) + entier val_max = valeur_max(taille, tab) + decaler(taille, tab, val_min) + entier nb_bits = nombre_de_bits(val_max - val_min) +# algo + entier tab_tmp[taille] + pour pos de 0 à nb_bits: + alveole_0(taille, tab, tab_tmp, pos) # 0 -> taille + alveole_1(taille, tab, tab_tmp, pos) # taille -> 0 + echanger(tab, tab_tmp) +# post-traitement + decaler(taille, tab, -val_min) +``` + +# Complexité algorithmique du radix-sort (2/2) + +\footnotesize + +<!-- Voici une liste de parcours utilitaires de tableau: + +1. Recherche de la valeur minimum ```val_min``` +2. Recherche de la valeur maximum ```val_max``` +3. Décalage des valeurs dans l'intervalle ```0..val_max-val_min``` +4. Décalage inverse pour revenir dans l'intervalle ```val_min..val_max``` +5. Copie éventuelle du tableau temporaire dans le tableau originel + +On a donc un nombre de parcours fixe (4 ou 5) qui se font en $\mathcal{O}(N)$ où $N$ est la taille du tableau. + +La partie du tri à proprement parler est une boucle sur le nombre de bits *b* de ```val_min..val_max```. + +A chaque passage à travers la boucle, on parcourt 2 fois le tableau: la 1ère fois pour s'occuper des éléments dont le bit courant à 0; la 2ème pour ceux dont le bit courant est à 1. + +A noter que le nombre d'opérations est de l'ordre de *b* pour la lecture d'un bit et constant pour la fonction ```swap_ptr()```. + +Ainsi, la complexité du tri par base est $\mathcal{O}(b\cdot N)$. --> + +## Pseudo-code + +```python +rien radix_sort(entier taille, entier tab[taille]): +# initialisation + entier val_min = valeur_min(taille, tab) # O(taille) + entier val_max = valeur_max(taille, tab) # O(taille) + decaler(taille, tab, val_min) # O(taille) + entier nb_bits = + nombre_de_bits(val_max - val_min) # O(nb_bits) +# algo + entier tab_tmp[taille] + pour pos de 0 à nb_bits: # O(nb_bits) + alveole_0(taille, tab, tab_tmp, pos) # O(taille) + alveole_1(taille, tab, tab_tmp, pos) # O(taille) + echanger(tab, tab_tmp) # O(1) +# post-traitement + decaler(taille, tab, -val_min) # O(N) +``` + +. . . + +* Au final: $\mathcal{O}(N\cdot (b+4))$. + +# Complexité algorithmique du merge-sort (1/2) + +## Pseudo-code + +```python +rien tri_fusion(entier taille, entier tab[taille]) + entier tab_tmp[taille]; + entier nb_etapes = log_2(taille) + 1; + pour etape de 0 a nb_etapes - 1: + entier gauche = 0; + entier t_tranche = 2**etape; + tant que (gauche < taille): + fusion( + tab[gauche..gauche+t_tranche-1], + tab[gauche+t_tranche..gauche+2*t_tranche-1], + tab_tmp[gauche..gauche+2*t_tranche-1]); + gauche += 2*t_tranche; + echanger(tab, tab_tmp); +``` + +# Complexité algorithmique du merge-sort (2/2) + +## Pseudo-code + +```python +rien tri_fusion(entier taille, entier tab[taille]) + entier tab_tmp[taille] + entier nb_etapes = log_2(taille) + 1 + pour etape de 0 a nb_etapes - 1: # O(log2(taille)) + entier gauche = 0; + entier t_tranche = 2**etape + tant que (gauche < taille): # O(taille) + fusion( + tab[gauche..gauche+t_tranche-1], + tab[gauche+t_tranche..gauche+2*t_tranche-1], + tab_tmp[gauche..gauche+2*t_tranche-1]) + gauche += 2*t_tranche + echanger(tab, tab_tmp) +``` + +. . . + +* Au final: $\mathcal{O}(N\log_2(N))$. + +# Complexité algorithmique du quick-sort (1/2) + +## Pseudocode: quicksort + +```python +rien quicksort(entier tableau[], entier ind_min, entier ind_max) + si (longueur(tab) > 1) + ind_pivot = partition(tableau, ind_min, ind_max) + si (longueur(tableau[ind_min:ind_pivot-1]) != 0) + quicksort(tableau, ind_min, pivot_ind - 1) + si (longueur(tableau[ind_pivot+1:ind_max-1]) != 0) + quicksort(tableau, ind_pivot + 1, ind_max) +``` + + + +# Complexité algorithmique du quick-sort (2/2) + +## Quelle est la complexité du tri rapide? + +. . . + +* Pire des cas: $\mathcal{O}(N^2)$ + * Quand le pivot sépare toujours le tableau de façon déséquilibrée ($N-1$ + éléments d'un côté $1$ de l'autre). + * $N$ boucles et $N$ comparaisons $\Rightarrow N^2$. +* Meilleur des cas (toujours le meilleur pivot): $\mathcal{O}(N\cdot \log_2(N))$. + * Chaque fois le tableau est séparé en $2$ parties égales. + * On a $\log_2(N)$ partitions, et $N$ boucles $\Rightarrow N\cdot + \log_2(N)$. +* En moyenne: $\mathcal{O}(N\cdot \log_2(N))$. + + + -- GitLab