diff --git a/slides/cours_6.md b/slides/cours_6.md index 4940d6a6232c7c00a03cff1df90e21ea6eede040..53055702e01f6a3ca0e1777bdd4f4d06bd6d985b 100644 --- a/slides/cours_6.md +++ b/slides/cours_6.md @@ -1,5 +1,5 @@ --- -title: "Récursivité" +title: "Récursivité et complexité" date: "2021-11-03" patat: eval: @@ -151,7 +151,7 @@ int fib_imp(int n) { } ``` -# Exponentiation rapide ou indienne (1/N) +# Exponentiation rapide ou indienne (1/4) ## But: Calculer $x^n$ @@ -173,7 +173,7 @@ int fib_imp(int n) { * Complexité? Combien de multiplication en fonction de `n`? -# Exponentiation rapide ou indienne (1/N) +# Exponentiation rapide ou indienne (2/4) * Algorithme naïf et récursif @@ -187,10 +187,293 @@ int fib_imp(int n) { } ``` -# Exercices pour les semaines sans cours +# Exponentiation rapide ou indienne (3/4) + +## Exponentiation rapide ou indienne de $x^n$ + +* Écrivons $n=\sum_{i=0}^{d-1}b_i 2^i,\ b_i=\{0,1\}$ (écriture binaire sur $d$ bits, avec +$d\sim\log_2(n)$). +* +$$ +x^n={x^{2^0}}^{b_0}\cdot {x^{2^1}}^{b_1}\cdots {x^{2^{d-1}}}^{b_{d-1}}. +$$ +* On a besoin de $d$ calculs pour les $x^{2^i}$. +* On a besoin de $d$ calculs pour évaluer les produits de tous les termes. + +## Combien de calculs en terme de $n$? + +. . . + +* $n$ est représenté en binaire avec $d$ bits $\Rightarrow d\sim\log_2(n)$. +* il y a $2\log_2(n)\sim \log_2(n)$ calculs. + +# Exponentiation rapide ou indienne (4/4) + +## Le vrai algorithme + +* Si n est pair: calculer $\left(x^{n/2}\right)^2$, +* Si n est impair: calculer $x \cdot \left(x^{(n-1)/2}\right)^2$. + +## Exercice: écrire l'algorithme récursif correspondant + +. . . + +```C +double pow(double x, int n) { + if (1 == n) { + return x; + } else if (n % 2 == 0) { + return pow(x, n / 2) * pow(x, n/2); + } else { + return x * pow(x, (n-1)); + } +} +``` + + +# 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/N) + +* 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/N) + +## 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/N) + +## 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/N) + +## 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). +$$ + +. . . + +<https://fr.wikipedia.org/wiki/Analyse_de_la_complexit%C3%A9_des_algorithmes> + +# 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 +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 +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). +$$ -## Quelques algorithmes à réaliser et poster sur matrix -1. Algorithme du PPCM. -2. La puissance indienne.