Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • algorithmique/cours
  • aurelien.boyer/cours
  • jeremy.meissner/cours
  • radhwan.hassine/cours
  • yassin.elhakoun/cours-algo
  • gaspard.legouic/cours
  • joachim.bach/cours
  • gabriel.marinoja/algo-cours
  • loic.lavorel/cours
  • iliya.saroukha/cours
  • costanti.volta/cours
  • jacquesw.ndoumben/cours
12 results
Select Git revision
Show changes
Showing
with 3885 additions and 1458 deletions
---
title: "Théorie des graphes et plus courts chemins"
date: "2025-05-26"
---
# Les graphes
\Huge
Les graphes
# Exercice
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en largeur au graphe
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---3;
1---4;
2---3;
2---6;
3---6;
3---4;
3---5;
4---5;
```
# Illustration: parcours en profondeur
![Le parcours en profondeur. À quel parcours d'arbre cela ressemble-t-il?](figs/parcours_prof.pdf){width=80%}
# Parcours en profondeur
## Idée générale
* Initialiser les sommets comme non-lus
* Visiter un sommet
* Pour chaque sommet visité, on visite un sommet adjacent s'il n'est pas encore visité, et ce récursivement.
## Remarque
* La récursivité est équivalente à l'utilisation d'une **pile**.
# Parcours en profondeur
## Pseudo-code (5min)
. . .
```C
initialiser(graphe) // tous les sommets sont non-visités
visiter(sommet, pile) // on choisit un sommet du graphe
tant que !est_vide(pile)
dépiler(pile, (v,u))
si u != visité
ajouter (v,u) à arbre T
visiter(u, pile)
```
## Que fait visiter?
. . .
```C
rien visiter(x, pile)
marquer x comme visité
pour chaque arête (x,w)
si w != visité
empiler(pile, (x,w))
```
# Exercice
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en profondeur au graphe
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---3;
1---4;
2---3;
2---6;
3---6;
3---4;
3---5;
4---5;
```
# Interprétation des parcours
* Un graphe vu comme espace d'états (sommet: état, arête: action);
* Labyrinthe;
* Arbre des coups d'un jeu.
. . .
* BFS (Breadth-First) ou DFS (Depth-First) parcourent l'espace des états à la recherche du meilleur mouvement.
* Les deux parcourent *tout* l'espace;
* Mais si l'arbre est grand, l'espace est gigantesque!
. . .
* Quand on a un temps limité
* BFS explore beaucoup de coups dans un futur proche;
* DFS explore peu de coups dans un futur lointain.
# Algorithmes de plus courts chemins
\Huge Plus courts chemins
# Contexte: les réseaux (informatique, transport, etc.)
* Graphe orienté;
* Source: sommet `s`;
* Destination: sommet `t`;
* Les arêtes ont des poids (coût d'utilisation, distance, etc.);
* Le coût d'un chemin est la somme des poids des arêtes d'un chemin.
## Problème à résoudre
* Quel est le plus court chemin entre `s` et `t`?
# Exemples d'application de plus courts chemins
## Devenir riches!
* On part d'un tableau de taux de change entre devises.
* Quelle est la meilleure façon de convertir l'or en dollar?
![Taux de change.](figs/taux_change.pdf){width=80%}
. . .
* 1kg d'or => 327.25 dollars
* 1kg d'or => 208.1 livres => 327 dollars
* 1kg d'or => 455.2 francs => 304.39 euros => 327.28 dollars
# Exemples d'application de plus courts chemins
## Formulation sous forme d'un graphe: Comment (3min)?
![Taux de change.](figs/taux_change.pdf){width=80%}
# Exemples d'application de plus courts chemins
## Formulation sous forme d'un graphe: Comment (3min)?
![Graphes des taux de change.](figs/taux_change_graphe.pdf){width=60%}
* Un sommet par devise;
* Une arête orientée par transaction possible avec le poids égal au taux de change;
* Trouver le chemin qui maximise le produit des poids.
. . .
## Problème
* On aimerait plutôt avoir une somme...
# Exemples d'application de plus courts chemins
## Conversion du problème en plus courts chemins
* Soit `taux(u, v)` le taux de change entre la devise `u` et `v`.
* On pose `w(u,w)=-log(taux(u,v))`
* Trouver le chemin poids minimal pour les poids `w`.
![Graphe des taux de change avec logs.](figs/taux_change_graphe_log.pdf){width=60%}
* Cette conversion se base sur l'idée que
$$
\log(u\cdot v)=\log(u)+\log(v).
$$
# Applications de plus courts chemins
## Quelles applications voyez-vous?
. . .
* Déplacement d'un robot;
* Planificaiton de trajet / trafic urbain;
* Routage de télécommunications;
* Réseau électrique optimal;
* ...
# Algorithmes de plus courts chemins
\Huge
Algorithmes de plus courts chemins
# Rappel de la problématique
* Graphe orienté;
* Source: sommet `s`;
* Destination: sommet `t`;
* Les arêtes ont des poids (coût d'utilisation, distance, etc.);
* Le coût d'un chemin est la somme des poids des arêtes d'un chemin.
## Problème à résoudre
* Quel est le plus court chemin entre `s` et `t`.
# Plus courts chemins à source unique
* Soit un graphe, $G=(V, E)$, une fonction de pondération $w:E\rightarrow\mathbb{R}$, et un sommet $s\in V$
* Trouver pour tout sommet $v\in V$, le chemin de poids minimal reliant $s$ à $v$.
* Algorithmes standards:
* Dijkstra (arêtes de poids positif seulement);
* Bellman-Ford (arêtes de poids positifs ou négatifs, mais sans cycles négatifs).
* Comment résoudre le problèmes si tous les poids sont les mêmes?
. . .
* Un parcours en largeur!
# Algorithme de Dijkstra
## Comment chercher pour un plus court chemin?
. . .
```
si distance(u,v) > distance(u,w) + distance(w,v)
on passe par w plutôt qu'aller directement
```
# Algorithme de Dijkstra (1 à 5)
* $D$ est le tableau des distances au sommet $1$: $D[7]$ est la distance de 1 à 7.
* Le chemin est pas forcément direct.
* $S$ est le tableau des sommets visités.
::: columns
:::: column
![Initialisation.](figs/dijkstra_0.png)
::::
:::: column
. . .
![1 visité, `D[2]=1`, `D[4]=3`.](figs/dijkstra_1.png)
::::
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est 2.](figs/dijkstra_1.png)
::::
:::: column
. . .
![2 visité, `D[3]=2`, `D[7]=3`.](figs/dijkstra_2.png)
::::
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est 3.](figs/dijkstra_2.png)
::::
:::: column
. . .
![3 visité, `D[7]=3` inchangé, `D[6]=6`.](figs/dijkstra_3.png)
::::
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est 4 ou 7.](figs/dijkstra_3.png)
::::
:::: column
. . .
![4 visité, `D[7]=3` inchangé, `D[5]=9`.](figs/dijkstra_4.png)
::::
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est `7`.](figs/dijkstra_4.png)
::::
:::: column
. . .
![7 visité, `D[5]=7`, `D[6]=6` inchangé.](figs/dijkstra_5.png)
::::
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est 6.](figs/dijkstra_5.png)
::::
:::: column
. . .
![`6` visité, `D[5]=7` inchangé.](figs/dijkstra_6.png)
::::
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est 5 et c'est la cible.](figs/dijkstra_6.png)
::::
:::: column
. . .
![The end, tous les sommets ont été visités.](figs/dijkstra_7.png)
::::
:::
# Algorithme de Dijkstra
## Idée générale
* On assigne à chaque noeud une distance $0$ pour $s$, $\infty$ pour les autres.
* Tous les noeuds sont marqués non-visités.
* Depuis le noeud courant, on suit chaque arête du noeud vers un sommet non visité et on calcule le poids du chemin à chaque voisin et on met à jour sa distance si elle est plus petite que la distance du noeud.
* Quand tous les voisins du noeud courant ont été visités, le noeud est mis à visité (il ne sera plus jamais visité).
* Continuer avec le noeud à la distance la plus faible.
* L'algorithme est terminé losrque le noeud de destination est marqué comme visité, ou qu'on n'a plus de noeuds qu'on peut visiter et que leur distance est infinie.
# Algorithme de Dijkstra
## Pseudo-code (5min, matrix)
\footnotesize
. . .
```C
tab dijkstra(graph, s, t)
pour chaque v dans graphe
distance[v] = infini
distance[s] = 0
q = ajouter(q, s) // q est une liste
tant que non_vide(q)
// sélection de u t.q. la distance dans q est min
u = min(q, distance)
si u == t // on a atteint la cible
retourne distance
q = remove(q, u)
// voisin de u encore dans q
pour chaque v dans voisinage(u, q)
// on met à jour la distance du voisin en passant par u
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
retourne distance
```
# Algorithme de Dijkstra
* Cet algorithme, nous donne le plus court chemin mais...
* ne nous donne pas le chemin!
## Comment modifier l'algorithme pour avoir le chemin?
. . .
* Pour chaque nouveau noeud à visiter, il suffit d'enregistrer d'où on est venu!
* On a besoin d'un tableau `precedent`.
## Modifier le pseudo-code ci-dessus pour ce faire (3min matrix)
# Algorithme de Dijkstra
\footnotesize
```C
tab, tab dijkstra(graph, s, t)
pour chaque v dans graphe
distance[v] = infini
precedent[v] = indéfini
distance[s] = 0
q = ajouter(q, s)
tant que non_vide(q)
// sélection de u t.q. la distance dans q est min
u = min(q, distance)
si u == t
retourne distance, precedent
q = remove(q, u)
// voisin de u encore dans q
pour chaque v dans voisinage(u, q)
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
precedent[v] = u
retourne distance, precedent
```
# Algorithme de Dijkstra
## Comment reconstruire un chemin ?
. . .
```C
pile parcours(precedent, s, t)
sommets = vide
u = t
// on a atteint t ou on ne connait pas de chemin
si u != s && precedent[u] != indéfini
tant que vrai
sommets = empiler(sommets, u)
u = precedent[u]
si u == s // la source est atteinte
retourne sommets
retourne sommets
```
# Algorithme de Dijkstra amélioré
## On peut améliorer l'algorithme
* Avec une file de priorité!
## Une file de priorité est
* Une file dont chaque élément possède une priorité,
* Elle existe en deux saveurs: `min` ou `max`:
* File `min`: les éléments les plus petits sont retirés en premier.
* File `max`: les éléments les plus grands sont retirés en premier.
* On regarde l'implémentation de la `max`.
## Comment on fait ça?
. . .
* On insère les éléments à haute priorité tout devant dans la file!
# Les files de priorité
## Trois fonction principales
```C
booléen est_vide(element) // triviale
element enfiler(element, data, priorite)
data defiler(element)
rien changer_priorite(element, data, priorite)
nombre priorite(element) // utilitaire
```
## Pseudo-implémentation: structure (1min)
. . .
```C
struct element
data
priorite
element suivant
```
# Les files de priorité
## Pseudo-implémentation: enfiler (2min)
. . .
```C
element enfiler(element, data, priorite)
n_element = creer_element(data, priorite)
si est_vide(element)
retourne n_element
si priorite(n_element) > priorite(element)
n_element.suivant = element
retourne n_element
sinon
tmp = element
prec = element
tant que !est_vide(tmp)
&& priorite(n_element) < priorite(tmp)
prec = tmp
tmp = tmp.suivant
prec.suivant = n_element
n_element.suivant = tmp
retourne element
```
# Les files de priorité
## Pseudo-implémentation: defiler (2min)
. . .
```C
data, element defiler(element)
si est_vide(element)
retourne AARGL!
sinon
tmp = element.data
n_element = element.suivant
liberer(element)
retourne tmp, n_element
```
# Algorithme de Dijkstra avec file de priorité min
```C
distance, precedent dijkstra(graphe, s, t):
fp = file_p_vide()
distance[s] = 0
pour v dans sommets(graphe)
si v != s
distance[v] = infini
precedent[v] = indéfini
fp = enfiler(fp, v, distance[v])
tant que !est_vide(fp)
u, fp = defiler(fp)
si u == t
retourne distance, precedent
pour v dans voisinage de u
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
precedent[v] = u
fp = changer_priorite(fp, v, n_distance)
retourne distance, precedent
```
# Algorithme de Dijkstra avec file
\footnotesize
```C
distance dijkstra(graphe, s, t)
--------------------O(V*V)--------------------------------
distance[s] = 0
fp = file_p_vide()
pour v dans sommets(graphe) // O(|V|)
si v != s
distance[v] = infini
fp = enfiler(fp, s, distance[s]) // O(|V|)
------------------O(V * V)-------------------------------
tant que !est_vide(fp)
u, fp = defiler(fp) // O(1)
---------------------------------------------------------
pour v dans voisinage de u // O(|E|)
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
fp = changer_priorite(fp, v, n_distance) // O(|V|)
---------------------------------------------------------
retourne distance
```
* Total: $\mathcal{O}(|V|^2+|E|\cdot |V|)$:
* Graphe dense: $\mathcal{O}(|V|^3)$
* Graphe peu dense: $\mathcal{O}(|V|^2)$
# Algorithme de Dijkstra avec file
## On peut faire mieux
* Avec une meilleure implémentation de la file de priorité:
* Tas binaire: $\mathcal{O}(|V|\log|V|+|E|\log|V|)$.
* Tas de Fibonnacci: $\mathcal{O}(|V|+|E|\log|V|)$
* Graphe dense: $\mathcal{O}(|V|^2\log|V|)$.
* Graphe peu dense: $\mathcal{O}(|V|\log|V|)$.
# Algorithme de Dijkstra (exercice, 5min)
![L'exercice.](figs/dijkstra_exo.png){width=60%}
* Donner la liste de priorité, puis...
## A chaque étape donner:
* Le tableau des distances à `a`;
* Le tableau des prédécesseurs;
* L'état de la file de priorité.
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 1.](figs/dijkstra_ex_0.png)
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 2.](figs/dijkstra_ex_1.png)
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 3.](figs/dijkstra_ex_2.png)
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 4.](figs/dijkstra_ex_3.png)
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 5.](figs/dijkstra_ex_4.png)
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 6.](figs/dijkstra_ex_5.png)
# Limitation de l'algorithme de Dijkstra
## Que se passe-t-il pour?
![Exemple.](figs/exemple_neg.png){width=50%}
## Quel est le problème?
. . .
* L'algorithme n'essaiera jamais le chemin `s->x->y->v` et prendra direct `s->v`.
* Ce problème n'apparaît que s'il y a des poids négatifs.
# Plus cours chemin pour toute paire de sommets
## Comment faire pour avoir toutes les paires?
. . .
* Appliquer Dijkstra sur tous les sommets d'origine.
* Complexité:
* Graphe dense: $\mathcal{O}(|V|)\mathcal{O}(|V|^2\log|V|)=\mathcal{O}(|V|^3\log|V|)$.
* Graphe peu dense: $\mathcal{O}(|V|)\mathcal{O}(|V|\log|V|)=\mathcal{O}(|V|^2\log|V|)$.
. . .
## Solution alternative: Floyd--Warshall
* Pour toutes paires de sommets $u,v\in V$, trouver le chemin de poids minimal reliant $u$ à $v$.
* Complexité $\mathcal{O}(|V|^3)$, indiqué pour graphes denses.
* Fonctionne avec la matrice d'adjacence.
# Algorithme de Floyd--Warshall
## Idée générale
* Soit l'ensemble de sommets $V=\{1, 2, 3, 4, ..., n\}$.
* Pour toute paire de sommets, $i,j$, on considère tous les chemins passant par les sommets intermédiaires $\in\{1, 2, ..., k\}$ avec $k\leq n$.
* On garde pour chaque $k$ la plus petite valeur.
## Principe
* A chaque étape, $k$, on vérifie s'il est plus court d'aller de $i$ à $j$ en passant par le sommet $k$.
* Si à l'étape $k-1$, le coût du parcours est $p$, on vérifie si $p$ est plus petit que $p_1+p_2$, le chemin de $i$ à $k$, et $k$ à $j$ respectivement.
# Algorithme de Floyd--Warshall
## The algorithme
Soit $d_{ij}(k)$ le plus court chemin de $i$ à $j$ passant par les sommets $\in\{1,2,...,k\}$
$$
d_{ij}(k)=\left\{
\begin{array}{ll}
w(i,j), & \mbox{si } k=0,\\
\min(d_{ij}(k-1),d_{ik}(k-1)+d_{kj}(k-1)), & \mbox{sinon}.
\end{array}
\right.
$$
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
![Le graphe, $D=w$.](figs/floyd_exemple.png)
::::
:::: column
## Que vaut $D^{(0)}$ (3min)?
. . .
$$
D^{(0)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 8 & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \infty & \infty & 0 & 5 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(0)}$?
$$
D^{(0)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 8 & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \infty & \infty & 0 & 5 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::: column
## Que vaut $D^{(1)}$ (3min)?
. . .
$$
D^{(1)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & \mathbf{6} & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \mathbf{3} & \mathbf{5} & 0 & \mathbf{4} \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(0)}$
$$
D^{(0)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 8 & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \infty & \infty & 0 & 5 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::: column
## Que vaut $D^{(1)}$ (3min)?
. . .
$$
D^{(1)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & \mathbf{6} & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \mathbf{3} & \mathbf{5} & 0 & \mathbf{4} \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
## Exemple
$$
D_{42}^{(1)}=D_{41}^{(0)}+D_{12}^{(0)}=1+2<\infty.
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(1)}$
$$
D^{(1)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 6 & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::: column
## Que vaut $D^{(2)}$ (3min)?
. . .
$$
D^{(2)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 6 & \infty & 1 \\
\mathbf{4} & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(2)}$
$$
D^{(2)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 6 & \infty & 1 \\
4 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::: column
## Que vaut $D^{(3)}$ (3min)?
. . .
$$
D^{(3)}=\begin{bmatrix}
0 & 2 & 4 & \mathbf{8} & 3 \\
2 & 0 & 6 & \mathbf{10} & 1 \\
4 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(3)}$
$$
D^{(3)}=\begin{bmatrix}
0 & 2 & 4 & 8 & 3 \\
2 & 0 & 6 & 10 & 1 \\
4 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::: column
## Que vaut $D^{(4)}$ (3min)?
. . .
$$
D^{(4)}=\begin{bmatrix}
0 & 2 & 4 & 8 & 3 \\
2 & 0 & 6 & 10 & 1 \\
4 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
\mathbf{2} & \mathbf{4} & \mathbf{6} & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(4)}$
$$
D^{(4)}=\begin{bmatrix}
0 & 2 & 4 & 8 & 3 \\
2 & 0 & 6 & 10 & 1 \\
4 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
2 & 4 & 6 & 1 & 0 \\
\end{bmatrix}
$$
::::
:::: column
## Que vaut $D^{(5)}$ (3min)?
. . .
$$
D^{(5)}=\begin{bmatrix}
0 & 2 & 4 & \mathbf{4} & 3 \\
2 & 0 & 6 & \mathbf{2} & 1 \\
4 & 2 & 0 & 4 & 3 \\
1 & 3 & 5 & 0 & 4 \\
2 & 4 & 6 & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall
## The pseudo-code (10min)
* Quelle structure de données?
* Quelle initialisation?
* Quel est le code pour le calcul de la matrice $D$?
# Algorithme de Floyd--Warshall
## The pseudo-code
* Quelle structure de données?
```C
int distance[n][n];
```
. . .
* Quelle initialisation?
```C
matrice ini_floyd_warshall(distance, n, w)
pour i de 1 à n
pour j de 1 à n
distance[i][j] = w(i,j)
retourne distance
```
# Algorithme de Floyd--Warshall
## The pseudo-code
* Quel est le code pour le calcul de la matrice $D$?
```C
matrice floyd_warshall(distance, n, w)
pour k de 1 à n
pour i de 1 à n
pour j de 1 à n
distance[i][j] = min(distance[i][j],
distance[i][k] + distance[k][j])
retourne distance
```
# Algorithme de Floyd--Warshall
## La matrice de précédence
* On a pas encore vu comment reconstruire le plus court chemin!
* On définit, $P_{ij}^{(k)}$, qui est le prédécesseur du sommet $j$ depuis $i$ avec les sommets intermédiaires $\in\{1, 2, ..., k\}$.
$$
P^{(0)}_{ij}=\left\{
\begin{array}{ll}
\mbox{vide}, & \mbox{si } i=j\mbox{, ou }w(i,j)=\infty\\
i, & \mbox{sinon}.
\end{array}
\right.
$$
* Mise à jour
$$
P^{(k)}_{ij}=\left\{
\begin{array}{ll}
P^{(k-1)}_{\mathbf{i}j}, & \mbox{si } d_{ij}^{(k)}\leq d_{ik}^{(k-1)}+d_{kj}^{(k-1)}\\
P^{(k-1)}_{\mathbf{k}j}, & \mbox{sinon}.
\end{array}
\right.
$$
. . .
* Moralité: si le chemin est plus court en passant par $k$, alors il faut utiliser son prédécesseur!
# Algorithme de Floyd--Warshall
## La matrice de précédence (pseudo-code, 3min)
. . .
```C
matrice, matrice floyd_warshall(distance, n, w)
pour k de 1 à n
pour i de 1 à n
pour j de 1 à n
n_distance = distance[i][k] + distance[k][j]
if n_distance < distance[i][j]
distance[i][j] = n_distance
précédence[i][j] = précédence[k][j]
retourne distance, précédence
```
# Algorithme de Floyd--Warshall (exercice)
::: columns
:::: column
![Le graphe, $D=w$.](figs/floyd_exemple.png)
::::
:::: column
## Que vaut $P^{(0)}$ (3min)?
. . .
$$
P^{(0)}=\begin{bmatrix}
- & 1 & 1 & - & 1 \\
2 & - & 2 & - & 2 \\
3 & 3 & - & 3 & 3 \\
4 & - & - & - & 4 \\
- & - & - & 5 & - \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exercice)
::: columns
:::: column
![Le graphe, $D=w$.](figs/floyd_exemple.png)
::::
:::: column
## Que vaut $P^{(5)}$ (10min)?
. . .
$$
P^{(5)}=\begin{bmatrix}
- & 1 & 1 & 5 & 1 \\
2 & - & 1 & 5 & 2 \\
2 & 3 & - & 3 & 3 \\
4 & 1 & 1 & - & 1 \\
4 & 1 & 1 & 5 & - \\
\end{bmatrix}
$$
::::
:::
# Exercice: retrouver le chemin entre 1 et 4 (5min)
$$
P=\begin{bmatrix}
- & 1 & 1 & 5 & 1 \\
2 & - & 1 & 5 & 2 \\
2 & 3 & - & 3 & 3 \\
4 & 1 & 1 & - & 4 \\
4 & 1 & 1 & 5 & - \\
\end{bmatrix}
$$
. . .
## Solution
* Le sommet $5=P_{14}$, on a donc, $5\rightarrow 4$, on veut connaître le prédécesseur de 5.
* Le sommet $1=P_{15}$, on a donc, $1\rightarrow 5\rightarrow 4$. The end.
# Exercice complet
## Appliquer l'algorithme de Floyd--Warshall au graphe suivant
![The exorcist.](figs/floyd_exercice.png){width=50%}
* Bien indiquer l'état de $D$ et $P$ à chaque étape!
* Ne pas oublier de faire la matrice d'adjacence évidemment...
---
title: "Arbres couvrants"
date: "2025-06-06"
---
# Arbres couvrants
\Huge Les arbres couvrants
# Trouver un réseau électrique pour
![Ces maisons n'ont pas d'électricité.](figs/arbre_couvrant_vide.png)
# Solution: pas optimale
![Le réseau simple, mais nul.](figs/arbre_couvrant_mal.png)
* La longueur totale des câbles est super longue!
# Solution: optimale
![Le meilleur réseau.](figs/arbre_couvrant_bien.png)
# Formalisation: Les arbres couvrants
## Application: minimisation des coûts
* Équipement d'un lotissement avec des lignes électriques/téléphoniques, des canalisations, ...
. . .
* Pour réduire les coûts, on cherche à minimiser la longueur totale des câbles/tuyaux.
. . .
* Les lignes/tuyaux forment un *arbre couvrant*.
. . .
* La meilleure option est un *arbre couvrant minimal*.
# Formalisation: Les arbres couvrants
* Qu'est-ce qu'un arbre couvrant? Des idées? De quel objet part-on? Où va-t-on?
. . .
* Un arbre couvrant d'un graphe non-orienté et connexe est:
* un arbre inclus dans le graphe qui connecte tous les sommets du graphe.
. . .
![Exemple d'arbres couvrants d'un graphe connexe.](figs/arbre_couvrant_exemples.png)
# Arbres couvrants
* Quels algorithmes que nous avons déjà vus, permettent de construire des arbres couvrants?
. . .
* Les parcours en largeur et en profondeur!
. . .
![Graphe et parcours comme arbres couvrants.](figs/arbres_couvrants_parcours.png)
# Arbres couvrants minimaux
* Un *arbre couvrant minimal* est un sous-graphe d'un graphe non-orienté pondéré $G(V,E)$ tel quel:
* C'est un arbre (graphe acyclique);
* Il couvre tous les sommets de $G$ et contient $|V|-1$ arêtes;
* Le coût total associé aux arêtes de l'arbre est minimum parmi tous les arbres couvrants possibles.
. . .
* Est-il unique?
. . .
* Pas forcément.
# Arbres couvrants minimaux
* Comment générer un arbre couvrant minimal?
![Un graphe, connexe, non-orienté, pondéré, et un arbre couvrant minimal.](figs/arbre_couvrant_minimal_exemple.png)
# Algorithme de Prim
::: columns
:::: column
## Un exemple
![Le graphe de départ.](figs/prim_0.png)
::::
:::: column
## On part de `e` (au hasard)
![Le sommet `e` est couvert.](figs/prim_1.png)
::::
:::
# Algorithme de Prim
::: columns
:::: column
## On choisit comment?
![Quelle arête choisir?](figs/prim_1.png)
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
::::
:::: column
. . .
## L'arête `e->d`
![Le sommet `d` est couvert.](figs/prim_2.png)
::::
:::
# Algorithme de Prim
::: columns
:::: column
## On choisit comment?
![Quelle arête choisir?](figs/prim_2.png)
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
::::
:::: column
. . .
## L'arête `d->a`
![Le sommet `a` est couvert.](figs/prim_3.png)
::::
:::
# Algorithme de Prim
::: columns
:::: column
## On choisit comment?
![Quelle arête choisir?](figs/prim_3.png)
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
::::
:::: column
. . .
## L'arête `d->c`
![Le sommet `c` est couvert.](figs/prim_4.png)
::::
:::
# Algorithme de Prim
::: columns
:::: column
## On choisit comment?
![Quelle arête choisir?](figs/prim_4.png)
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
::::
:::: column
. . .
## L'arête `e->b`
![Le sommet `b` est couvert.](figs/prim_5.png)
* Game over!
::::
:::
# Exemple d'algorithme de Prim
::: columns
:::: {.column width="40%"}
## Un exemple
![Étape 1.](figs/prim_1.png)
::::
:::: column
```
FP | e | d | b | c | a |
----------------------------------
D | 0 | inf | inf | inf | inf |
| e | d | b | c | a |
----------------------------------
P | - | - | - | - | - |
```
## Devient?
. . .
```
FP | d | b | c | a |
----------------------------
D | 4 | 5 | 5 | inf |
| e | d | b | c | a |
----------------------------------
P | - | e | e | e | - |
```
::::
:::
# Exemple d'algorithme de Prim
::: columns
:::: {.column width="40%"}
## Un exemple
![Étape 2.](figs/prim_2.png)
::::
:::: column
```
FP | d | b | c | a |
----------------------------
D | 4 | 5 | 5 | inf |
| e | d | b | c | a |
----------------------------------
P | - | e | e | e | - |
```
## Devient?
. . .
```
FP | a | c | b |
----------------------
D | 2 | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Exemple d'algorithme de Prim
::: columns
:::: {.column width="40%"}
## Un exemple
![Étape 3.](figs/prim_3.png)
::::
:::: column
```
FP | a | c | b |
----------------------
D | 2 | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
## Devient?
. . .
```
FP | c | b |
----------------
D | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Exemple d'algorithme de Prim
::: columns
:::: {.column width="40%"}
## Un exemple
![Étape 4.](figs/prim_4.png)
::::
:::: column
```
FP | c | b |
----------------
D | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
## Devient?
. . .
```
FP | b |
----------
D | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Exemple d'algorithme de Prim
::: columns
:::: {.column width="40%"}
## Un exemple
![Étape 5.](figs/prim_4.png)
::::
:::: column
```
FP | b |
----------
D | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
## Devient?
. . .
```
FP |
----
D |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Algorithme de Prim
## Structures de données
* Dans quoi allons-nous stocker les sommets?
. . .
* File de priorité min.
* Autre chose?
. . .
* Tableau des distances (comme pour Dijkstra).
* Autre chose?
. . .
* Tableau des parents (presque comme pour Dijkstra).
* Autre chose?
. . .
* Non.
# Algorithme de Prim
## Initialisation: Pseudo-code (2min)
. . .
```C
file_priorité, distance, parent initialisation(graphe)
s_initial = aléatoire(graphe)
distance[s_initial] = 0
fp = file_p_vide()
pour s_courant dans sommets(graphe)
si s_courant != s_initial
distance[s_courant] = infini
parent[s_courant] = indéfini
fp = enfiler(fp, s_courant, distance[s_courant])
retourne fp, distance, parent
```
# Algorithme de Prim
\footnotesize
## Algorithme: Pseudo-code (5min)
. . .
```C
distance, parent prim(graphe)
fp, distance, parent initialisation(graphe)
sommets = vide
tant que !est_vide(fp)
s_courant, fp = défiler(fp)
sommets = insérer(sommets, s_courant)
pour s_voisin dans voisinage(s_courant) et pas dans sommets
// ou dans fp
si poids(s_courant, s_voisin) < distance[s_voisin]
parent[s_voisin] = s_courant
distance[s_voisin] = poids(s_courant, s_voisin)
fp = changer_priorité(fp, s_voisin,
poids(s_courant, s_voisin))
retourne distance, parent
```
# Exercice: algorithme de Prim
## Appliquer l'algorithme de Prim à (15min):
![En démarrant du sommet $V_1$.](figs/prim_exercice.png)
# Exercice: algorithme de Prim
## Solution
![](figs/prim_solution.png)
# Complexité de l'algorithme de Prim
\footnotesize
```C
file_priorité, distance, parent initialisation(graphe)
// choix r et initialisation
pour v dans sommets(graphe)
// initialisation distance et parent en O(|V|)
fp = enfiler(fp, v, distance[v])
retourne fp, distance, parent
distance, parent prim(graphe)
fp, distance, parent initialisation(graphe) // O(|V|)
sommets = vide
tant que !est_vide(fp)
u, fp = défiler(fp) // O(|V|)
sommets = insérer(sommets, u)
pour v dans voisinage de u et pas dans sommets
si poids(u, v) < distance[v] // O(|E|)
// màj distance + parent
fp = changer_priorité(fp, v, poids(u, v)) // O(|V|)
retourne distance, parent
```
* $O(|V|)+O(|E|)+O(|V|^2)=O(|E|+|V|^2)$
* Remarque: $O(|E|)$ n'est pas mutliplié par $O(|V|)$, car les arêtes ne sont traitées qu'une fois en **tout**.
# Algorithme de Kruskal
* On ajoute les arêtes de poids minimal:
* si cela ne crée pas de cycle;
* on s'arrête quand on a couvert tout le graphe.
. . .
* Comment on fait ça?
. . .
* Faisons un exemple pour voir.
# Algorithme de Kruskal: exemple
::: columns
:::: column
## Un exemple
![Le graphe de départ.](figs/kruskal_0.png)
::::
:::: column
## On part de `(a, d)` (poids le plus faible)
![Les sommets `a, d` sont couverts.](figs/kruskal_1.png)
::::
:::
# Algorithme de Kruskal: exemple
::: columns
:::: column
## On continue avec `(c, d)`
![On aurait pu choisir `(d, e)` aussi.](figs/kruskal_1.png)
::::
:::: column
## Résultat
![Les sommets `a, d, c` sont couverts.](figs/kruskal_2.png)
::::
:::
# Algorithme de Kruskal: exemple
::: columns
:::: column
## On continue avec `(d, e)`
![Le poids de `(d, e)` est le plus bas.](figs/kruskal_2.png)
::::
:::: column
## Résultat
![Les sommets `a, d, c, e` sont couverts.](figs/kruskal_3.png)
::::
:::
# Algorithme de Kruskal: exemple
::: columns
:::: column
## On continue avec `(b, e)`
![Le poids de `(b, e)` est le plus bas.](figs/kruskal_3.png)
::::
:::: column
## Résultat
![Les sommets `a, d, c, e, b` sont couverts.](figs/kruskal_4.png)
::::
:::
# Algorithme de Kruskal: exemple
::: columns
:::: column
## Mais pourquoi pas `(c, e)`?
![Le poids de `(b, e)` ou `(a,c)` est le même.](figs/kruskal_3.png)
::::
:::: column
## Résultat: un cycle
![Les sommets `a, d, c, e` sont couverts.](figs/kruskal_cycle.png)
::::
:::
* Comment faire pour empêcher l'ajout de `(c, e)` ou `(a, c)`?
. . .
* Si les deux sommets sont déjà couverts nous sommes sauvés (presque)!
# Algorithme de Kruskal
## L'initialisation
* Créer un ensemble de sommets pour chaque de sommet du graphe ($V_1$, $V_2$, ...):
* $V_1=\{v_1\}$, $V_2=\{v_2\}$, ...
* S'il y a $n$ sommets, il y a $n$ $V_i$.
* Initialiser l'ensemble $A$ des arêtes "sûres" constituant l'arbre couvrant minimal, $A=\emptyset$.
* Initialiser l'ensemble des sommets couverts $F=\emptyset$.
* Trier les arêtes par poids croissant dans l'ensemble $E$.
## Mise à jour
* Tant qu'il reste plus d'un $V_i$:
* Pour $(u,v)\in E$ à poids minimal:
* Retirer $(u,v)$ de $E$.
* Si $u\in V_i$ et $v\in V_j$ avec $V_i\cap V_j=\emptyset$:
* Ajouter $(u,v)$ à $A$;
* Fusionner $V_i$ et $V_j$ dans $F$.
# Algorithme de Kruskal: exemple
::: columns
:::: column
![Couvrir cet arbre bon sang!](figs/kruskal_enonce.png)
::::
:::: column
::::
:::
# Algorithme de Kruskal: solution
![La solution!](figs/kruskal_solution.png)
# Algorithme de Kruskal: exercice
::: columns
:::: column
![Couvrir cet arbre bon sang!](figs/kruskal_exercice.png)
::::
:::: column
::::
:::
# Algorithme de Kruskal: solution
![La solution!](figs/kruskal_solution_exercice.png)
--- ---
title: "Introduction aux algorithmes" title: "Introduction aux algorithmes III"
date: "2023-10-03" date: "2024-09-30"
--- ---
# Rappel (1/2) # Rappel (1/2)
...@@ -258,6 +258,7 @@ for (int i = 0; i < SIZE; ++i) { ...@@ -258,6 +258,7 @@ for (int i = 0; i < SIZE; ++i) {
tab[i] = rand() / (double)RAND_MAX * 10.0 - 5.0; tab[i] = rand() / (double)RAND_MAX * 10.0 - 5.0;
// tab[i] contient un double dans [-5;5] // tab[i] contient un double dans [-5;5]
} }
int other_tab[4] = {0}; // pareil que {0, 0, 0, 0}
``` ```
# Recherche du minimum dans un tableau (1/2) # Recherche du minimum dans un tableau (1/2)
...@@ -289,7 +290,7 @@ pour i de 1 à SIZE - 1 ...@@ -289,7 +290,7 @@ pour i de 1 à SIZE - 1
int index = 0; int index = 0;
float min = tab[0]; float min = tab[0];
for (int i = 1; i < SIZE; ++i) { for (int i = 1; i < SIZE; ++i) {
if min > tab[i] { if (min > tab[i]) {
min = tab[i]; min = tab[i];
index = i; index = i;
} }
......
--- ---
title: "Introduction aux algorithmes" title: "Introduction aux algorithmes IV"
date: "2023-10-10" date: "2024-10-07"
--- ---
# Rappel # Tri par sélection
## Quel est l'algorithme du tri par sélection? ## Quel est l'algorithme du tri par sélection?
...@@ -208,64 +208,3 @@ Algorithme de génération de nombres premiers. ...@@ -208,64 +208,3 @@ Algorithme de génération de nombres premiers.
* Implémenter l'algorithme et le poster sur le salon `Element`. * Implémenter l'algorithme et le poster sur le salon `Element`.
# Crible d'Ératosthène: solution
\footnotesize
```C
#include <stdio.h>
#include <stdbool.h>
#define SIZE 51
int main() {
bool tab[SIZE];
for (int i=0;i<SIZE;i++) {
tab[i] = true;
}
for (int i = 2; i < SIZE; i++) {
if (tab[i]) {
printf("%d ", i);
int j = i;
while (j < SIZE) {
j += i;
tab[j] = false;
}
}
}
printf("\n");
}
```
# Réusinage de code (refactoring)
## Le réusinage est?
. . .
* le processus de restructuration d'un programme:
* en modifiant son design,
* en modifiant sa structure,
* en modifiant ses algorithmes
* mais en **conservant ses fonctionalités**.
. . .
## Avantages?
. . .
* Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
* Réduction de la complexité.
. . .
## "Make it work, make it nice, make it fast", Kent Beck.
. . .
## Exercice:
* Réusiner le code se trouvant sur
[Cyberlearn](https://cyberlearn.hes-so.ch/pluginfile.php/703384/mod_resource/content/1/comprendre.c).
--- ---
title: "Tableaux à deux dimensions et représentation des nombres" title: "Tableaux à deux dimensions et récursivité"
date: "2023-10-17" date: "2024-10-14"
--- ---
# Rappel / devoirs: Crible d'Ératosthène
* But:
- Générer tous les nombres premiers plus petit qu'un entier $N$.
- En utilisant qu'un tableau de booléens
- Et que des multiplications
* Exercice: Écrire l'algorithme en C.
# Crible d'Ératosthène: solution
\footnotesize
```C
#include <stdio.h>
#include <stdbool.h>
#define SIZE 51
int main() {
bool tab[SIZE];
for (int i=0;i<SIZE;i++) {
tab[i] = true;
}
for (int i = 2; i < SIZE; i++) {
if (tab[i]) {
printf("%d ", i);
int j = i;
while (j < SIZE) {
j += i;
tab[j] = false;
}
}
}
printf("\n");
}
```
# Réusinage de code (refactoring) # Réusinage de code (refactoring)
## Le réusinage est?
. . .
* le processus de restructuration d'un programme:
* en modifiant son design,
* en modifiant sa structure,
* en modifiant ses algorithmes
* mais en **conservant ses fonctionalités**.
. . .
## Avantages?
. . .
* Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
* Réduction de la complexité.
. . .
## "Make it work, make it nice, make it fast", Kent Beck.
. . .
## Exercice: ## Exercice:
* Réusiner le code se trouvant sur * Réusiner le code se trouvant sur
[Cyberlearn](https://cyberlearn.hes-so.ch/pluginfile.php/703384/mod_resource/content/1/comprendre.c). [Cyberlearn](https://cyberlearn.hes-so.ch/pluginfile.php/703384/mod_resource/content/1/comprendre.c).
# Tableau à deux dimensions (1/4) # Tableau à deux dimensions (1/4)
## Mais qu'est-ce donc? ## Mais qu'est-ce donc?
...@@ -99,6 +161,8 @@ for (int i = 0; i < NX; ++i) { ...@@ -99,6 +161,8 @@ for (int i = 0; i < NX; ++i) {
# La couverture de la reine # La couverture de la reine
\footnotesize
* Aux échecs la reine peut se déplacer horizontalement et verticalement * Aux échecs la reine peut se déplacer horizontalement et verticalement
* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous * Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous
...@@ -175,313 +239,3 @@ for (int i = 0; i < NX; ++i) { ...@@ -175,313 +239,3 @@ for (int i = 0; i < NX; ++i) {
A faire à la maison comme exercice! A faire à la maison comme exercice!
# Représentation des nombres (1/2)
* Le nombre `247`.
## Nombres décimaux: Les nombres en base 10
+--------+--------+--------+
| $10^2$ | $10^1$ | $10^0$ |
+--------+--------+--------+
| `2` | `4` | `7` |
+--------+--------+--------+
$$
247 = 2\cdot 10^2 + 4\cdot 10^1 + 7\cdot 10^0.
$$
# Représentation des nombres (2/2)
* Le nombre `11110111`.
## Nombres binaires: Les nombres en base 2
+-------+-------+-------+-------+-------+-------+-------+-------+
| $2^7$ | $2^6$ | $2^5$ | $2^4$ | $2^3$ | $2^2$ | $2^1$ | $2^0$ |
+-------+-------+-------+-------+-------+-------+-------+-------+
| `1` | `1` | `1` | `1` | `0` | `1` | `1` | `1` |
+-------+-------+-------+-------+-------+-------+-------+-------+
$$
1\cdot 2^7 + 1\cdot 2^6 +1\cdot 2^5 +1\cdot 2^4 +0\cdot 2^3 +1\cdot 2^2
+1\cdot 2^1 +1\cdot 2^0
$$
. . .
$$
= 247.
$$
# Conversion de décimal à binaire (1/2)
## Convertir 11 en binaire?
. . .
* On décompose en puissances de 2 en partant de la plus grande possible
```
11 / 8 = 1, 11 % 8 = 3
3 / 4 = 0, 3 % 4 = 3
3 / 2 = 1, 3 % 2 = 1
1 / 1 = 1, 1 % 1 = 0
```
* On a donc
$$
1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot
2^0=11.
$$
# Conversion de décimal à binaire (2/2)
## Convertir un nombre arbitraire en binaire: 247?
* Par groupe établir un algorithme.
. . .
## Algorithme
1. Initialisation
```C
num = 247
N = 0
tant que (2^(N+1) < num) {
N += 1
}
```
. . .
2. Boucle
```C
tant que (N >= 0) {
bit = num / 2^N
num = num % 2^N
N -= 1
}
```
# Les additions en binaire
Que donne l'addition `1101` avec `0110`?
* L'addition est la même que dans le système décimal
```
1101 8+4+0+1 = 13
+ 0110 + 0+4+2+0 = 6
------- -----------------
10011 16+0+0+2+1 = 19
```
* Les entiers sur un ordinateur ont une précision **fixée** (ici 4 bits).
* Que se passe-t-il donc ici?
. . .
## Dépassement de capacité: le nombre est "tronqué"
* `10011 (19) -> 0011 (3)`.
* On fait "le tour"."
# Entier non-signés minimal/maximal
* Quel est l'entier non-signé maximal représentable avec 4 bit?
. . .
$$
(1111)_2 = 8+4+2+1 = 15
$$
* Quel est l'entier non-signé minimal représentable avec 4 bit?
. . .
$$
(0000)_2 = 0+0+0+0 = 0
$$
* Quel est l'entier non-signé min/max représentable avec N bit?
. . .
$$
0\mbox{ et }2^N-1.
$$
* Donc `uint32_t?` maximal est?
. . .
$$
2^{32}-1=4'294'967'295
$$
# Les multiplications en binaire (1/2)
Que donne la multiplication de `1101` avec `0110`?
* La multiplication est la même que dans le système décimal
```
1101 13
* 0110 * 6
--------- --------------
0000 78
11010
110100
+ 0000000
--------- --------------
1001110 64+0+0+8+4+2+0
```
# Les multiplications en binaire (2/2)
## Que fait la multiplication par 2?
. . .
* Décalage de un bit vers la gauche!
```
0110
* 0010
---------
0000
+ 01100
---------
01100
```
. . .
## Que fait la multiplication par $2^N$?
. . .
* Décalage de $N$ bits vers la gauche!
# Entiers signés (1/2)
Pas de nombres négatifs encore...
## Comment faire?
. . .
## Solution naïve:
* On ajoute un bit de signe (le bit de poids fort):
```
00000010: +2
10000010: -2
```
## Problèmes?
. . .
* Il y a deux zéros (pas trop grave): `10000000` et `00000000`
* Les additions différentes que pour les non-signés (très grave)
```
00000010 2
+ 10000100 + -4
---------- ----
10000110 = -6 != -2
```
# Entiers signés (2/2)
## Beaucoup mieux
* Complément à un:
* on inverse tous les bits: `1001 => 0110`.
## Encore un peu mieux
* Complément à deux:
* on inverse tous les bits,
* on ajoute 1 (on ignore les dépassements).
. . .
* Comment écrit-on `-4` en 8 bits?
. . .
```
4 = 00000100
________
-4 => 00000100
11111011
+ 00000001
----------
11111100
```
# Le complément à 2 (1/2)
## Questions:
* Comment on écrit `+0` et `-0`?
* Comment calcule-t-on `2 + (-4)`?
* Quel est le complément à 2 de `1000 0000`?
. . .
## Réponses
* Comment on écrit `+0` et `-0`?
```
+0 = 00000000
-0 = 11111111 + 00000001 = 100000000 => 00000000
```
* Comment calcule-t-on `2 + (-4)`?
```
00000010 2
+ 11111100 + -4
---------- -----
11111110 -2
```
* En effet
```
11111110 => 00000001 + 00000001 = 00000010 = 2.
```
# Le complément à 2 (2/2)
## Quels sont les entiers représentables en 8 bits?
. . .
```
01111111 => 127
10000000 => -128 // par définition
```
## Quels sont les entiers représentables sur $N$ bits?
. . .
$$
-2^{N-1} ... 2^{N-1}-1.
$$
## Remarque: dépassement de capacité en `C`
* Comportement indéfini!
--- ---
title: "Récursivité" title: "Récursivité et représentation des nombres"
date: "2023-10-31" date: "2024-10-29"
--- ---
# Nombres à virgule (1/3) # La récursivité
## Comment manipuler des nombres à virgule?
$$
0.1 + 0.2 = 0.3.
$$
Facile non?
. . .
## Et ça?
```C
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
float a = atof(argv[1]);
float b = atof(argv[2]);
printf("%.10f\n", (double)(a + b));
}
```
. . .
## Que se passe-t-il donc?
# Nombres à virgule (2/3)
## Nombres à virgule fixe
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
| $2^3$ | $2^2$ | $2^1$ | $2^0$ | `.` | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ |
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
| `1` | `0` | `1` | `0` | `.` | `0` | `1` | `0` | `1` |
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
## Qu'est-ce ça donne en décimal?
. . .
$$
2^3+2^1+\frac{1}{2^2}+\frac{1}{2^4} = 8+2+0.5+0.0625=10.5625.
$$
## Limites de cette représentation?
. . .
* Tous les nombres `> 16`.
* Tous les nombres `< 0.0625`.
* Tous les nombres dont la décimale est pas un multiple de `0.0625`.
# Nombres à virgule (3/3)
## Nombres à virgule fixe
* Nombres de $0=0000.0000$ à $15.9375=1111.1111$.
* Beaucoup de "trous" (au moins $0.0625$) entre deux nombres.
## Solution partielle?
. . .
* Rajouter des bits.
* Bouger la virgule.
# Nombres à virgule flottante (1/2)
## Notation scientifique
* Les nombres sont représentés en terme:
* Une mantisse
* Une base
* Un exposant
$$
\underbrace{22.1214}_{\mbox{nombre}}=\underbrace{221214}_{\mbox{mantisse}}\cdot
{\underbrace{10}_{\mbox{base}}}{\overbrace{^{-4}}^{\mbox{exp.}}},
$$
. . .
On peut donc séparer la représentation en 2:
* La mantisse
* L'exposant
# Nombres à virgule flottante (2/2)
## Quel est l'avantage?
. . .
On peut représenter des nombres sur énormément d'ordres de grandeur avec un
nombre de bits fixés.
## Différence fondamentale avec la virgule fixe?
. . .
La précision des nombres est **variable**:
* On a uniquement un nombre de chiffres **significatifs**.
$$
123456\cdot 10^{23}+ 123456\cdot 10^{-23}.
$$
## Quel inconvénient y a-t-il?
. . .
Ce mélange d'échelles entraîne un **perte de précision**.
# Nombres à virgule flottante simple précision (1/4)
Aussi appelés *IEEE 754 single-precision binary floating point*.
![Nombres à virgule flottante 32 bits. Source:
[Wikipedia](https://en.wikipedia.org/wiki/Single-precision_floating-point_format)](figs/Float_example_bare.svg)
## Spécification
* 1 bit de signe,
* 8 bits d'exposant,
* 23 bits de mantisse.
$$
(-1)^{b_{31}}\cdot 2^{(b_{30}b_{29}\dots b_{23})_{2}-127}\cdot (1.b_{22}b_{21}\dots b_{0})_{2},
$$
## Calculer la valeur décimale du nombre ci-dessus
# Nombres à virgule flottante simple précision (2/4)
![Un exercice de nombres à virgule flottante 32 bits. Source:
[Wikipedia](https://en.wikipedia.org/wiki/Single-precision_floating-point_format)](figs/Float_example.svg)
. . .
\begin{align}
\mbox{exposant}&=\sum_{i=0}^7 b_{23+i}2^i=2^2+2^3+2^4+2^5+2^6=124-127,\\
\mbox{mantisse}&=1+\sum_{i=1}^{23}b_{23-i}2^{-i}=1+2^{-2}=1.25,\\
&\Rightarrow (-1)^0\cdot 2^{-3}\cdot 1.25=0.15625
\end{align}
# Nombres à virgule flottante simple précision (3/4)
## Quel nombre ne peux pas être vraiment représenté?
. . .
## Zéro: exception pour l'exposant
* Si l'exposant est `00000000` (zéro)
$$
(-1)^{\mbox{sign}}\cdot 2^{-126}\cdot 0.\mbox{mantisse},
$$
* Sinon si l'exposant est `00000001` à `11111110`
$$
\mbox{valeur normale},
$$
* Sinon `11111111` donne `NaN`.
# Nombres à virgule flottante simple précision (4/4)
## Quels sont les plus petits/grands nombres positifs représentables?
. . .
\begin{align}
0\ 0\dots0\ 0\dots01&=2^{-126}\cdot 2^{-23}=1.4...\cdot
10^{-45},\\
0\ 1\dots10\ 1\dots1&=2^{127}\cdot (2-2^{-23})=3.4...\cdot
10^{38}.
\end{align}
## Combien de chiffres significatifs en décimal?
. . .
* 24 bits ($23 + 1$) sont utiles pour la mantisse, soit $2^{24}-1$:
* La mantisse fait $\sim2^{24}\sim 10^7$,
* Ou encore $\sim \log_{10}(2^{24})\sim 7$.
* Environ **sept** chiffres significatifs.
# Nombres à virgule flottante double précision (64bits)
## Spécification
* 1 bit de signe,
* 11 bits d'exposant,
* 52 bits de mantisse.
. . .
## Combien de chiffres significatifs?
* La mantisse fait $\sim 2^{53}\sim10^{16}$,
* Ou encore $\sim \log_{10}(2^{53})\sim 16$,
* Environ **seize** chiffres significatifs.
## Plus petit/plus grand nombre représentable?
. . .
* Plus petite mantisse et exposant: $\sim 2^{-52}\cdot 2^{-1022}\sim 4\cdot 10^{-324}$,
* Plus grande mantisse et exposant: $\sim 2\cdot 2^{1023}\sim \cdot 1.8\cdot 10^{308}$.
# Précision finie (1/3)
## Erreur de représentation
* Les nombres réels ont potentiellement un **nombre infini** de décimales
* $1/3=0.\overline{3}$,
* $\pi=3.1415926535...$.
* Les nombres à virgule flottante peuvent en représenter qu'un **nombre
fini**.
* $1/3\cong 0.33333$, erreur $0.00000\overline{3}$.
* $\pi\cong3.14159$, erreur $0.0000026535...$.
On rencontre donc des **erreurs de représentation** ou **erreurs
d'arrondi**.
. . .
## Et quand on calcule?
* Avec deux chiffres significatifs
\begin{align}
&8.9+(0.02+0.04)=8.96=9.0,\\
&(8.9+0.02)+0.04=8.9+0.04=8.9.
\end{align}
. . .
## Même pas associatif!
# Précision finie (2/3)
## Erreur de représentation virgule flottante
$$
(1.2)_{10} = 1.\overline{0011}\cdot 2^0\Rightarrow 0\ 01111111\
00110011001100110011010.
$$
Erreur d'arrondi dans les deux derniers bits et tout ceux qui viennent
ensuite
$$
\varepsilon_2 = (00000000000000000000011)_2.
$$
Ou en décimal
$$
\varepsilon_{10} = 4.76837158203125\cdot 10^{-8}.
$$
# Précision finie (3/3)
## Comment définir l'égalité de 2 nombres à virgule flottante?
. . .
Ou en d'autres termes, pour quel $\varepsilon>0$ (appelé `epsilon-machine`) on a
$$
1+\varepsilon = 1,
$$
pour un nombre à virgule flottante?
. . .
Pour un `float` (32 bits) la différence est à
$$
2^{-23}=1.19\cdot 10^{-7},
$$
Soit la précision de la mantisse.
## Comment le mesurer (par groupe)?
. . .
```C
float eps = 1.0;
while ((float)1.0 + (float)0.5 * eps != (float)1.0) {
eps = (float)0.5 * eps;
}
printf("eps = %g\n", eps);
```
# Erreurs d'arrondi
Et jusqu'ici on a encore pas fait d'arithmétique!
## Multiplication avec deux chiffres significatifs, décimal
$$
(1.1)_{10}\cdot (1.1)_{10}=(1.21)_{10}=(1.2)_{10}.
$$
En continuant ce petit jeu:
$$
\underbrace{1.1\cdot 1.1\cdots 1.1}_{\mbox{10 fois}}=2.0.
$$
Alors qu'en réalité
$$
1.1^{10}=2.5937...
$$
Soit une erreur de près de 1/5e!
. . .
## Le même phénomène se produit (à plus petite échelle) avec les `float` ou `double`.
# And now for something completely different
\Huge La récursivité \Huge La récursivité
...@@ -597,3 +284,94 @@ int fib_imp(int n) { ...@@ -597,3 +284,94 @@ int fib_imp(int n) {
} }
``` ```
# Exponentiation rapide
\Huge L'exponentiation rapide ou indienne
# Exponentiation rapide ou indienne (1/4)
## But: Calculer $x^n$
* Quel est l'algorithmie le plus simple que vous pouvez imaginer?
. . .
```C
double pow(double x, int n) {
if (0 == n) {
return 1;
}
double p = x;
for (int i = 1; i < n; ++i) {
p = p * x; // p *= x
}
return p;
}
```
* 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
double pow(double x, int 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}\cdot x^{n/2}\right)$,
* Si n est impair: calculer $x \cdot \left(x^{(n-1)/2}\right)^2=x\cdot x^{n-1}$.
## Exercice: écrire l'algorithme récursif correspondant
. . .
```C
double pow(double x, int n) {
if (0 == n) {
return 1;
} else if (n % 2 == 0) {
return pow(x, n / 2) * pow(x, n/2);
} else {
return x * pow(x, (n-1));
}
}
```
--- ---
title: "Récursion et tris" title: "Représentation des nombres, tris, et complexité"
date: "2023-11-07" date: "2024-11-11"
header-includes: | header-includes: |
\usepackage{xcolor} \usepackage{xcolor}
--- ---
# Exponentiation rapide ou indienne (1/4) # Représentation des nombres
## But: Calculer $x^n$ \Huge La représentation des nombres
* Quel est l'algorithmie le plus simple que vous pouvez imaginer? # Représentation des nombres (1/2)
. . . * Le nombre `247`.
```C ## Nombres décimaux: Les nombres en base 10
double pow(double x, int n) {
if (0 == n) {
return 1;
}
double p = c;
for (int i = 1; i < n; ++i) {
p = p * x; // x *= x
}
return x;
}
```
* Combien de multiplication et d'assignations en fonction de `n`? +--------+--------+--------+
| $10^2$ | $10^1$ | $10^0$ |
+--------+--------+--------+
| `2` | `4` | `7` |
+--------+--------+--------+
. . . $$
247 = 2\cdot 10^2 + 4\cdot 10^1 + 7\cdot 10^0.
* `n` assignations et `n` multiplications. $$
# Exponentiation rapide ou indienne (2/4) # Représentation des nombres (2/2)
* Proposez un algorithme naïf et récursif * Le nombre `11110111`.
. . . ## Nombres binaires: Les nombres en base 2
```C +-------+-------+-------+-------+-------+-------+-------+-------+
double pow(double x, int n) { | $2^7$ | $2^6$ | $2^5$ | $2^4$ | $2^3$ | $2^2$ | $2^1$ | $2^0$ |
if (n != 0) { +-------+-------+-------+-------+-------+-------+-------+-------+
return x * pow(x, n-1); | `1` | `1` | `1` | `1` | `0` | `1` | `1` | `1` |
} else { +-------+-------+-------+-------+-------+-------+-------+-------+
return 1;
}
}
```
# Exponentiation rapide ou indienne (3/4) $$
1\cdot 2^7 + 1\cdot 2^6 +1\cdot 2^5 +1\cdot 2^4 +0\cdot 2^3 +1\cdot 2^2
+1\cdot 2^1 +1\cdot 2^0
$$
## 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}}. = 247.
$$ $$
* 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$? # Conversion de décimal à binaire (1/2)
## Convertir 11 en binaire?
. . . . . .
* $n$ est représenté en binaire avec $d$ bits $\Rightarrow d\sim\log_2(n)$. * On décompose en puissances de 2 en partant de la plus grande possible
* il y a $2\log_2(n)\sim \log_2(n)$ calculs.
# Exponentiation rapide ou indienne (4/4) ```
11 / 8 = 1, 11 % 8 = 3
3 / 4 = 0, 3 % 4 = 3
3 / 2 = 1, 3 % 2 = 1
1 / 1 = 1, 1 % 1 = 0
```
* On a donc
$$
1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot
2^0=11.
$$
## Le vrai algorithme # Conversion de décimal à binaire (2/2)
* Si n est pair: calculer $\left(x^{n/2}\cdot x^{n/2}\right)$, ## Convertir un nombre arbitraire en binaire: 247?
* Si n est impair: calculer $x \cdot \left(x^{(n-1)/2}\right)^2=x\cdot x^{n-1}$.
## Exercice: écrire l'algorithme récursif correspondant * Par groupe établir un algorithme.
. . . . . .
## Algorithme
1. Initialisation
```C ```C
double pow(double x, int n) { num = 247
if (0 == n) { N = 0
return 1;
} else if (n % 2 == 0) { tant que (2^(N+1) < num) {
return pow(x, n / 2) * pow(x, n/2); N += 1
} else {
return x * pow(x, (n-1));
}
} }
``` ```
# 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 2. Boucle
1. On considère le bit le moins significatif. ```C
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; tant que (N >= 0) {
puis on répète l'opération 2 pour les éléments dont le bit est 1. bit = num / 2^N
3. On répète l'étape 2 en regardant le bit suivant et en permutant le rôle des deux tableaux. num = num % 2^N
N -= 1
}
```
On utilise donc deux tableaux pour réaliser ce tri. # Les additions en binaire
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) Que donne l'addition `1101` avec `0110`?
Soit la liste de nombre entier: * L'addition est la même que dans le système décimal
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ```
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 1101 8+4+0+1 = 13
| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 | + 0110 + 0+4+2+0 = 6
------- -----------------
10011 16+0+0+2+1 = 19
```
* Les entiers sur un ordinateur ont une précision **fixée** (ici 4 bits).
* Que se passe-t-il donc ici?
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 | ## Dépassement de capacité: le nombre est "tronqué"
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 14 | 4 | 10 | 15 | 13 | 3 | 11 | 0 | 11 |
# Illustration sur un exemple (2/6) * `10011 (19) -> 0011 (3)`.
* On fait "le tour"."
* Écrivons les éléments en représentation binaire. # Entier non-signés minimal/maximal
* La valeur maximale est 15, on a besoin de 4 bits.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | * Quel est l'entier non-signé maximal représentable avec 4 bit?
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 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 $$
(1111)_2 = 8+4+2+1 = 15
$$
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | * Quel est l'entier non-signé minimal représentable avec 4 bit?
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 111**0** | 010**0** | 101**0** | 111**1** | 110**1** | 001**1** | 101**1** | 000**0** | 101**1** |
. . . . . .
* On obtient le tableau: $$
(0000)_2 = 0+0+0+0 = 0
$$
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | * Quel est l'entier non-signé min/max représentable avec N bit?
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 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\mbox{ et }2^N-1.
$$
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | * Donc `uint32_t?` maximal est?
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 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: $$
2^{32}-1=4'294'967'295
$$
| 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} |
# Les multiplications en binaire (1/2)
# Illustration sur un exemple (5/6) Que donne la multiplication de `1101` avec `0110`?
4. On passe au dernier bit et on obtient le tableau final: * La multiplication est la même que dans le système décimal
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ```
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| 1101 13
| \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 | * 0110 * 6
| \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} | --------- --------------
0000 78
11010
110100
+ 0000000
--------- --------------
1001110 64+0+0+8+4+2+0
```
. . . # Les multiplications en binaire (2/2)
* En revenant à la représentation décimale, on a le tableau trié: ## Que fait la multiplication par 2?
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | . . .
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| 0 | 3 | 4 | 10 | 11 | 11 | 13 | 14 | 15 |
# Illustration sur un exemple (6/6) * Décalage de un bit vers la gauche!
* Pour revenir aux valeurs initiales, il faut décaler de 9 dans l'autre sens. ```
0110
* 0010
---------
0000
+ 01100
---------
01100
```
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | . . .
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| -9 | -6 | -5 | 1 | 2 | 2 | 4 | 5 | 6 |
* Et alors? ## Que fait la multiplication par $2^N$?
. . . . . .
* Et alors rien. C'est fini. * Décalage de $N$ bits vers la gauche!
# Entiers signés (1/2)
# Pseudo-code Pas de nombres négatifs encore...
```python ## Comment faire?
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()` ## Solution naïve:
* On ajoute un bit de signe (le bit de poids fort):
```python
rien decaler(entier taille, entier tab[taille], entier val):
pour i de 0 à taille-1:
taille[i] -= val
``` ```
00000010: +2
10000010: -2
```
## Problèmes?
. . . . . .
## La fonction `echanger()` * Il y a deux zéros (pas trop grave): `10000000` et `00000000`
* Les additions différentes que pour les non-signés (très grave)
```python ```
rien echanger(entier tab[], entier tab2[]) 00000010 2
# échanger les tableaux (sans copier les valeurs) + 10000100 + -4
---------- ----
10000110 = -6 != -2
``` ```
# Un peu plus de détails (2/2) # Entiers signés (2/2)
## La fonction `alveole_0()` ## Beaucoup mieux
```python * Complément à un:
rien alveole_0(entier taille, entier tab[taille], * on inverse tous les bits: `1001 => 0110`.
entier tab_tmp[taille], entier pos):
entier k = 0 ## Encore un peu mieux
pour i de 0 à taille-1:
si bit(tab[i], pos) == 0: * Complément à deux:
tab_tmp[k] = tab[i] * on inverse tous les bits,
k = k + 1 * on ajoute 1 (on ignore les dépassements).
```
. . . . . .
## La fonction `alveole_1()` * Comment écrit-on `-4` en 8 bits?
```python . . .
rien alveole_1(entier taille, entier tab[taille],
entier tab_tmp[taille], entier pos):
# pareil que alveole_0 mais dans l'autre sens
```
# Tri par fusion (merge sort) ```
4 = 00000100
________
-4 => 00000100
11111011
+ 00000001
----------
11111100
```
* Tri par comparaison. # Le complément à 2 (1/2)
* 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. --> \footnotesize
# Principe de l'algorithme ## Questions:
* Soit `taille` la taille du tableau à trier. * Comment on écrit `+0` et `-0`?
* Pour `i = 0` à `entier(log2(taille))-1`: * Comment calcule-t-on `2 + (-4)`?
* Fusion des paires de sous-tableaux successifs de taille `2**i` (ou moins pour l'extrémité) * Quel est le complément à 2 de `1000 0000`?
. . . . . .
* Remarques: ## Réponses
* 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 * Comment on écrit `+0` et `-0`?
* Soit la liste de nombres entiers stockés dans un tableau de taille 9: ```
+0 = 00000000
-0 = 11111111 + 00000001 = 100000000 => 00000000
```
* Comment calcule-t-on `2 + (-4)`?
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ```
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 00000010 2
| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 | + 11111100 + -4
---------- -----
11111110 -2
```
* En effet
```
11111110 => 00000001 + 00000001 = 00000010 = 2.
```
# Le complément à 2 (2/2)
## Quels sont les entiers représentables en 8 bits?
. . . . . .
* Fusion des éléments successifs (ce qui revient à les mettre dans l'ordre): ```
01111111 => 127
10000000 => -128 // par définition
```
| étape | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## Quels sont les entiers représentables sur $N$ bits?
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 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) $$
-2^{N-1} ... 2^{N-1}-1.
$$
```python ## Remarque: dépassement de capacité en `C`
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);
```
* Comportement indéfini!
# Algorithme de fusion possible
## Une idée? # Les types énumérés
. . . \Huge Les types énumérés
* Parcourir les deux tableaux jusqu'à atteindre la fin d'un des deux # Types énumérés (1/2)
* 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) * Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.
\footnotesize ```C
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
## Une idée? ```C
enum days {
monday = 2, tuesday = 8, wednesday = -2,
thursday = 1, friday = 3, saturday = 12, sunday = 9
};
```
* S'utilise comme un type standard et un entier
. . . ```C
enum days d = monday;
(d + 2) == monday + monday; // true
```
```python # Types énumérés (2/2)
# hyp: tab_g et tab_d sont triés
rien fusion(entier tab_g[], entier tab_d[], entier res[]): * Très utile dans les `switch ... case`{.C}
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
```C
enum days d = monday;
switch (d) {
case monday:
// trucs
break;
case tuesday:
printf("0 ou 1\n");
break;
}
``` ```
* Le compilateur vous prévient qu'il en manque!
# Utilisation des types énumérés
<!-- ## Complexité ## Réusiner votre couverture de la reine avec des `enum`
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 faire à la maison comme exercice!
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. # Le tri rapide ou quicksort
Ainsi, la complexité du tri par fusion est $\mathcal{O}(N\cdot \log_2(N)$. --> \Huge Tri rapide ou quicksort
# Tri rapide ou quicksort (1/8) # Tri rapide ou quicksort (1/8)
...@@ -457,7 +442,7 @@ rien quicksort(entier tableau[], entier ind_min, entier ind_max) ...@@ -457,7 +442,7 @@ rien quicksort(entier tableau[], entier ind_min, entier ind_max)
ind_pivot = partition(tableau, ind_min, ind_max) ind_pivot = partition(tableau, ind_min, ind_max)
si (longueur(tableau[ind_min:ind_pivot-1]) != 0) si (longueur(tableau[ind_min:ind_pivot-1]) != 0)
quicksort(tableau, ind_min, pivot_ind - 1) quicksort(tableau, ind_min, pivot_ind - 1)
si (longueur(tableau[ind_pivot+1:ind_max-1]) != 0) si (longueur(tableau[ind_pivot+1:ind_max]) != 0)
quicksort(tableau, ind_pivot + 1, ind_max) quicksort(tableau, ind_pivot + 1, ind_max)
``` ```
......
--- ---
title: "Tris et complexité" title: "Tris et complexité"
date: "2023-11-21" date: "2024-11-18"
header-includes: | header-includes: |
\usepackage{xcolor} \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:
tab[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 # Efficacité d'un algorithmique
Comment mesurer l'efficacité d'un algorithme? Comment mesurer l'efficacité d'un algorithme?
...@@ -266,490 +603,3 @@ $$ ...@@ -266,490 +603,3 @@ $$
$$ $$
# 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 à bulles d'un tableau d'entiers](figs/tri_bulles.svg)
# 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 d'un tableau d'entiers](figs/tri_insertion.svg)
# 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é.
![Problème des 8-reines. Source:
[wikipedia](https://fr.wikipedia.org/wiki/Problème_des_huit_dames)](./figs/fig_recursivite_8_reines.png){width=35%}
# Problème des 2-reines
![Le problème des 2 reines n'a pas de solution.](figs/2reines.svg){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
![Le problème des 3 reines n'a pas de solution non plus.](figs/3reines.svg)
# Problème des 4-reines
![Le problème des 4 reines a une solution.](figs/4reines.svg)
# Problème des 4-reines, symétrie
![Le problème des 4 reines a une autre solution (symétrie
horizontale).](figs/4reines_sym.svg)
# 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;
}
}
}
```
---
title: "Tris, complexité, backtracking et assertions"
date: "2024-11-25"
header-includes: |
\usepackage{xcolor}
---
# Le tri à bulle
\Huge Le tri à bulle
# 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 à bulles d'un tableau d'entiers](figs/tri_bulles.svg)
# 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(tableau[j], tableau[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 d'un tableau d'entiers](figs/tri_insertion.svg)
# 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))$.
# Le problème des 8-reines
\Huge Le problème des 8-reines
# 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é.
![Problème des 8-reines. Source:
[wikipedia](https://fr.wikipedia.org/wiki/Problème_des_huit_dames)](./figs/fig_recursivite_8_reines.png){width=35%}
# Problème des 2-reines
![Le problème des 2 reines n'a pas de solution.](figs/2reines.svg){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
![Le problème des 3 reines n'a pas de solution non plus.](figs/3reines.svg)
# Problème des 4-reines
![Le problème des 4 reines a une solution.](figs/4reines.svg)
# Problème des 4-reines, symétrie
![Le problème des 4 reines a une autre solution (symétrie
horizontale).](figs/4reines_sym.svg)
# 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/5)
## 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/5)
## Le calcul du nombre de solutions
```C
// Calcule le nombre de solutions au problème des <n> reines
rien 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/5)
## Le calcul du nombre de solutions
```C
// Placer une reine et mettre à jour
rien 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/5)
## 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/5)
\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 placer_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;
}
}
}
```
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define SIZE 10
int tab_find_min_index(int i, int size, char tab[size]) {
int i_min = i;
for (int j = i + 1; j < (int)strlen(tab); ++j) {
if (tab[j] < tab[i_min]) {
i_min = j;
}
}
return i_min;
}
void swap(int i, int j, char tab[]) {
char tmp = tab[i];
tab[i] = tab[j];
tab[j] = tmp;
}
void tab_selection_sort(int size, char tab[size]) {
for (int i = 0; i < (int)strlen(tab); ++i) {
int i_min = tab_find_min_index(i, size, tab);
// échange tab[i] et tab[j]
if (i_min != i) {
swap(i, i_min, tab);
}
}
}
bool tab_is_sorted(int size, char tab[size]) {
for (int i = 1; i < size; ++i) {
if (tab[i - 1] > tab[i]) {
return false;
}
}
return true;
}
int main() {
// allocate tab
char tab_lhs[SIZE] = "tutut";
char tab_rhs[SIZE] = "tutta";
printf("Are %s and %s anagrams? ", tab_lhs, tab_rhs);
tab_selection_sort(SIZE, tab_lhs);
tab_selection_sort(SIZE, tab_rhs);
printf("Are %s and %s anagrams? ", tab_lhs, tab_rhs);
int is_equal = strcmp(tab_lhs, tab_rhs);
printf("%d", is_equal);
}
#include <stdio.h>
#include <strings.h>
#define SIZE 4
int main() {
int tab[SIZE] = {1, -1, 10, 4};
int new_tab[SIZE];
for (int index = 0; index < SIZE - 1; ++index) {
int min_index = index;
int min = tab[min_index];
for (int i = min_index + 1; i < SIZE; ++i) {
if (tab[i] < min) {
min = tab[i];
min_index = i;
}
}
new_tab[index] = min;
}
for (int i = 0; i < SIZE; ++i) {
printf("%d ", new_tab[i]);
}
printf("\n");
}
#include "dll.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static element_t *create_element(int data, element_t *prev, element_t *next) {
element_t *elem = (element_t *)malloc(sizeof(*elem));
if (NULL == elem) {
return NULL;
}
elem->data = data;
elem->prev = prev;
elem->next = next;
return elem;
}
void dll_init(dll *list) {
list->pos = NULL;
list->head = NULL;
}
bool dll_is_empty(dll list) {
return (NULL == list.head && NULL == list.pos);
}
bool dll_value(dll list, int *val) {
if (dll_is_empty(list)) {
return false;
}
*val = list.pos->data;
return true;
}
bool dll_is_head(dll list) {
return (!dll_is_empty(list) && list.pos == list.head);
}
bool dll_is_tail(dll list) {
return (!dll_is_empty(list) && NULL == list.pos->next);
}
bool dll_is_present(dll list, int data) {
dll_move_to_head(&list);
int l_data = 0;
while (dll_value(list, &l_data)) {
if (data == l_data) {
return true;
} else if (dll_is_tail(list)) {
return false;
}
dll_next(&list);
}
return false;
}
static char *robust_realloc(char *str, size_t num) {
char *new_str = (char *)realloc(str, num * sizeof(*new_str));
if (NULL == new_str) {
free(str);
return NULL;
}
return new_str;
}
char *dll_to_str(dll list) {
char *str = (char *)malloc(sizeof(*str));
str[0] = 0;
if (dll_is_empty(list)) {
return str;
}
dll_move_to_head(&list);
int data = 0;
while (dll_value(list, &data)) {
char buffer[12]; // normally largest posible string is -2e9 (11 digits
// and space and 0)
if (dll_is_tail(list)) {
sprintf(buffer, "%d", data);
str = robust_realloc(str, strlen(str) + strlen(buffer) + 1);
if (NULL == str) {
return NULL;
}
strncat(str, buffer, strlen(str) + strlen(buffer) + 1);
break;
} else {
sprintf(buffer, "%d ", data);
str = robust_realloc(str, strlen(str) + strlen(buffer) + 1);
if (NULL == str) {
return NULL;
}
strncat(str, buffer, strlen(str) + strlen(buffer) + 1);
}
dll_next(&list);
}
return str;
}
void dll_move_to_head(dll *list) {
list->pos = list->head;
}
void dll_next(dll *list) {
if (!dll_is_tail(*list) && !dll_is_empty(*list)) {
list->pos = list->pos->next;
}
}
void dll_prev(dll *list) {
if (!dll_is_head(*list) && !dll_is_empty(*list)) {
list->pos = list->pos->prev;
}
}
bool dll_insert_after(dll *list, int data) {
if (dll_is_empty(*list)) {
element_t *elem = create_element(data, NULL, NULL);
if (NULL == elem) {
return false;
}
list->head = list->pos = elem;
} else {
element_t *new_elem = create_element(data, list->pos, list->pos->next);
if (NULL == new_elem) {
return false;
}
if (!dll_is_tail(*list)) {
list->pos->next->prev = new_elem;
}
list->pos->next = new_elem;
list->pos = new_elem;
}
return true;
}
bool dll_push(dll *list, int data) {
if (dll_is_empty(*list)) {
element_t *elem = create_element(data, NULL, NULL);
if (NULL == elem) {
return false;
}
list->head = list->pos = elem;
} else {
element_t *new_elem = create_element(data, NULL, list->head);
if (NULL == new_elem) {
return false;
}
list->head->prev = new_elem;
list->head = new_elem;
list->pos = new_elem;
}
return true;
}
bool dll_extract(dll *list, int *val) {
bool ret = dll_value(*list, val);
if (!ret) {
return false;
}
element_t *elem = list->pos;
if (dll_is_tail(*list) && dll_is_head(*list)) {
list->head = list->pos = NULL;
} else if (dll_is_tail(*list)) {
list->pos = list->pos->prev;
list->pos->next = NULL;
} else if (dll_is_head(*list)) {
list->pos = list->pos->next;
list->pos->prev = NULL;
list->head = list->pos;
} else {
elem->prev->next = list->pos->next;
elem->next->prev = list->pos->prev;
list->pos = elem->next;
}
free(elem);
return true;
}
bool dll_pop(dll *list, int *val) {
dll_move_to_head(list);
bool ret = dll_value(*list, val);
if (!ret) {
return false;
}
element_t *elem = list->head;
if (dll_is_tail(*list) && dll_is_head(*list)) {
list->head = list->pos = NULL;
} else {
list->head->next->prev = NULL;
list->pos = list->head = list->head->next;
}
free(elem);
return true;
}
void dll_clear(dll *list) {
dll_move_to_head(list);
int val = 0;
while (dll_pop(list, &val)) {
// we do nothing
}
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#define MIN_CAPACITY 5
struct stack {
int top;
int capacity;
int *data;
};
void stack_init(struct stack *s, int capacity) {
s->top = -1;
s->data = malloc(capacity * sizeof(int));
s->capacity = capacity;
}
bool stack_is_empty(struct stack s) {
return s.top == -1;
}
void stack_push(struct stack *s, int val) {
if (s->top != s->capacity - 1) {
s->top += 1;
s->data[s->top] = val;
} else {
s->capacity *= 2;
s->data = realloc(s->data, s->capacity * sizeof(int));
s->top += 1;
s->data[s->top] = val;
}
}
/*int stack_pop(struct stack *s) {*/
/* if (!stack_is_empty(*s)) {*/
/* int tmp = s->data[s->top];*/
/* s->top -= 1;*/
/* return tmp;*/
/* }*/
/*}*/
/*void stack_pop(struct stack *s, int *val) {*/
/* if (!stack_is_empty(*s)) {*/
/* *val = s->data[s->top];*/
/* s->top -= 1;*/
/* }*/
/*}*/
int *stack_pop(struct stack *s) {
if (!stack_is_empty(*s)) {
int *val = &(s->data[s->top]);
s->top -= 1;
if (s->top < s->capacity / 4 && (s->capacity / 2) > MIN_CAPACITY) {
s->capacity /= 2;
s->data = realloc(s->data, s->capacity * sizeof(int));
}
return val;
} else {
return NULL;
}
}
int *stack_peek(struct stack s) {
if (!stack_is_empty(s)) {
int *val = &(s.data[s.top]);
return val;
} else {
return NULL;
}
}
void stack_print(struct stack s) {
printf("capacity = %d\n", s.capacity);
for (int i = s.top; i >= 0; --i) {
printf("%d\n", s.data[i]);
}
}
int main() {
struct stack s;
stack_init(&s, 5);
stack_print(s);
printf("s.top = %d\n", s.top);
printf("is_empty(): %s\n", stack_is_empty(s) ? "True" : "False");
stack_push(&s, 10);
stack_push(&s, 20);
stack_push(&s, 30);
stack_push(&s, 40);
stack_push(&s, 50);
stack_push(&s, 60);
stack_push(&s, 70);
stack_push(&s, 80);
stack_push(&s, 90);
stack_push(&s, 100);
stack_push(&s, 110);
printf("is_empty(): %s\n", stack_is_empty(s) ? "True" : "False");
stack_print(s);
/*int val = -1;*/
/*stack_pop(&s, &val);*/
/*printf("popped value = %d\n", val);*/
/*stack_pop(&s, &val);*/
/*printf("popped value = %d\n", val);*/
/*stack_pop(&s, &val);*/
/*printf("popped value = %d\n", val);*/
/*stack_pop(&s, &val);*/
/*printf("popped value = %d\n", val);*/
/*stack_pop(&s, &val);*/
/*printf("popped value = %d\n", val);*/
/*stack_pop(&s, &val);*/
/*printf("popped value = %d\n", val);*/
/*printf("popped value = %d\n", stack_pop(&s));*/
/*printf("popped value = %d\n", stack_pop(&s));*/
/*printf("popped value = %d\n", stack_pop(&s));*/
/*printf("popped value = %d\n", stack_pop(&s));*/
/*printf("popped value = %d\n", stack_pop(&s));*/
/*printf("popped value = %d\n", stack_pop(&s));*/
printf("peeked value = %d\n", *stack_peek(s));
printf("peeked value = %d\n", *stack_peek(s));
stack_print(s);
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
printf("popped value = %d\n", *stack_pop(&s));
/*printf("popped value = %d\n", *stack_pop(&s));*/
stack_print(s);
printf("is_empty(): %s\n", stack_is_empty(s) ? "True" : "False");
return EXIT_SUCCESS;
}
#include <stdio.h>
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
}
int main() {
int n = 1000000000;
printf("%d! = %d\n", n, factorial(n));
return 0;
}
CC:=gcc
CFLAGS:=-Wall -Wextra -pedantic -fsanitize=address -g
LDFLAGS:=-fsanitize=address
main: main.o hm.o
main.o: hm.h
hm.o: hm.h
.PHONY: clean
clean:
rm -f *.o main
#include "hm.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static int hash(char *key, int capacity) {
int h = 0;
for (int i = 0; i < (int)strlen(key); ++i) {
h = (h + key[i] * 43) % capacity;
}
return h;
}
// allocate memory of the table to capacity
void hm_init(struct hm_t *hm, int capacity) {
hm->table = malloc(sizeof(*(hm->table)) * capacity);
hm->capacity = capacity;
hm->size = 0;
for (int i = 0; i < hm->capacity; ++i) {
hm->table[i].state = empty;
}
}
// insert the key - value pair: if key is already present
// we erase the old value. if the table is full we return false
bool hm_insert(struct hm_t *hm, char *key, char *value) {
int index = hash(key, hm->capacity);
int initial_index = index;
while (hm->table[index].state == occupied && strncmp(hm->table[index].key, key, MAX_LEN) != 0) {
index = (index + 1) % hm->capacity;
if (index == initial_index) {
return false;
}
}
hm->table[index].state = occupied;
strncpy(hm->table[index].key, key, MAX_LEN);
strncpy(hm->table[index].value, value, MAX_LEN);
hm->size += 1;
return true;
}
// changes the state of the table at the "key" position to deleted
void hm_delete(struct hm_t *hm, char *key) {
int index = hash(key, hm->capacity);
int initial_index = index;
while (hm->table[index].state != empty) {
if (hm->table[index].state == occupied && strncmp(hm->table[index].key, key, MAX_LEN) == 0)
{
hm->table[index].state = deleted;
hm->size -= 1;
}
index = (index + 1) % hm->capacity;
if (index == initial_index) {
return;
}
}
}
// returns the value linked to the key if present
// return NULL otherwise
char *hm_get(struct hm_t hm, char *key) {
int index = hash(key, hm.capacity);
int initial_index = index;
while (hm.table[index].state != empty) {
if (hm.table[index].state == occupied && strncmp(hm.table[index].key, key, MAX_LEN) == 0) {
return hm.table[index].value;
}
index = (index + 1) % hm.capacity;
if (index == initial_index) {
return NULL;
}
}
return NULL;
}
// prints the state of the table
void hm_print(struct hm_t hm) {
for (int i = 0; i < hm.capacity; ++i) {
if (hm.table[i].state == empty) {
printf("[%d]: empty\n", i);
} else if (hm.table[i].state == occupied) {
printf("[%d]: occupied, %s, %s\n", i, hm.table[i].key, hm.table[i].value);
} else {
printf("[%d]: deleted, %s, %s\n", i, hm.table[i].key, hm.table[i].value);
}
}
}
// frees ressources
void hm_destroy(struct hm_t *hm) {
free(hm->table);
hm->table = NULL;
hm->size = -1;
hm->capacity = -1;
}
#ifndef HM_H
#define HM_H
#include <stdbool.h>
#define MAX_LEN 80
typedef enum {occupied, empty, deleted} state_t;
struct cell_t {
char key[MAX_LEN];
char value[MAX_LEN];
state_t state;
};
struct hm_t {
struct cell_t *table;
int size;
int capacity;
};
// allocate memory of the table to capacity
void hm_init(struct hm_t *hm, int capacity);
// insert the key - value pair: if key is already present
// we erase the old value. if the table is full we return false
bool hm_insert(struct hm_t *hm, char *key, char *value);
// changes the state of the table at the "key" position to deleted
void hm_delete(struct hm_t *hm, char *key);
// returns the value linked to the key if present
// return NULL otherwise
char *hm_get(struct hm_t hm, char *key);
// prints the state of the table
void hm_print(struct hm_t hm);
// frees ressources
void hm_destroy(struct hm_t *hm);
#endif // !HM_H
#include <stdlib.h>
#include <stdio.h>
#include "hm.h"
int main() {
struct hm_t hm;
hm_init(&hm, 10);
hm_print(hm);
hm_insert(&hm, "orestis", "123");
hm_insert(&hm, "paul", "234");
hm_insert(&hm, "delphine", "12lsk3");
hm_insert(&hm, "noria", "12as 3");
hm_insert(&hm, "andres", "123am");
/*hm_insert(&hm, "fabien", "236123");*/
hm_insert(&hm, "tewfik", "123skjs");
hm_insert(&hm, "florent", "12adsadcasd3");
hm_insert(&hm, "mickael", "123aaaaaaa");
hm_insert(&hm, "guido", "1asdljncaskjdnckajsd23");
hm_insert(&hm, "orestis", "1298392");
hm_print(hm);
printf("value is: %s\n", hm_get(hm, "orestis"));
printf("value is: %s\n", hm_get(hm, "guido"));
printf("value is: %s\n", hm_get(hm, "noria"));
printf("value is: %s\n", hm_get(hm, "bob"));
hm_delete(&hm, "fabien");
hm_delete(&hm, "paul");
hm_delete(&hm, "florent");
hm_delete(&hm, "orestis");
hm_delete(&hm, "paul");
hm_print(hm);
hm_insert(&hm, "paul", "aksjdnckajndcaksjdc");
hm_print(hm);
hm_destroy(&hm);
return EXIT_SUCCESS;
}
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#define NX 50
#define NY 100
int main() {
// allocate a 2d array for 50 x 100 of integers
int tab[NX][NY];
// fill the array with random values from 0 to 255 (inclusive)
srand(time(NULL));
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
tab[i][j] = rand() % 256;
}
}
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
printf("%d ", tab[i][j]);
}
printf("\n");
}
}
#include <stdio.h>
#include <strings.h>
int minimum(int a, int b) {
if (a < b) {
return a;
} else {
return b;
}
}
int old_pgcd(int a, int b) {
int min = minimum(a, b);
for (int i = min; i >= 2; --i) {
if ((a % i == 0) && (b % i == 0)) {
return i;
}
}
return 1;
}
int pgcd(int a, int b) {
while (b != 0) {
int rest = a % b;
a = b;
b = rest;
}
return a;
}
int main() {
printf("pgcd(%d, %d) = %d\n", 3, 4, pgcd(3, 4));
printf("pgcd(%d, %d) = %d\n", 5, 15, pgcd(5, 15));
printf("pgcd(%d, %d) = %d\n", 36, 90, pgcd(36, 90));
printf("pgcd(%d, %d) = %d\n", 4, 6, pgcd(4, 6));
}