diff --git a/slides/cours_6.md b/slides/cours_6.md index 311016a350f1c2ee2d742fc083e2a097d8a82d30..c8144c8675464730e584871bb45d95070d6a9164 100644 --- a/slides/cours_6.md +++ b/slides/cours_6.md @@ -597,350 +597,3 @@ int fib_imp(int n) { } ``` -# Exponentiation rapide ou indienne (1/4) - -## But: Calculer $x^n$ - -* Quel est l'algorithmie le plus simple que vous pouvez imaginer? - -. . . - -```C -int pow(int x, int n) { - if (0 == n) { - return 1; - } - for (int i = 1; i < n; ++i) { - x = x * x; // x *= x - } - return x; -} -``` - -* Combien de multiplication et d'assignations en fonction de `n`? - -. . . - -* `n` assignations et `n` multiplications. - -# Exponentiation rapide ou indienne (2/4) - -* Proposez un algorithme naïf et récursif - -. . . - -```C -int pow(x, n) { - if (n != 0) { - return x * pow(x, n-1); - } else { - return 1; - } -} -``` - -# 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/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). -$$ - - diff --git a/slides/cours_7.md b/slides/cours_7.md new file mode 100644 index 0000000000000000000000000000000000000000..12e9665bac2f5dd2021fd695b7d5eb2c2d8b6709 --- /dev/null +++ b/slides/cours_7.md @@ -0,0 +1,874 @@ +--- +title: "Récursion et tris" +date: "2022-11-16" +--- + +# Exponentiation rapide ou indienne (1/4) + +## But: Calculer $x^n$ + +* Quel est l'algorithmie le plus simple que vous pouvez imaginer? + +. . . + +```C +int pow(int x, int n) { + if (0 == n) { + return 1; + } + for (int i = 1; i < n; ++i) { + x = x * x; // x *= x + } + return x; +} +``` + +* Combien de multiplication et d'assignations en fonction de `n`? + +. . . + +* `n` assignations et `n` multiplications. + +# Exponentiation rapide ou indienne (2/4) + +* Proposez un algorithme naïf et récursif + +. . . + +```C +int pow(x, n) { + if (n != 0) { + return x * pow(x, n-1); + } else { + return 1; + } +} +``` + +# 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)); + } +} +``` + +# Tri rapide ou quicksort (1/8) + +## Idée: algorithme `diviser pour régner` (`divide-and-conquer`) + +* Diviser: découper un problème en sous problèmes; +* Régner: résoudre les sous-problèmes (souvent récursivement); +* Combiner: à partir des sous problèmes résolu, calculer la solution. + +## Le pivot + +* Trouver le **pivot**, un élément qui divise le tableau en 2, tels que: + 1. Éléments à gauche sont **plus petits** que le pivot. + 2. Élements à droite sont **plus grands** que le pivot. + +# Tri rapide ou quicksort (2/8) + +## Algorithme `quicksort(tableau)` + +1. Choisir le pivot et l'amener à sa place: + * Les éléments à gauche sont plus petits que le pivot. + * Les éléments à droite sont plus grand que le pivot. +2. `quicksort(tableau_gauche)` en omettant le pivot. +3. `quicksort(tableau_droite)` en omettant le pivot. +4. S'il y a moins de deux éléments dans le tableau, le tableau est trié. + +. . . + +Compris? + +. . . + +Non c'est normal, faisons un exemple. + +# Tri rapide ou quicksort (3/8) + +\footnotesize + +Deux variables sont primordiales: + +```C +entier ind_min, ind_max; // les indices min/max des tableaux à trier +``` + + + +# Tri rapide ou quicksort (4/8) + +\footnotesize + +Deux variables sont primordiales: + +```C +entier ind_min, ind_max; // les indices min/max des tableaux à trier +``` + +## 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) +``` + +# Tri rapide ou quicksort (5/8) + +\footnotesize + +## Pseudocode: partition + +```C +entier partition(entier tableau[], entier ind_min, entier ind_max) + pivot = tableau[ind_max] // choix arbitraire + i = ind_min + j = ind_max-1 + tant que i < j: + en remontant i trouver le premier élément > pivot + en descendant j trouver le premier élément < pivot + échanger(tableau[i], tableau[j]) + // les plus grands à droite + // mettre les plus petits à gauche + + // on met le pivot "au milieu" + échanger(tableau[i], tableau[ind_max]) + retourne i // on retourne l'indice pivot +``` + +# Tri rapide ou quicksort (6/8) + +## Exercice: implémenter les fonctions `quicksort` et `partition` + +. . . + +```C +void quicksort(int size, int array[size], int first, + int last) +{ + if (first < last) { + int midpoint = partition(size, array, first, last); + if (first < midpoint - 1) { + quicksort(size, array, first, midpoint - 1); + } + if (midpoint + 1 < last) { + quicksort(size, array, midpoint + 1, last); + } + } +} +``` + + +# Tri rapide ou quicksort (7/8) + +\footnotesize + +## Exercice: implémenter les fonctions `quicksort` et `partition` + +```C +int partition(int size, int array[size], int first, int last) { + int pivot = array[last]; + int i = first - 1, j = last; + do { + do { + i += 1; + } while (array[i] < pivot && i < j); + do { + j -= 1; + } while (array[j] > pivot && i < j); + if (j > i) { + swap(&array[i], &array[j]); + } + } while (j > i); + swap(&array[i], &array[last]); + return i; +} +``` + +# Tri rapide ou quicksort (8/8) + +## Quelle est la complexité du tri rapide? + +. . . + +* Pire des cas plus: $\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))$. + +# L'algorithme à la main + +## Exercice *sur papier* + +* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]` + +```C + + + + + + + + + + + + + +``` + + + +# 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 + + + + + + + + + + + + + +``` + + +# 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 par insertion (1/3) + +## But + +* trier un tableau par ordre croissant + +## Algorithme + +Prendre un élément du tableau et le mettre à sa place parmis 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 + + + + + + + + + + + + + +``` + +# Problème des 8-reines + +* Placer 8 reines sur un échiquier de $8 \times 8$. +* Sans que les reines ne puissent se menacer mutuellement (92 solutions). + +## Conséquence + +* Deux reines ne partagent pas la même rangée, colonne, ou diagonale. +* Donc chaque solution a **une** reine **par colonne** ou **ligne**. + +## Généralisation + +* Placer $N$ reines sur un échiquier de $N \times + N$. +- Exemple de **backtracking** (retour en arrière) $\Rightarrow$ récursivité. + +](./figs/fig_recursivite_8_reines.png){width=35%} + +# Problème des 2-reines + +{width=50%} + +# Comment trouver les solutions? + +* On pose la première reine sur la première case disponible. +* On rend inaccessibles toutes les cases menacées. +* On pose la reine suivante sur la prochaine case non-menacée. +* Jusqu'à ce qu'on puisse plus poser de reine. +* On revient alors en arrière jusqu'au dernier coup où il y avait plus qu'une + possibilité de poser une reine. +* On recommence depuis là . + +. . . + +* Le jeu prend fin quand on a énuméré *toutes* les possibilités de poser les + reines. + +# Problème des 3-reines + + + +# Problème des 4-reines + + + +# Problème des 4-reines, symétrie + + + +# Problème des 5 reines + +## Exercice: Trouver une solution au problème des 5 reines + +* Faire une capture d'écran / une photo de votre solution et la poster sur + matrix. + +```C + + + + + + + + + + + + + +``` + +# Quelques observations sur le problème + +* Une reine par colonne au plus. +* On place les reines sur des colonnes successives. +* On a pas besoin de "regarder en arrière" (on place "devant" uniquement). +* Trois étapes: + * On place une reine dans une case libre. + * On met à jour le tableau. + * Quand on a plus de cases libres on "revient dans le temps" ou c'est qu'on + a réussi. + +# Le code du problème des 8 reines (1/N) + +## Quelle structure de données? + +. . . + +Une matrice de booléens fera l'affaire: + +```C +bool board[n][n]; +``` + +## Quelles fonctionnalités? + +. . . + +```C +// Pour chaque ligne placer la reine sur toutes les colonnes +// et compter les solutions +void nbr_solutions(board, column, counter); +// Copier un tableau dans un autre +void copy(board_in, board_out); +// Placer la reine à li, co et rendre inaccessible devant +void placer_devant(board, li, co); +``` + +# Le code du problème des 8 reines (2/N) + +## Le calcul du nombre de solutions + +```C +// Calcule le nombre de solutions au problème des <n> reines +nbr_solutions(board, column, count) + // pour chaque ligne + // si la case libre + // si column < n - 1 + // copier board dans un "new" board, + // y poser une reine + // et mettre à jour ce "new" board + // nbr_solutions(new_board, column+1, count) + // sinon + // on a posé la n-ème et on a gagné + // count += 1 +``` + +# Le code du problème des 8 reines (3/N) + +## Le calcul du nombre de solutions + +```C +// Placer une reine et mettre à jour +placer_devant(board, ligne, colonne) + // board est occupé à ligne/colonne + // toutes les cases des colonnes + // suivantes sont mises à jour +``` + +# Le code du problème des 8 reines (4/N) + +## Compris? Alors écrivez le code et postez le! + +. . . + +## Le nombre de solutions + +\footnotesize + +```C +// Calcule le nombre de solutions au problème des <n> reines +void nb_sol(int n, bool board[n][n], int co, int *ptr_cpt) { + for (int li = 0; li < n; li++) { + if (board[li][co]) { + if (co < n-1) { + bool new_board[n][n]; // alloué à chaque nouvelle tentative + copy(n, board, new_board); + prises_devant(n, new_board, li, co); + nb_sol(n, new_board, co+1, ptr_cpt); + } else { + *ptr_cpt = (*ptr_cpt)+1; + } + } + } +} +``` + + +# Le code du problème des 8 reines (5/N) + +\footnotesize + +## Placer devant + +```C +// Retourne une copie du tableau <board> complété avec les positions +// prises sur la droite droite par une reine placée en <board(li,co)> +void prises_devant(int n, bool board[n][n], int li, int co) { + board[li][co] = false; // position de la reine + for (int j = 1; j < n-co; j++) { + // horizontale et diagonales à droite de la reine + if (j <= li) { + board[li-j][co+j] = false; + } + board[li][co+j] = false; + if (li+j < n) { + board[li+j][co+j] = false; + } + } +} +```