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
  • main
1 result

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
  • master
1 result
Show changes
Showing
with 3451 additions and 2131 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 est pas encore visité récursivement.
## Remarque
* La récursivité est équivalent à 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.
# 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 court chemin
## 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 court chemin
## Formulation sous forme d'un graphe: Comment (3min)?
![Taux de change.](figs/taux_change.pdf){width=80%}
# Exemples d'application de plus court chemin
## 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 court chemin
## Conversion du problème en plus court chemin
* 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
# 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`.
# 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).
* 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 du 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 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
q = ajouter(q, v)
distance[s] = 0
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
q = ajouter(q, v)
distance[s] = 0
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
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 < priorite(tmp)
prec = tmp
tmp = tmp.suivant
prev.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):
distance[source] = 0
fp = file_p_vide()
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)
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)
---------------------------------------------------------
pour v dans sommets(graphe)
O(V) si v != s
distance[v] = infini
O(V) fp = enfiler(fp, v, distance[v]) // notre impl est nulle
------------------O(V * V)-------------------------------
tant que !est_vide(fp)
O(1) u, fp = defiler(fp)
---------------------------------------------------------
O(E) pour v dans voisinage de u
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
O(V) fp = changer_priorite(fp, v, n_distance)
---------------------------------------------------------
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^{(0)}=\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: "Introduction aux algorithmes"
date: "2022-10-05"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Introduction aux algorithmes III"
date: "2024-09-30"
---
# Rappel (1/2)
......@@ -23,7 +11,6 @@ patat:
* L'algorithme de la factorielle.
* L'algorithme du PPCM.
* Le début de l'algorithme du PGCD.
# Rappel (2/2)
......@@ -158,16 +145,15 @@ Par groupe de 3 (5-10min):
## Pseudo-code
```C
entier pgcd(m, n) {
entier pgcd(m, n)
tmp_n = n
tmp_m = m
tant que (tmp_m ne divise pas tmp_n) {
tant que (tmp_m ne divise pas tmp_n)
tmp = tmp_n
tmp_n = tmp_m
tmp_m = tmp modulo tmp_m
}
retourne tmp_m
}
```
# Le code du PGCD de 2 nombres
......@@ -200,7 +186,7 @@ void main() {
* Remplissage d'un tableau et recherche de la valeur minimal
* Anagrammes
* Palindromes
* Crible dratosthène
* Crible d’Ératosthène
. . .
......@@ -211,7 +197,7 @@ void main() {
\footnotesize
* Objets de même type: leur nombre est **connu à la compilation**;
* Stockés contigüement en mémoire (très efficace);
* Stockés de façon contiguë en mémoire (très efficace);
```C
#define SIZE 10
......@@ -235,8 +221,7 @@ void main() {
# Remarques
* Depuis `C99` possibilité d'avoir des tableaux dont la taille est *inconnue à
la compilation*;
* Depuis `C99` la taille peut être *inconnue à la compilation* (VLA);
```C
int size;
......@@ -273,6 +258,7 @@ for (int i = 0; i < SIZE; ++i) {
tab[i] = rand() / (double)RAND_MAX * 10.0 - 5.0;
// 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)
......@@ -304,7 +290,7 @@ pour i de 1 à SIZE - 1
int index = 0;
float min = tab[0];
for (int i = 1; i < SIZE; ++i) {
if min > tab[i] {
if (min > tab[i]) {
min = tab[i];
index = i;
}
......@@ -322,18 +308,10 @@ Trier un tableau par ordre croissant.
```C
ind = 0
tant que (ind < SIZE-1)
Trouver le minimum du tableau, tab_min[ind:SIZE].
Trouver le minimum du tableau, tab_min = min([ind:SIZE]).
Échanger tab_min avec tab[ind]
ind += 1
```
# Tri par sélection (2/2)
## Implémentation par groupe de 3
* Initialiser aléatoirement un tableau de `double` de taille 10;
* Afficher le tableau;
* Trier par sélection le tableau;
* Afficher le résultat trié;
* Vérifier algorithmiquement que le résultat est bien trié.
---
title: "Introduction aux algorithmes"
date: "2022-10-19"
title: "Introduction aux algorithmes IV"
date: "2024-10-07"
---
# Rappel
# Tri par sélection
## Quel est l'algorithme du tri par sélection?
. . .
1. Soit un tableau d'entiers, `tab[0:SIZE-1]` et `i=0`.
2. Trouver l'indice, `j`, de `tab[i:SIZE-2]` où la valeur est minimale.
1. Soit un tableau d'entiers, `tab[0:SIZE-1]` et `i = 0`.
2. Trouver l'indice, `j`, de `tab[i:SIZE-1]` où la valeur est minimale.
3. Échanger `tab[i]` et `tab[j]`.
4. `i+=1` et revenir à 2, tant que `j < SIZE-2`.
4. `i += 1` et revenir à 2, tant que `i < SIZE-1`.
# Tri par sélection
## Implémentation par groupe de 3
* Initialiser aléatoirement un tableau de `double` de taille 10;
* Afficher le tableau;
* Trier par sélection le tableau;
* Afficher le résultat trié;
* Vérifier algorithmiquement que le résultat est bien trié.
# Un type de tableau particulier
......@@ -82,6 +92,8 @@ char name[100] = "Paul is not 100 characters long.";
# Fonctions
\footnotesize
- Il existe une grande quantités de fonction pour la manipulation de chaînes de caractères dans `string.h`.
- Fonctions principales:
......@@ -98,11 +110,15 @@ char name[100] = "Paul is not 100 characters long.";
int strcmp(char *str1, char *str2);
```
- Pour avoir la liste complète: `man string`.
- Pour avoir la liste complète: `man 3 string`.
. . .
## Quels problèmes peuvent se produire avec `strlen`, `strcpy`, `strcmp`?
## Quel problème peut se produire avec `strlen`, `strcpy`, `strcmp`?
. . .
- Si `\0` est absent... on a un comportement indéfini.
# Les anagrammes
......@@ -141,8 +157,6 @@ int main() { // pseudo C
}
```
<!-- TODO: Live implémentation hors des cours? -->
# Les palindromes
Mot qui se lit pareil de droite à gauche que de gauche à droite:
......@@ -158,7 +172,7 @@ Mot qui se lit pareil de droite à gauche que de gauche à droite:
## Solution 1
```C
while (first_index < last_index {
while (first_index < last_index) {
if (mot[first_index] != mot [last_index]) {
return false;
}
......@@ -194,658 +208,3 @@ Algorithme de génération de nombres premiers.
* 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/mod/resource/view.php?id=1627712).
# Tableau à deux dimensions (1/4)
## Mais qu'est-ce donc?
. . .
* Un tableau où chaque cellule est un tableau.
## Quels cas d'utilisation?
. . .
* Tableau à double entrée;
* Image;
* Écran (pixels);
* Matrice (mathématique);
# Tableau à deux dimensions (2/4)
## Exemple: tableau à 3 lignes et 4 colonnes d'entiers
+-----------+-----+-----+-----+-----+
| `indices` | `0` | `1` | `2` | `3` |
+-----------+-----+-----+-----+-----+
| `0` | `7` | `4` | `7` | `3` |
+-----------+-----+-----+-----+-----+
| `1` | `2` | `2` | `9` | `2` |
+-----------+-----+-----+-----+-----+
| `2` | `4` | `8` | `8` | `9` |
+-----------+-----+-----+-----+-----+
## Syntaxe
```C
int tab[3][4]; // déclaration d'un tableau 4x3
tab[2][1]; // accès à la case 2, 1
tab[2][1] = 14; // assignation de 14 à la position 2, 1
```
# Tableau à deux dimensions (3/4)
## Exercice: déclarer et initialiser aléatoirement un tableau `50x100`
. . .
```C
#define NX 50
#define NY 100
int tab[NX][NY];
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
tab[i][j] = rand() % 256; // 256 niveaux de gris
}
}
```
## Exercice: afficher le tableau
. . .
```C
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
printf("%d ", tab[i][j]);
}
printf("\n");
}
```
# Tableau à deux dimensions (4/4)
## Attention
* Les éléments ne sont **jamais** initialisés.
* Les bornes ne sont **jamais** vérifiées.
```C
int tab[3][2] = { {1, 2}, {3, 4}, {5, 6} };
printf("%d\n", tab[2][1]); // affiche?
printf("%d\n", tab[10][9]); // affiche?
printf("%d\n", tab[3][1]); // affiche?
```
# La couverture de la reine
* Aux échecs la reine peut se déplacer horizontalement et verticalement
* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous
+-----+-----+-----+-----+-----+-----+-----+
| ` ` | `0` | `1` | `2` | `3` | `4` | `5` |
+-----+-----+-----+-----+-----+-----+-----+
| `0` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `1` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `2` | `*` | `*` | `R` | `*` | `*` | `*` |
+-----+-----+-----+-----+-----+-----+-----+
| `3` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `4` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
## Exercice
* En utilisant les conditions, les tableaux à deux dimensions, et des
`char` uniquement.
* Implémenter un programme qui demande à l'utilisateur d'entrer les
coordonnées de la reine et affiche un tableau comme ci-dessus pour un
échiquier `8x8`.
## Poster le résultat sur `Element`
# Types énumérés (1/2)
* Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.
```C
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
```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) == tuesday + tuesday; // true
```
# Types énumérés (2/2)
* Très utile dans les `switch ... case`{.C}
```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
## Réusiner votre couverture de la reine avec des `enum`
# 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
while (2^N < num) {
N += 1
}
```
. . .
2. Boucle
```C
while (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?
. . .
$$
4294967295
$$
# Les multiplications en binaire (1/2)
Que donne la multiplication de `1101` avec `0110`?
* L'addition 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écalade 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 (1/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!
<!-- # TODO --
<!-- ## Entiers, entiers non-signés -->
<!-- ## Complément à 1, 2 -->
<!-- ## Nombres à virgule flottante, simple/double précision -->
# Types composés: `struct`{.C} (1/6)
## Fractions
* Numérateur: `int num`;
* Dénominateur: `int denom`.
## Addition
```C
int num1 = 1, denom1 = 2;
int num2 = 1, denom2 = 3;
int num3 = num1 * denom2 + num2 * denom1;
int denom3 = denom1 * denom2;
```
## Pas super pratique....
# Types composés: `struct`{.C} (2/6)
## On peut faire mieux
* Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}.
```C
struct fraction { // déclaration du type
int32_t num, denom;
}
struct fraction frac; // déclaration de frac
```
# Types composés: `struct`{.C} (3/6)
## Simplifications
- `typedef`{.C} permet de définir un nouveau type.
```C
typedef unsinged int uint;
typedef struct fraction fraction_t;
typedef struct fraction {
int32_t num, denom;
} fraction_t;
```
- L'initialisation peut aussi se faire avec
```C
fraction_t frac = {1, -2}; // num = 1, denom = -2
fraction_t frac = {.denom = 1, .num = -2};
fraction_t frac = {.denom = 1}; // argl! .num non initialisé
fraction_t frac2 = frac; // copie
```
# Types composés: `struct`{.C} (4/6)
## Pointeurs
- Comme pour tout type, on peut avoir des pointeurs vers un `struct`{.C}.
- Les champs sont accessible avec le sélecteur `->`{.C}
```C
fraction_t *frac; // on crée un pointeur
frac->num = 1; // seg fault...
frac->denom = -1; // mémoire pas allouée.
```
![La représentation mémoire de
`fraction_t`.](figs/pointer_struct.svg){width=50%}
# Types composés: `struct`{.C} (5/6)
## Initialisation
- Avec le passage par **référence** on peut modifier un struct *en place*.
- Les champs sont accessible avec le sélecteur `->`{.C}
```C
void fraction_init(fraction_t *frac,
int32_t re, int32_t im)
{
// frac a déjà été allouée
frac->num = frac;
frac->denom = denom;
}
int main() {
fraction_t frac; // on alloue une fraction
fraction_init(&frac, 2, -1); // on l'initialise
}
```
# Types composés: `struct`{.C} (6/6)
## Initialisation version copie
* On peut allouer une fraction, l'initialiser et le retourner.
* La valeur retournée peut être copiée dans une nouvelle structure.
```C
fraction_t fraction_create(int32_t re, int32_t im) {
fraction_t frac;
frac.num = re;
frac.denom = im;
return frac;
}
int main() {
// on crée une fraction et on l'initialise
// en copiant la fraction créé par fraction_create
// deux allocation et une copie
fraction_t frac = fraction_create(2, -1);
}
```
<!-- # TODO jusqu'aux vacances -->
<!-- * Refactorisation -->
<!-- * Tris et complexité -->
<!-- * Récursivité -->
---
title: "Représentation des nombres et récursivité"
date: "2022-11-02"
title: "Tableaux à deux dimensions et récursivité"
date: "2024-10-14"
---
# Types énumérés (1/2)
* Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.
```C
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
```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
```
# Types énumérés (2/2)
* Très utile dans les `switch ... case`{.C}
```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
## Réusiner votre couverture de la reine avec des `enum`
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
tant que (2^N < 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 mutliplication 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écalade 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!
# Nombres à virgule (1/3)
# Rappel / devoirs: Crible d'Ératosthène
## Comment manipuler des nombres à virgule?
* 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.
$$
0.1 + 0.2 = 0.3.
$$
# Crible d'Ératosthène: solution
Facile non?
. . .
## Et ça?
\footnotesize
```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));
#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");
}
```
. . .
## Que se passe-t-il donc?
# Réusinage de code (refactoring)
# 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?
## Le réusinage est?
. . .
$$
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?
* 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**.
. . .
* 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?
## Avantages?
. . .
* 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.}}},
$$
* Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
* Réduction de la complexité.
. . .
On peut donc séparer la représentation en 2:
* La mantisse
* L'exposant
# Nombres à virgule flottante (2/2)
## Quel est l'avantage?
## "Make it work, make it nice, make it fast", Kent Beck.
. . .
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?
## Exercice:
. . .
* Réusiner le code se trouvant sur
[Cyberlearn](https://cyberlearn.hes-so.ch/pluginfile.php/703384/mod_resource/content/1/comprendre.c).
La précision des nombres est **variable**:
* On a uniquement un nombre de chiffres **significatifs**.
$$
123456\cdot 10^{23}+ 123456\cdot 10^{-23}.
$$
# Tableau à deux dimensions (1/4)
## Quel inconvénient y a-t-il?
## Mais qu'est-ce donc?
. . .
Ce mélange d'échelles entraîne un **perte de précision**.
* Un tableau où chaque cellule est un tableau.
# 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#/media/File:Float_example.svg)](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#/media/File:Float_example.svg)](figs/Float_example.svg)
## Quels cas d'utilisation?
. . .
\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?
. . .
* Tableau à double entrée;
* Image;
* Écran (pixels);
* Matrice (mathématique);
# Tableau à deux dimensions (2/4)
Pour un `float` (32 bits) la différence est à
$$
2^{-23}=1.19\cdot 10^{-7},
$$
Soit la précision de la mantisse.
## Exemple: tableau à 3 lignes et 4 colonnes d'entiers
## Comment le mesurer (par groupe)?
+-----------+-----+-----+-----+-----+
| `indices` | `0` | `1` | `2` | `3` |
+-----------+-----+-----+-----+-----+
| `0` | `7` | `4` | `7` | `3` |
+-----------+-----+-----+-----+-----+
| `1` | `2` | `2` | `9` | `2` |
+-----------+-----+-----+-----+-----+
| `2` | `4` | `8` | `8` | `9` |
+-----------+-----+-----+-----+-----+
. . .
## Syntaxe
```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);
int tab[3][4]; // déclaration d'un tableau 3 x 4
tab[2][1]; // accès case: ligne 2, colonne 1
tab[2][1] = 14; // assignation de 14 à la position 2, 1
```
# 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é
# Tableau à deux dimensions (3/4)
# Exemple de récursivité (1/2)
## La factorielle
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
}
```
\footnotesize
. . .
## Exercice:
## Que se passe-t-il quand on fait `factorial(4)`?
Déclarer et initialiser aléatoirement un tableau `50x100` avec des valeurs `0` à `255`
. . .
## On empile les appels
+----------------+----------------+----------------+----------------+
| | | | `factorial(1)` |
+----------------+----------------+----------------+----------------+
| | | `factorial(2)` | `factorial(2)` |
+----------------+----------------+----------------+----------------+
| | `factorial(3)` | `factorial(3)` | `factorial(3)` |
+----------------+----------------+----------------+----------------+
| `factorial(4)` | `factorial(4)` | `factorial(4)` | `factorial(4)` |
+----------------+----------------+----------------+----------------+
# Exemple de récursivité (2/2)
## La factorielle
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
#define NX 50
#define NY 100
int tab[NX][NY];
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
tab[i][j] = rand() % 256; // 256 niveaux de gris
}
}
```
## Exercice: afficher le tableau
. . .
## Que se passe-t-il quand on fait `factorial(4)`?
. . .
## On dépile les calculs
+----------------+----------------+----------------+----------------+
| `1` | | | |
+----------------+----------------+----------------+----------------+
| `factorial(2)` | `2 * 1 = 2` | | |
+----------------+----------------+----------------+----------------+
| `factorial(3)` | `factorial(3)` | `3 * 2 = 6` | |
+----------------+----------------+----------------+----------------+
| `factorial(4)` | `factorial(4)` | `factorial(4)` | `4 * 6 = 24` |
+----------------+----------------+----------------+----------------+
# La récursivité (1/4)
## Formellement
* Une condition de récursivité - qui *réduit* les cas successifs vers...
* Une condition d'arrêt - qui retourne un résultat
## Pour la factorielle, qui est qui?
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
printf("%d ", tab[i][j]);
}
printf("\n");
}
```
# La récursivité (2/4)
## Formellement
# Tableau à deux dimensions (4/4)
* Une condition de récursivité - qui *réduit* les cas successifs vers...
* Une condition d'arrêt - qui retourne un résultat
## Attention
## Pour la factorielle, qui est qui?
* Les éléments ne sont **jamais** initialisés.
* Les bornes ne sont **jamais** vérifiées.
```C
int factorial(int n) {
if (n > 1) { // Condition de récursivité
return n * factorial(n - 1);
} else { // Condition d'arrêt
return 1;
}
}
```
```C
int tab[3][2] = { {1, 2}, {3, 4}, {5, 6} };
printf("%d\n", tab[2][1]); // affiche?
printf("%d\n", tab[10][9]); // affiche?
printf("%d\n", tab[3][1]); // affiche?
```
# La couverture de la reine
# La récursivité (3/4)
\footnotesize
## Exercice: trouver l'$\varepsilon$-machine pour un `double`
* Aux échecs la reine peut se déplacer horizontalement et verticalement
* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous
+-----+-----+-----+-----+-----+-----+-----+
| ` ` | `0` | `1` | `2` | `3` | `4` | `5` |
+-----+-----+-----+-----+-----+-----+-----+
| `0` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `1` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `2` | `*` | `*` | `R` | `*` | `*` | `*` |
+-----+-----+-----+-----+-----+-----+-----+
| `3` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `4` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
## Exercice
* En utilisant les structures de contrôle, les tableaux à deux dimensions, et des
`char` uniquement.
* Implémenter un programme qui, à partir des
coordonnées de la reine, affiche un tableau comme ci-dessus pour un
échiquier `8x8`.
## Poster le résultat sur `Element`
. . .
# Types énumérés (1/2)
Rappelez-vous vous l'avez fait en style **impératif** plus tôt.
* Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.
. . .
```C
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
```C
double epsilon_machine(double eps) {
if (1.0 + eps != 1.0) {
return epsilon_machine(eps / 2.0);
} else {
return eps;
}
}
```
```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
# La récursivité (4/4)
```C
enum days d = monday;
(d + 2) == monday + monday; // true
```
\footnotesize
# Types énumérés (2/2)
## Exercice: que fait ce code récursif?
* Très utile dans les `switch ... case`{.C}
```C
void recurse(int n) {
printf("%d ", n % 2);
if (n / 2 != 0) {
recurse(n / 2);
} else {
printf("\n");
```C
enum days d = monday;
switch (d) {
case monday:
// trucs
break;
case tuesday:
printf("0 ou 1\n");
break;
}
}
recurse(13);
```
. . .
```
* Le compilateur vous prévient qu'il en manque!
```C
recurse(13): n = 13, n % 2 = 1, n / 2 = 6,
recurse(6): n = 6, n % 2 = 0, n / 2 = 3,
recurse(3): n = 3, n % 2 = 1, n / 2 = 1,
recurse(1): n = 1, n % 2 = 1, n / 2 = 0.
# Utilisation des types énumérés
// affiche: 1 1 0 1
```
## Réusiner votre couverture de la reine avec des `enum`
. . .
A faire à la maison comme exercice!
Affiche la représentation binaire d'un nombre!
---
title: "Récursivité et complexité"
date: "2022-11-09"
title: "Récursivité et représentation des nombres"
date: "2024-10-29"
---
# La récursivité (1/2)
# La récursivité
* Code récursif
\Huge La récursivité
```C
int factorial(int n) {
if (n > 1) { // Condition de récursivité
return n * factorial(n - 1);
} else { // Condition d'arrêt
return 1;
}
# La factorielle: Code impératif
* Code impératif
```C
int factorial(int n) {
int f = 1;
for (int i = 1; i < n; ++i) {
f *= i;
}
return f;
}
```
# Exemple de récursivité (1/2)
## La factorielle
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
}
```
. . .
* Code impératif
## Que se passe-t-il quand on fait `factorial(4)`?
. . .
## On empile les appels
+----------------+----------------+----------------+----------------+
| | | | `factorial(1)` |
+----------------+----------------+----------------+----------------+
| | | `factorial(2)` | `factorial(2)` |
+----------------+----------------+----------------+----------------+
| | `factorial(3)` | `factorial(3)` | `factorial(3)` |
+----------------+----------------+----------------+----------------+
| `factorial(4)` | `factorial(4)` | `factorial(4)` | `factorial(4)` |
+----------------+----------------+----------------+----------------+
```C
int factorial(int n) {
int f = 1;
for (int i = 1; i < n; ++i) {
f *= i;
}
return f;
# Exemple de récursivité (2/2)
## La factorielle
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
```
}
```
. . .
## Que se passe-t-il quand on fait `factorial(4)`?
. . .
## On dépile les calculs
+----------------+----------------+----------------+----------------+
| `1` | | | |
+----------------+----------------+----------------+----------------+
| `factorial(2)` | `2 * 1 = 2` | | |
+----------------+----------------+----------------+----------------+
| `factorial(3)` | `factorial(3)` | `3 * 2 = 6` | |
+----------------+----------------+----------------+----------------+
| `factorial(4)` | `factorial(4)` | `factorial(4)` | `4 * 6 = 24` |
+----------------+----------------+----------------+----------------+
# La récursivité (1/4)
## Formellement
* Une condition de récursivité - qui *réduit* les cas successifs vers...
* Une condition d'arrêt - qui retourne un résultat
## Pour la factorielle, qui est qui?
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
}
```
# La récursivité (2/4)
## Formellement
* Une condition de récursivité - qui *réduit* les cas successifs vers...
* Une condition d'arrêt - qui retourne un résultat
## Pour la factorielle, qui est qui?
```C
int factorial(int n) {
if (n > 1) { // Condition de récursivité
return n * factorial(n - 1);
} else { // Condition d'arrêt
return 1;
}
}
```
# La récursivité (3/4)
## Exercice: trouver l'$\varepsilon$-machine pour un `double`
. . .
Rappelez-vous vous l'avez fait en style **impératif** plus tôt.
. . .
```C
double epsilon_machine(double eps) {
if (1.0 + eps != 1.0) {
return epsilon_machine(eps / 2.0);
} else {
return eps;
}
}
```
# La récursivité (4/4)
\footnotesize
## Exercice: que fait ce code récursif?
```C
void recurse(int n) {
printf("%d ", n % 2);
if (n / 2 != 0) {
recurse(n / 2);
} else {
printf("\n");
}
}
recurse(13);
```
. . .
```C
recurse(13): n = 13, n % 2 = 1, n / 2 = 6,
recurse(6): n = 6, n % 2 = 0, n / 2 = 3,
recurse(3): n = 3, n % 2 = 1, n / 2 = 1,
recurse(1): n = 1, n % 2 = 1, n / 2 = 0.
// affiche: 1 1 0 1
```
. . .
Affiche la représentation binaire d'un nombre!
# Exercice: réusinage et récursivité (1/4)
......@@ -139,6 +284,10 @@ int fib_imp(int n) {
}
```
# Exponentiation rapide
\Huge L'exponentiation rapide ou indienne
# Exponentiation rapide ou indienne (1/4)
## But: Calculer $x^n$
......@@ -148,14 +297,15 @@ int fib_imp(int n) {
. . .
```C
int pow(int x, int n) {
double pow(double x, int n) {
if (0 == n) {
return 1;
}
double p = x;
for (int i = 1; i < n; ++i) {
x = x * x; // x *= x
p = p * x; // p *= x
}
return x;
return p;
}
```
......@@ -172,7 +322,7 @@ int pow(int x, int n) {
. . .
```C
int pow(x, n) {
double pow(double x, int n) {
if (n != 0) {
return x * pow(x, n-1);
} else {
......@@ -205,8 +355,8 @@ $$
## Le vrai algorithme
* Si n est pair: calculer $\left(x^{n/2}\right)^2$,
* Si n est impair: calculer $x \cdot \left(x^{(n-1)/2}\right)^2$.
* 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
......@@ -214,8 +364,8 @@ $$
```C
double pow(double x, int n) {
if (1 == n) {
return x;
if (0 == n) {
return 1;
} else if (n % 2 == 0) {
return pow(x, n / 2) * pow(x, n/2);
} else {
......@@ -225,264 +375,3 @@ double pow(double x, int n) {
```
# Efficacité d'un algorithmique
Comment mesurer l'efficacité d'un algorithme?
. . .
* Mesurer le temps CPU,
* Mesurer le temps d'accès à la mémoire,
* Mesurer la place prise mémoire,
. . .
Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc!
## Mesure du temps CPU
```C
#include <time.h>
struct timespec tstart={0,0}, tend={0,0};
clock_gettime(CLOCK_MONOTONIC, &tstart);
// some computation
clock_gettime(CLOCK_MONOTONIC, &tend);
printf("computation about %.5f seconds\n",
((double)tend.tv_sec + 1e-9*tend.tv_nsec) -
((double)tstart.tv_sec + 1e-9*tstart.tv_nsec));
```
# Programme simple: mesure du temps CPU
## Preuve sur un [petit exemple](../source_codes/complexity/sum.c)
```bash
source_codes/complexity$ make bench
RUN ONCE -O0
the computation took about 0.00836 seconds
RUN ONCE -O3
the computation took about 0.00203 seconds
RUN THOUSAND TIMES -O0
the computation took about 0.00363 seconds
RUN THOUSAND TIMES -O3
the computation took about 0.00046 seconds
```
Et sur votre machine les résultats seront **différents**.
. . .
## Conclusion
* Nécessité d'avoir une mesure indépendante du/de la
matériel/compilateur/façon de mesurer/météo.
# Analyse de complexité algorithmique (1/4)
* On analyse le **temps** pris par un algorithme en fonction de la **taille de
l'entrée**.
## Exemple: recherche d'un élément dans une liste triée de taille N
```C
int sorted_list[N];
bool in_list = is_present(N, sorted_list, elem);
```
* Plus `N` est grand, plus l'algorithme prend de temps sauf si...
. . .
* l'élément est le premier de la liste (ou à une position toujours la même).
* ce genre de cas pathologique ne rentre pas en ligne de compte.
# Analyse de complexité algorithmique (2/4)
## Recherche linéaire
```C
bool is_present(int n, int tab[], int elem) {
for (int i = 0; i < n; ++i) {
if (tab[i] == elem) {
return true;
} else if (elem < tab[i]) {
return false;
}
}
return false;
}
```
* Dans le **meilleurs des cas** il faut `1` comparaison.
* Dans le **pire des cas** (élément absent p.ex.) il faut `n` comparaisons.
. . .
La **complexité algorithmique** est proportionnelle à `N`: on double la taille
du tableau $\Rightarrow$ on double le temps pris par l'algorithme.
# Analyse de complexité algorithmique (3/4)
## Recherche dichotomique
```C
bool is_present_binary_search(int n, int tab[], int elem) {
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = (right + left) / 2;
if (tab[mid] < elem) {
left = mid + 1;
} else if (tab[mid] > elem) {
right = mid - 1;
} else {
return true;
}
}
return false;
}
```
# Analyse de complexité algorithmique (4/4)
## Recherche dichotomique
![Source: [Wikipédia](https://upload.wikimedia.org/wikipedia/commons/a/aa/Binary_search_complexity.svg)](figs/Binary_search_complexity.svg){width=80%}
. . .
* Dans le **meilleurs de cas** il faut `1` comparaison.
* Dans le **pire des cas** il faut $\log_2(N)+1$ comparaisons
. . .
## Linéaire vs dichotomique
* $N$ vs $\log_2(N)$ comparaisons logiques.
* Pour $N=1000000$: `1000000` vs `21` comparaisons.
# Notation pour la complexité
## Constante de proportionnalité
* Pour la recherche linéaire ou dichotomique, on a des algorithmes qui sont $\sim N$ ou $\sim \log_2(N)$
* Qu'est-ce que cela veut dire?
. . .
* Temps de calcul est $t=C\cdot N$ (où $C$ est le temps pris pour une comparaisons sur une machine/compilateur donné)
* La complexité ne dépend pas de $C$.
## Le $\mathcal{O}$ de Leibnitz
* Pour noter la complexité d'un algorithme on utilise le symbole $\mathcal{O}$ (ou "grand Ô de").
* Les complexités les plus couramment rencontrées sont
. . .
$$
\mathcal{O}(1),\quad \mathcal{O}(\log(N)),\quad \mathcal{O}(N),\quad
\mathcal{O}(\log(N)\cdot N), \quad \mathcal{O}(N^2), \quad
\mathcal{O}(N^3).
$$
# Ordres de grandeur
\begin{table}[!h]
\begin{center}
\caption{Valeurs approximatives de quelques fonctions usuelles de complexité.}
\medskip
\begin{tabular}{|c|c|c|c|c|}
\hline
$\log_2(N)$ & $\sqrt{N}$ & $N$ & $N\log_2(N)$ & $N^2$ \\
\hline\hline
$3$ & $3$ & $10$ & $30$ & $10^2$ \\
\hline
$6$ & $10$ & $10^2$ & $6\cdot 10^2$ & $10^4$ \\
\hline
$9$ & $31$ & $10^3$ & $9\cdot 10^3$ & $10^6$ \\
\hline
$13$ & $10^2$ & $10^4$ & $1.3\cdot 10^5$ & $10^8$ \\
\hline
$16$ & $3.1\cdot 10^2$ & $10^5$ & $1.6\cdot 10^6$ & $10^{10}$ \\
\hline
$19$ & $10^3$ & $10^6$ & $1.9\cdot 10^7$ & $10^{12}$ \\
\hline
\end{tabular}
\end{center}
\end{table}
# Quelques exercices (1/3)
## Complexité de l'algorithme de test de primalité naïf?
```C
for (i = 2; i < sqrt(N); ++i) {
if (N % i == 0) {
return false;
}
}
return true;
```
. . .
## Réponse
$$
\mathcal{O}(\sqrt{N}).
$$
# Quelques exercices (2/3)
## Complexité de trouver le minimum d'un tableau?
```C
int min = MAX;
for (i = 0; i < N; ++i) {
if (tab[i] < min) {
min = tab[i];
}
}
return min;
```
. . .
## Réponse
$$
\mathcal{O}(N).
$$
# Quelques exercices (3/3)
## Complexité du tri par sélection?
```C
int ind = 0
while (ind < SIZE-1) {
min = find_min(tab[ind:SIZE]);
swap(min, tab[ind]);
ind += 1
}
```
. . .
## Réponse
### `min = find_min`
$$
(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2).
$$
## Finalement
$$
\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{swaps})=\mathcal{O}(N^2).
$$
---
title: "Tris"
date: "2022-11-16"
title: "Représentation des nombres, tris, et complexité"
date: "2024-11-11"
header-includes: |
\usepackage{xcolor}
---
# Rappel: Complexité
# Représentation des nombres
\footnotesize
\Huge La représentation des nombres
# 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`.
Soit `tab` un tableau de longueur `N` de `double`.
## Nombres binaires: Les nombres en base 2
1. Comment déclare-t-on un tel tableau?
+-------+-------+-------+-------+-------+-------+-------+-------+
| $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
$$
. . .
```C
double tab[N];
```
$$
= 247.
$$
2. Quelle est la complexité du calcul de la moyenne de `tab`?
# Conversion de décimal à binaire (1/2)
## Convertir 11 en binaire?
. . .
```C
double mean = 0.0;
for (int i = 0; i < N; ++i) { // N assignations
mean += tab[i]; // N additions
}
mean /= N; // O(N)
```
* 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.
. . .
3. Quelle est la complexité du calcul de l'écart type de `tab` ($\sigma=\sqrt{\sum_i (t_i - m)^2}/N$?
## Algorithme
1. Initialisation
```C
num = 247
N = 0
tant que (2^(N+1) < num) {
N += 1
}
```
. . .
```C
double mean = moyenne(N, tab); // O(N)
double dev = 0.0;
for (int i = 0; i < N; ++i) {
dev += pow(tab[i] - moyenne, 2); // N tours
}
dev = sqrt(dev); dev /= N; // O(2*N) = O(N)
```
2. Boucle
# Tri par insertion (1/3)
```C
tant que (N >= 0) {
bit = num / 2^N
num = num % 2^N
N -= 1
}
```
## But
# Les additions en binaire
* trier un tableau par ordre croissant
Que donne l'addition `1101` avec `0110`?
## Algorithme
* L'addition est la même que dans le système décimal
Prendre un élément du tableau et le mettre à sa place parmis les éléments déjà
triés du tableau.
```
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?
![Tri par insertion d'un tableau d'entiers](figs/tri_insertion.svg)
. . .
## Dépassement de capacité: le nombre est "tronqué"
# Tri par insertion (2/3)
* `10011 (19) -> 0011 (3)`.
* On fait "le tour"."
## Exercice: Proposer un algorithme (en C)
# Entier non-signés minimal/maximal
* Quel est l'entier non-signé maximal représentable avec 4 bit?
. . .
```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;
}
}
```
$$
(1111)_2 = 8+4+2+1 = 15
$$
* Quel est l'entier non-signé minimal représentable avec 4 bit?
. . .
# Tri par insertion (3/3)
$$
(0000)_2 = 0+0+0+0 = 0
$$
## Question: Quelle est la complexité?
* Quel est l'entier non-signé min/max représentable avec N bit?
. . .
* 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)$
$$
0\mbox{ et }2^N-1.
$$
* Donc `uint32_t?` maximal est?
. . .
* Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$
* Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$
$$
2^{32}-1=4'294'967'295
$$
# L'algorithme à la main
## Exercice *sur papier*
# Les multiplications en binaire (1/2)
* Trier par insertion le tableau `[5, -2, 1, 3, 10]`
Que donne la multiplication de `1101` avec `0110`?
```C
* 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)
\footnotesize
## 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!
# Les types énumérés
\Huge Les types énumérés
# Types énumérés (1/2)
* Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.
```C
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
```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
```
# Types énumérés (2/2)
* Très utile dans les `switch ... case`{.C}
```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
## Réusiner votre couverture de la reine avec des `enum`
A faire à la maison comme exercice!
# Le tri rapide ou quicksort
\Huge Tri rapide ou quicksort
# Tri rapide ou quicksort (1/8)
......@@ -177,7 +442,7 @@ rien quicksort(entier tableau[], entier ind_min, entier ind_max)
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)
si (longueur(tableau[ind_pivot+1:ind_max]) != 0)
quicksort(tableau, ind_pivot + 1, ind_max)
```
......@@ -253,7 +518,7 @@ int partition(int size, int array[size], int first, int last) {
}
```
# Tri rapide ou quicksort (8/8)
<!-- # Tri rapide ou quicksort (8/8)
## Quelle est la complexité du tri rapide?
......@@ -267,7 +532,7 @@ int partition(int size, int array[size], int first, int last) {
* 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))$.
* En moyenne: $\mathcal{O}(N\cdot \log_2(N))$. -->
# L'algorithme à la main
......@@ -291,5 +556,3 @@ int partition(int size, int array[size], int first, int last) {
```
---
title: "Backtracking et piles"
date: "2022-11-23"
title: "Tris et complexité"
date: "2024-11-18"
header-includes: |
\usepackage{xcolor}
---
# Tri à bulle (1/4)
# L'algorithme à la main
## Exercice *sur papier*
* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
```C
## 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)
# 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:
## Quelle est la complexité du tri à bulles?
| 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** |
. . .
* 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).
* On obtient le tableau:
# L'algorithme à la main
| 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} |
## Exercice *sur papier*
# Illustration sur un exemple (4/6)
* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
* On passe au 2ème bit et on obtient le tableau:
```C
| 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?
# 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).
* 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);
}
``` -->
## Conséquence
# Un peu plus de détails (1/2)
* Deux reines ne partagent pas la même rangée, colonne, ou diagonale.
* Donc chaque solution a **une** reine **par colonne** ou **ligne**.
## La fonction `decaler()`
## Généralisation
```python
rien decaler(entier taille, entier tab[taille], entier val):
pour i de 0 à taille-1:
tab[i] -= val
```
* 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%}
## La fonction `echanger()`
# Problème des 2-reines
```python
rien echanger(entier tab[], entier tab2[])
# échanger les tableaux (sans copier les valeurs)
```
![Le problème des 2 reines n'a pas de solution.](figs/2reines.svg){width=50%}
# Un peu plus de détails (2/2)
# Comment trouver les solutions?
## La fonction `alveole_0()`
* 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à.
```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
```
. . .
* Le jeu prend fin quand on a énuméré *toutes* les possibilités de poser les
reines.
## La fonction `alveole_1()`
# Problème des 3-reines
```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 problème des 3 reines n'a pas de solution non plus.](figs/3reines.svg)
# Le tri par fusion (merge sort)
# Problème des 4-reines
\Huge Le tri par fusion (merge sort)
![Le problème des 4 reines a une solution.](figs/4reines.svg)
# Tri par fusion (merge sort)
# Problème des 4-reines, symétrie
* 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. -->
![Le problème des 4 reines a une autre solution (symétrie
horizontale).](figs/4reines_sym.svg)
<!-- 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. -->
# Problème des 5 reines
# Principe de l'algorithme
## Exercice: Trouver une solution au problème des 5 reines
* 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é)
* Faire une capture d'écran / une photo de votre solution et la poster sur
matrix.
. . .
```C
* 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
```
# Quelques observations sur le problème
<!-- ## Complexité
L'algorithme présenté précédemment nécessite un certain nombre d'opérations lié à la taille $N$ du tableau.
* 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.
Il y a essentiellement $\log_2(N)$ étapes.
# Le code du problème des 8 reines (1/N)
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.
## Quelle structure de données?
Ainsi, la complexité du tri par fusion est $\mathcal{O}(N\cdot \log_2(N)$. -->
# L'efficacité d'un algorithmique
\Huge L'efficacité d'un algorithmique
# Efficacité d'un algorithmique
Comment mesurer l'efficacité d'un algorithme?
. . .
Une matrice de booléens fera l'affaire:
* Mesurer le temps CPU,
* Mesurer le temps d'accès à la mémoire,
* Mesurer la place prise mémoire,
. . .
Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc!
## Mesure du temps CPU
```C
bool board[n][n];
#include <time.h>
struct timespec tstart={0,0}, tend={0,0};
clock_gettime(CLOCK_MONOTONIC, &tstart);
// some computation
clock_gettime(CLOCK_MONOTONIC, &tend);
printf("computation about %.5f seconds\n",
((double)tend.tv_sec + 1e-9*tend.tv_nsec) -
((double)tstart.tv_sec + 1e-9*tstart.tv_nsec));
```
## Quelles fonctionnalités?
# Programme simple: mesure du temps CPU
## Preuve sur un [petit exemple](../source_codes/complexity/sum.c)
```bash
source_codes/complexity$ make bench
RUN ONCE -O0
the computation took about 0.00836 seconds
RUN ONCE -O3
the computation took about 0.00203 seconds
RUN THOUSAND TIMES -O0
the computation took about 0.00363 seconds
RUN THOUSAND TIMES -O3
the computation took about 0.00046 seconds
```
Et sur votre machine les résultats seront **différents**.
. . .
## Conclusion
* Nécessité d'avoir une mesure indépendante du/de la
matériel/compilateur/façon de mesurer/météo.
# Analyse de complexité algorithmique (1/4)
* On analyse le **temps** pris par un algorithme en fonction de la **taille de
l'entrée**.
## Exemple: recherche d'un élément dans une liste triée de taille N
```C
// 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);
int sorted_list[N];
bool in_list = is_present(N, sorted_list, elem);
```
# Le code du problème des 8 reines (2/N)
* Plus `N` est grand, plus l'algorithme prend de temps sauf si...
. . .
* l'élément est le premier de la liste (ou à une position toujours la même).
* ce genre de cas pathologique ne rentre pas en ligne de compte.
# Analyse de complexité algorithmique (2/4)
## Le calcul du nombre de solutions
## Recherche linéaire
```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
bool is_present(int n, int tab[], int elem) {
for (int i = 0; i < n; ++i) {
if (tab[i] == elem) {
return true;
} else if (elem < tab[i]) {
return false;
}
}
return false;
}
```
# Le code du problème des 8 reines (3/N)
* Dans le **meilleurs des cas** il faut `1` comparaison.
* Dans le **pire des cas** (élément absent p.ex.) il faut `n` comparaisons.
## Le calcul du nombre de solutions
. . .
La **complexité algorithmique** est proportionnelle à `N`: on double la taille
du tableau $\Rightarrow$ on double le temps pris par l'algorithme.
# Analyse de complexité algorithmique (3/4)
## Recherche dichotomique
```C
// 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
bool is_present_binary_search(int n, int tab[], int elem) {
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = (right + left) / 2;
if (tab[mid] < elem) {
left = mid + 1;
} else if (tab[mid] > elem) {
right = mid - 1;
} else {
return true;
}
}
return false;
}
```
# Le code du problème des 8 reines (4/N)
# Analyse de complexité algorithmique (4/4)
## Compris? Alors écrivez le code et postez le!
## Recherche dichotomique
![Source: [Wikipédia](https://upload.wikimedia.org/wikipedia/commons/a/aa/Binary_search_complexity.svg)](figs/Binary_search_complexity.svg){width=80%}
. . .
## Le nombre de solutions
* Dans le **meilleurs de cas** il faut `1` comparaison.
* Dans le **pire des cas** il faut $\log_2(N)+1$ comparaisons
\footnotesize
. . .
## Linéaire vs dichotomique
* $N$ vs $\log_2(N)$ comparaisons logiques.
* Pour $N=1000000$: `1000000` vs `21` comparaisons.
# Notation pour la complexité
## Constante de proportionnalité
* Pour la recherche linéaire ou dichotomique, on a des algorithmes qui sont $\sim N$ ou $\sim \log_2(N)$
* Qu'est-ce que cela veut dire?
. . .
* Temps de calcul est $t=C\cdot N$ (où $C$ est le temps pris pour une comparaisons sur une machine/compilateur donné)
* La complexité ne dépend pas de $C$.
## Le $\mathcal{O}$ de Leibnitz
* Pour noter la complexité d'un algorithme on utilise le symbole $\mathcal{O}$ (ou "grand Ô de").
* Les complexités les plus couramment rencontrées sont
. . .
$$
\mathcal{O}(1),\quad \mathcal{O}(\log(N)),\quad \mathcal{O}(N),\quad
\mathcal{O}(\log(N)\cdot N), \quad \mathcal{O}(N^2), \quad
\mathcal{O}(N^3).
$$
# Ordres de grandeur
\begin{table}[!h]
\begin{center}
\caption{Valeurs approximatives de quelques fonctions usuelles de complexité.}
\medskip
\begin{tabular}{|c|c|c|c|c|}
\hline
$\log_2(N)$ & $\sqrt{N}$ & $N$ & $N\log_2(N)$ & $N^2$ \\
\hline\hline
$3$ & $3$ & $10$ & $30$ & $10^2$ \\
\hline
$6$ & $10$ & $10^2$ & $6\cdot 10^2$ & $10^4$ \\
\hline
$9$ & $31$ & $10^3$ & $9\cdot 10^3$ & $10^6$ \\
\hline
$13$ & $10^2$ & $10^4$ & $1.3\cdot 10^5$ & $10^8$ \\
\hline
$16$ & $3.1\cdot 10^2$ & $10^5$ & $1.6\cdot 10^6$ & $10^{10}$ \\
\hline
$19$ & $10^3$ & $10^6$ & $1.9\cdot 10^7$ & $10^{12}$ \\
\hline
\end{tabular}
\end{center}
\end{table}
# Quelques exercices (1/3)
## Complexité de l'algorithme de test de primalité naïf?
```C
// 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;
}
}
for (i = 2; i < sqrt(N); ++i) {
if (N % i == 0) {
return false;
}
}
return true;
```
. . .
# Le code du problème des 8 reines (5/N)
## Réponse
\footnotesize
$$
\mathcal{O}(\sqrt{N}).
$$
# Quelques exercices (2/3)
## Placer devant
## Complexité de trouver le minimum d'un tableau?
```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;
}
int min = MAX;
for (i = 0; i < N; ++i) {
if (tab[i] < min) {
min = tab[i];
}
}
return min;
```
. . .
## Réponse
$$
\mathcal{O}(N).
$$
# Quelques exercices (3/3)
## Complexité du tri par sélection?
```C
int ind = 0;
while (ind < SIZE-1) {
min = find_min(tab[ind:SIZE]);
swap(min, tab[ind]);
ind += 1;
}
```
. . .
## Réponse
### `min = find_min`
$$
(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2).
$$
## Finalement
$$
\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{swaps})=\mathcal{O}(N^2).
$$
---
title: "Piles"
date: "2022-12-07"
title: "Tris, complexité, backtracking et assertions"
date: "2024-11-25"
header-includes: |
\usepackage{xcolor}
---
# Les piles (1/5)
# Le tri à bulle
## Qu'est-ce donc?
\Huge Le tri à bulle
* Structure de données abstraite...
# 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?
. . .
* de type `LIFO` (*Last in first out*).
* 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.
![Une pile où on ajoute A, puis B avant de les retirer. Source:
[Wikipedia](https://upload.wikimedia.org/wikipedia/commons/e/e1/Stack_%28data_structure%29_LIFO.svg)](figs/Stack.svg){width=70%}
# Tri à bulle (2/4)
## Des exemples de la vraie vie
## Exemple
. . .
![Tri à bulles d'un tableau d'entiers](figs/tri_bulles.svg)
* Pile d'assiettes, de livres, ...
* Adresses visitées par un navigateur web.
* Les calculatrices du passé (en polonaise inverse).
* Les boutons *undo* de vos éditeurs de texte (aka *u* dans vim).
# Les piles (2/5)
# Tri à bulle (3/4)
## Fonctionnalités
## Exercice: écrire l'algorithme (poster le résultat sur matrix)
. . .
1. Empiler (push): ajouter un élément sur la pile.
2. Dépiler (pop): retirer l'élément du sommet de la pile et le retrouner.
3. Liste vide? (is_empty?).
```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?
. . .
4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler).
5. Nombre d'éléments (length).
* 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).
## Comment faire les 4,5 à partir de 1 à 3?
# L'algorithme à la main
. . .
## Exercice *sur papier*
* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
```C
4. Dépiler l'élément, le copier, puis l'empiler à nouveau.
5. Dépiler jusqu'à ce que la pile soit vide, puis empiler à nouveau.
. . .
## Existe en deux goûts
* Pile avec ou sans limite de capacité (à concurrence de la taille de la
mémoire).
# Les piles (3/5)
## Implémentation
* Jusqu'ici on n'a pas du tout parlé d'implémentation (d'où le nom de structure
abstraite).
* Pas de choix unique d'implémentation.
## Quelle structure de données allons nous utiliser?
. . .
Et oui vous avez deviné: un tableau!
## La structure: de quoi avons-nous besoin (pile de taille fixe)?
. . .
```C
#define MAX_CAPACITY 500
typedef struct _stack {
int data[MAX_CAPACITY]; // les données
int top; // indice du sommet
} stack;
```
# Les piles (4/5)
# Tri par insertion (1/3)
## Initialisation
## But
. . .
* trier un tableau par ordre croissant
```C
void stack_init(stack *s) {
s->top = -1;
}
```
## Algorithme
## Est vide?
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)
```C
bool stack_is_empty(stack s) {
return s.top == -1;
}
```
# Tri par insertion (2/3)
## Empiler (ajouter un élément au sommet)
## Exercice: Proposer un algorithme (en C)
. . .
```C
void stack_push(stack *s, int val) {
s->top += 1;
s->data[s->top] = val;
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;
}
}
```
# Les piles (5/5)
# Tri par insertion (3/3)
## Question: Quelle est la complexité?
. . .
## Dépiler (enlever l'élément du sommet)
* 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
int stack_pop(stack *s) {
s->top -= 1;
return s->data[s->top+1];
}
```
# 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)
```
## Jeter un oeil (regarder le sommet)
# 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)
```
. . .
```C
int stack_peek(stack *s) {
return s->data[s->top];
}
* 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);
```
## Quelle est la complexité de ces opérations?
# 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)
```
. . .
## Voyez-vous des problèmes potentiels avec cette implémentation?
* Au final: $\mathcal{O}(N\log_2(N))$.
. . .
# Complexité algorithmique du quick-sort (1/2)
* Empiler avec une pile pleine.
* Dépiler avec une pile vide.
* Jeter un oeil au sommet d'une pile vide.
## Pseudocode: quicksort
# Gestion d'erreur, level 0
```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)
```
* Il y a plusieurs façon de traiter les erreur:
* Ne rien faire (laisser la responsabilité à l'utilisateur).
* Faire paniquer le programme (il plante plus ou moins violemment).
* Utiliser des codes d'erreurs.
## La panique
* En C, on a les `assert()` pour faire paniquer un programme.
# Complexité algorithmique du quick-sort (2/2)
## Quelle est la complexité du tri rapide?
# Assertions (1/3)
. . .
```C
#include <assert.h>
void assert(int expression);
```
* 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))$.
## Qu'est-ce donc?
# Le problème des 8-reines
- Macro permettant de tester une condition lors de l'exécution d'un programme:
- Si `expression == 0`{.C} (condition fausse), `assert()`{.C} affiche un message d'erreur sur `stderr`{.C} et termine l'exécution du programme.
- Sinon l'exécution se poursuit normalement.
- Peuvent être désactivés à la compilation avec `-DNDEBUG` (équivalent à `#define
NDEBUG`)
\Huge Le problème des 8-reines
## À quoi ça sert?
# Problème des 8-reines
- Permet de réaliser des tests unitaires.
- Permet de tester des conditions catastrophiques d'un programme.
- **Ne permet pas** de gérer les erreurs.
* Placer 8 reines sur un échiquier de $8 \times 8$.
* Sans que les reines ne puissent se menacer mutuellement (92 solutions).
# Assertions (2/3)
## Conséquence
<!-- \footnotesize -->
* Deux reines ne partagent pas la même rangée, colonne, ou diagonale.
* Donc chaque solution a **une** reine **par colonne** ou **ligne**.
## Exemple
## Généralisation
```C
#include <assert.h>
void stack_push(stack *s, int val) {
assert(s->top < MAX_CAPACITY-1);
s->top += 1;
s->data[s->top] = val;
}
int stack_pop(stack *s) {
assert(s->top >= 0);
s->top -= 1;
return s->data[s->top+1];
}
int stack_peek(stack *s) {
assert(s->top >= 0);
return s->data[s->top];
}
```
* Placer $N$ reines sur un échiquier de $N \times
N$.
- Exemple de **backtracking** (retour en arrière) $\Rightarrow$ récursivité.
# Assertions (3/3)
![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%}
## Cas typiques d'utilisation
# Problème des 2-reines
- Vérification de la validité des pointeurs (typiquement `!= NULL`{.C}).
- Vérification du domaine des indices (dépassement de tableau).
![Le problème des 2 reines n'a pas de solution.](figs/2reines.svg){width=50%}
## Bug vs. erreur de *runtime*
# Comment trouver les solutions?
- Les assertions sont là pour détecter les bugs (erreurs d'implémentation).
- Les assertions ne sont pas là pour gérer les problèmes externes au programme (allocation mémoire qui échoue, mauvais paramètre d'entrée passé par l'utilisateur, ...).
* 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à.
. . .
- Mais peuvent être pratiques quand même pour ça...
- Typiquement désactivées dans le code de production.
* Le jeu prend fin quand on a énuméré *toutes* les possibilités de poser les
reines.
# La pile dynamique
# Problème des 3-reines
## Comment modifier le code précédent pour avoir une taille dynamique?
![Le problème des 3 reines n'a pas de solution non plus.](figs/3reines.svg)
. . .
# Problème des 4-reines
```C
// alloue une zone mémoire de size octets
void *malloc(size_t size);
// change la taille allouée à size octets (contiguïté garantie)
void *realloc(void *ptr, size_t size);
```
![Le problème des 4 reines a une solution.](figs/4reines.svg)
## Et maintenant?
# 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
stack_create(); // crée une pile avec une taille par défaut
// vérifie si la pile est pleine et réalloue si besoin
stack_push();
// vérifie si la pile est vide/trop grande
// et réalloue si besoin
stack_pop();
```
## Exercice: ouvrir un repo/issues pour l'implémentation
* Oui-oui cela est une introduction au développement collaboratif (et
hippie).
# Le tri à deux piles (1/3)
## Cas pratique
![Un exemple de tri à deux piles](figs/tri_piles.svg){width=70%}
# Le tri à deux piles (2/3)
## Exercice: formaliser l'algorithme
. . .
## Algorithme de tri nécessitant 2 piles (G, D)
Soit `tab` le tableau à trier:
```
# 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
pour i de 0 à N-1
tant que (tab[i] > que le sommet de G)
dépiler G dans D
tant que (tab[i] < que le sommet de D)
dépiler de D dans G
empiler tab[i] sur G
dépiler tout D dans G
tab est trié dans G
bool board[n][n];
```
# Le tri à deux piles (3/3)
## Quelles fonctionnalités?
## Exercice: trier le tableau `[2, 10, 5, 20, 15]`
. . .
```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));
}
#include <stdio.h>
int pgcd(int m, int n) {
if (m % n == 0) {
return n;
} else {
return pgcd(n, m % n);
}
}
// int pgcd(int m, int n) {
// if (m % n == 0) {
// return n;
// } else {
// return pgcd(n, m % n);
// }
// }
int main() {
int m = 42;
int n = 27;
printf("Le PGCD de %d et %d est %d\n", m, n, pgcd(m, n));
}