From 8e9d04d0a56dd083452b58242b45c9afbf109693 Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Mon, 20 Nov 2023 18:57:54 +0100 Subject: [PATCH] maj 2023 --- slides/cours_7.md | 727 ++------------------------------------------ slides/cours_8.md | 755 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 775 insertions(+), 707 deletions(-) create mode 100644 slides/cours_8.md diff --git a/slides/cours_7.md b/slides/cours_7.md index c861511..df1e222 100644 --- a/slides/cours_7.md +++ b/slides/cours_7.md @@ -280,106 +280,6 @@ rien alveole_1(entier taille, entier tab[taille], # pareil que alveole_0 mais dans l'autre sens ``` -<!-- ```C -int index_min(int size,int tab[size],int i) { - //à compléter - return 0; -} - -int index_max(int size,int tab[size],int i) { - //à compléter - return 0; -} - -int get_bit(int x,int pos) { - //à compléter - return 0; -} - -int get_nb_bits(int x) { - //à compléter - return 0; -} - -void swap_ptr(int** a,int** b) { - //à compléter - return 0; -} - -void bucket_0(int size,int* tab1,int* tab2,int pos) { - int k = 0; - for (int i = 0; i < size; i++) { - if (0 == get_bit(tab1[i], pos)) { - tab2[k] = tab1[i]; - k += 1; - } - } -} - -void bucket_1(int size,int* tab1,int* tab2,int pos) { - int k = size - 1; - for (int i = size - 1; i >= 0; i--) { - if (1 == get_bit(tab1[i], pos)) { - tab2[k] = tab1[i]; - k -= 1; - } - } -} - -void radix_sort(int size, int tab[size]) { - int val_min = tab[index_min(size,tab)]; - int val_max = tab[index_max(size,tab)]; - int nb_bits = get_nb_bits(val_max-val_min); - - int tab_tmp[size]; - int* tab1 = &tab[0]; - int* tab2 = &tab_tmp[0]; - // décalage des valeurs du tableau dans l'intervalle 0..val_max-val_min - for (int i=0; i < size; i++) { - tab1[i] -= val_min; - } - - for (int pos=0;pos<nb_bits;pos++) { - bucket_0(size, tab1, tab2, pos); - bucket_1(size, tab1, tab2, pos); - swap_ptr(&tab1, &tab2); - } - - // décalage inverse dans l'intervalle val_min..val_max - for (int i=0;i<size;i++) { - tab1[i] += val_min; - } - - if (tab1 != tab) { - for (int i=0;i<size;i++) { - tab[i] = tab1[i]; - } - } -} -``` --> - -<!-- # Complexité - -L'algorithme implémenté précédemment nécessite un certain nombre d'opérations lié à la taille du tableau. - -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)$. --> - # Tri par fusion (merge sort) * Tri par comparaison. @@ -421,7 +321,8 @@ Ainsi, la complexité du tri par base est $\mathcal{O}(b\cdot N)$. --> | 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 + +# Pseudo-code (autrement) ```python rien tri_fusion(entier taille, entier tab[taille]) @@ -440,10 +341,27 @@ rien tri_fusion(entier taille, entier tab[taille]) echanger(tab, tab_tmp); ``` -# La fonction de fusion + +# 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[]): @@ -653,608 +571,3 @@ int partition(int size, int array[size], int first, int last) { ``` -# 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; - } - } -} -``` diff --git a/slides/cours_8.md b/slides/cours_8.md new file mode 100644 index 0000000..3a263f1 --- /dev/null +++ b/slides/cours_8.md @@ -0,0 +1,755 @@ +--- +title: "Tris et complexité" +date: "2023-11-21" +header-includes: | + \usepackage{xcolor} +--- + +# 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))$. + +# 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; + } + } +} +``` -- GitLab