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 4423 additions and 3654 deletions
---
title: "B-Arbres"
date: "2022-05-03"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Théorie des graphes"
date: "2025-05-16"
---
# Rappel: Les B-arbres
# Les graphes
## Pourquoi utiliser un B-arbre?
\Huge
. . .
Les graphes
# Les graphes! Historique
**Un mini-peu d'histoire...**
## L. Euler et les 7 ponts de Koenigsberg:
* Existe-t-il une promenade sympa, passant **une seule fois** par les 7 ponts et revenant au point de départ?
## À quoi ressemble un B-arbre?
![Les ponts, c'est beau. Source: Wikipédia, <https://bit.ly/37h0yOG>](figs/Konigsberg_bridges.png){width=40%}
. . .
## Qu'est-ce qu'un B-arbre d'ordre $n$
* Réponse: ben non!
# Utilisation quotidienne
## Réseau social
![Source: Wikipedia, <https://bit.ly/3kG6cgo>](figs/Social_Network.svg){width=40%}
* Chaque sommet est un individu.
* Chaque trait une relation d'amitié.
* Miam, Miam, Facebook.
# Utilisation quotidienne
## Moteurs de recherche
![Source: Wikipedia, <https://bit.ly/3kG6cgo>](figs/PageRanks-Example.svg){width=40%}
* Site est un sommet.
* Liens sortants.
* Liens entrants.
* Notion d'importance d'un site: combien de liens entrants, pondérés par l'importance du site.
* Miam, Miam, Google (PageRank).
# Introduction
## Définition, plus ou moins
* Un graphe est un ensemble de sommets, reliés par des lignes ou des flèches.
![Deux exemples de graphes.](figs/ex_graphes.png)
* Chaque page d'un arbre contient au plus $2\cdot n$ *clés*;
* Chaque page (excepté la racine) contient au moins $n$ clés;
* Chaque page qui contient $m$ clés contient soit:
* $0$ descendants;
* $m+1$ descendants.
* Toutes les pages terminales apparaissent au même niveau.
* Des sommets (numérotés 1 à 6);
* Connectés ou pas par des traits ou des flèches!
# Généralités
# Rappel: Les B-arbres
## Définitions
## Quelques propriétés
* Un **graphe** $G(V, E)$ est constitué
* $V$: un ensemble de sommets;
* $E$: un ensemble d'arêtes.
* Une **arête** relie une **paire** de sommets de $V$.
* Dans chaque noeud les clés sont **triées**.
* Chaque page contient au plus $n$ noeuds: check;
* Chaque noeud avec $m$ clés a $m+1$ descendants;
* Toutes les feuilles apparaissent au même niveau.
## Remarques
# Les B-arbres
* Il y a **au plus** une arête $E$ par paire de sommets de $V$.
* La **complexité** d'un algorithme dans un graphe se mesure en terme de $|E|$ et $|V|$, le nombre d'éléments de $E$ et $V$ respectivement.
## Exemple de recherche: trouver `32`
# Généralités
![B-arbre d'ordre 2.](figs/barbres_exemple.png)
## Notations
* Une arête d'un graphe **non-orienté** est représentée par une paire **non-ordonnée** $(u,v)=(v,u)$, avec $u,v\in V$.
* Les arêtes ne sont pas orientées dans un graphe non-orienté
(elles sont bi-directionnelles, c.-à-d. peuvent être parcourues dans n'importe quel sens).
## Exemple
::: columns
:::: column
![Un graphe non-orienté.](figs/ex_graphe_non_oriente.svg)
::::
:::: column
## Que valent $V$, $|V|$, $E$, et $|E|$?
. . .
* Si `n` plus petit que la 1e clé ou plus grand que la dernière descendre.
* Sinon parcourir (par bissection ou séquentiellement) jusqu'à trouver ou descendre entre 2 éléments.
\begin{align*}
V&=\{1, 2, 3, 4\},\\
|V|&=4,\\
E&=\{(1,2),(2,3),(2,4),(4,1)\},\\
|E|&=4.
\end{align*}
::::
:::
# Généralités
## Notations
* Une arête d'un graphe **orienté** est représentée par une paire **ordonnée** $(u,v)\neq(v,u)$, avec $u,v\in V$.
* Les arêtes sont orientées dans un graphe orienté (étonnant non?).
## Exemple
# Les B-arbres
::: columns
## La recherche de la clé `C` algorithme
:::: column
0. En partant de la racine.
1. Si on est dans une feuille:
* Si la `C` est dans une page, retourner la page;
* Sinon c'est perdu.
2. Sinon:
* Tant que `C > page` passer à la page suivante
* Descendre
![Un graphe orienté.](figs/ex_graphe_oriente.svg)
# Les B-arbres
::::
## Exercice: insérer `22, 45, 50, 5, 32, 55, 60, 41` dans l'arbre d'ordre 2
:::: column
![](figs/barbres_ex1.png)
## Que valent $V$, $|V|$, $E$, et $|E|$?
. . .
![](figs/barbres_ex5.png)
\begin{align*}
V&=\{1, 2, 3, 4\},\\
|V|&=4,\\
E&=\{(1,2),(2,3),(2,4),(4,1),(4,2)\},\\
|E|&=5.
\end{align*}
::::
:::
# Généralités
## Définition
* Le somme $v$ est **adjacent** au sommet $u$, si et seulement si $(u,v)\in E$;
* Si un graphe non-orienté contient une arête $(u,v)$, $v$ est adjacent à $u$ et $u$ est adjacent à $v$.
## Exemple
::: columns
:::: column
![Sommet $a$ adjacent à $c$, $c$ adjacent à $a$.](figs/ex_adj_non_or.svg){width=60%}
::::
:::: column
![Sommet $c$ adjacent à $a$.](figs/ex_adj_or.svg){width=60%}
::::
:::
# Généralités
## Définition
* Un **graphe pondéré** ou **valué** est un graphe dont chaque arête a un
poids associé, habituellement donné par une fonction de pondération $w:E\rightarrow\mathbb{R}$.
## Exemples
![Graphe pondéré orienté (gauche) et non-orienté (droite).](figs/ex_graph_pond.pdf){width=80%}
# Généralités
## Définition
* Dans un graphe $G(V,E)$, une **chaîne** reliant un sommet $u$ à un sommet $v$ est une suite d'arêtes entre les sommets, $w_0$, $w_1$, ..., $w_k$, telles que
$$
(w_i, w_{i+1})\in E,\quad u=w_0,\quad v=w_k,\quad \mbox{pour }0\leq i< k,
$$
avec $k$ la longueur de la chaîne (le nombre d'arêtes du chemin).
## Exemples
![Illustration d'une chaîne dans un graphe.](figs/ex_graphe_chaine.pdf){width=80%}
# Généralités
## Définition
* Une **chaîne élémentaire** est une chaîne dont tous les sommets sont distincts, sauf les extrémités qui peuvent être égales.
## Exemples
![Illustration d'une chaîne élémentaire dans un graphe.](figs/ex_graphe_chaine_elem.pdf){width=80%}
# Généralités
## Définition
* Une **boucle** est une arête $(v,v)$ d'un sommet vers lui-même.
# Les B-arbres
## Exemples
## L'algorithme d'insertion
![Illustration d'une boucle dans un graphe.](figs/ex_graphe_boucle.pdf){width=40%}
0. Rechercher la feuille (la page a aucun enfant) où insérer;
1. Si la page n'est pas pleine insérer dans l'ordre croissant.
2. Si la page est pleine:
1. On décale les éléments plus grand que `N`;
2. On insère `N` dans la place "vide";
3. On trouve la valeur médiane `M` de la page (quel indice?);
4. On crée une nouvelle page de droite;
5. On copie les valeur à droite de `M` dans la nouvelle page;
6. On promeut `M` dans la page du dessus;
7. On connecte le pointeur de gauche de `M` et de droite de `M` avec l'ancienne et la nouvelle page respectivement.
# Généralités
# Les B-arbres
## Définition
## Pseudo-code structure de données (3min, matrix)?
* Un graphe non-orienté est dit **connexe**, s'il existe un chemin reliant n'importe quelle paire de sommets distincts.
## Exemples
\
::: columns
:::: column
![Graphe connexe. Source: Wikipédia, <https://bit.ly/3yiUzUv>](figs/graphe_connexe.svg){width=60%}
::::
:::: column
![Graphe non-connexe avec composantes connexes. Source: Wikipédia, <https://bit.ly/3KJB76d>](figs/composantes_connexes.svg){width=40%}
::::
:::
# Généralités
## Définition
* Un graphe orienté est dit **fortement connexe**, s'il existe un chemin reliant n'importe quelle paire de sommets distincts.
## Exemples
\
::: columns
:::: column
![Graphe fortement connexe.](figs/ex_graph_fort_connexe.pdf){width=60%}
::::
:::: column
![Composantes fortement connexes. Source, Wikipédia: <https://bit.ly/3w5PL2l>](figs/composantes_fortement_connexes.svg){width=100%}
::::
:::
# Généralités
## Définition
* Un **cycle** dans un graphe *non-orienté* est une chaîne de longueur $\geq 3$ telle que le 1er sommet de la chaîne est le même que le dernier, et dont les arêtes sont distinctes.
* Pour un graphe *orienté*, on parle de **circuit**.
* Un graphe sans cycles est dit **acyclique**.
## Exemples
![Illustration de cycles.](figs/ex_graphe_cycle.pdf){width=100%}
# Question de la mort
* Qu'est-ce qu'un graphe connexe acyclique?
. . .
```C
struct page
entier ordre, nb
element tab[2*ordre + 2]
```
* Un arbre!
```C
struct element
int clé
page pg
# Représentations
* La complexité des algorithmes sur les graphes s'expriment en fonction du nombre de sommets $V$, et du nombre d'arêtes $E$:
* Si $|E|\sim |V|^2$, on dit que le graphe est **dense**.
* Si $|E|\sim |V|$, on dit que le graphe est **peu dense**.
* Selon qu'on considère des graphes denses ou peu denses, différentes structures de données peuvent être envisagées.
## Question
* Comment peut-on représenter un graphe informatiquement? Des idées (réflexion de quelques minutes)?
. . .
* Matrice/liste d'adjacence.
# Matrice d'adjacence
* Soit le graphe $G(V,E)$, avec $V=\{1, 2, 3, ..., n\}$;
* On peut représenter un graphe par une **matrice d'adjacence**, $A$, de dimension $n\times n$ définie par
$$
A_{ij}=\left\{ \begin{array}{ll}
1 & \mbox{si } i,j\in E,\\
0 & \mbox{sinon}.
\end{array} \right.
$$
::: columns
:::: column
## Exemple
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---4;
2---5;
4---5;
5---3;
```
# Les B-arbres
::::
:::: column
\footnotesize
## Les fonctions utilitaires (5min matrix)
## Quelle matrice d'adjacence?
. . .
```C
booléen est_feuille(page) // la page est elle une feuille?
entier position(page, valeur) // à quelle indice on insère?
booléen est_dans_page(page, valeur) // la valeur est dans la page
```
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|===
1 || 0 | 1 | 0 | 1 | 0
---||---|---|---|---|---
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 1
---||---|---|---|---|---
4 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
5 || 0 | 1 | 1 | 1 | 0
```
. . .
::::
```C
booléen est_feuille(page)
retourne (page.tab[0].pg == vide)
:::
entier position(page, valeur)
i = 0
tant que i < page.nb && val >= page.tab[i+1].clef
i += 1
retourne i
# Matrice d'adjacence
## Remarques
* Zéro sur la diagonale.
* La matrice d'un graphe non-orienté est symétrique
booléen est_dans_page(page, valeur)
i = position(page, valeur)
retourne (page.nb > 0 && page.tab[i].clef == valeur)
$$
A_{ij}=A_{ji}, \forall i,j\in[1,n]
$$.
::: columns
:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---4;
2---5;
4---5;
5---3;
```
# Les B-arbres
::::
:::: column
\footnotesize
## Les fonctions utilitaires (5min matrix)
```
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|===
1 || 0 | 1 | 0 | 1 | 0
---||---|---|---|---|---
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 1
---||---|---|---|---|---
4 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
5 || 0 | 1 | 1 | 1 | 0
```
```C
page nouvelle_page(ordre) // creer une page
rien liberer_memoire(page) // liberer tout un arbre!
::::
:::
# Matrice d'adjacence
* Pour un graphe orienté (digraphe)
::: columns
:::: column
## Exemple
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
2-->1;
1-->4;
2-->5;
5-->2;
4-->5;
5-->3;
```
::::
:::: column
\footnotesize
## Quelle matrice d'adjacence?
. . .
```C
page nouvelle_page(ordre)
page = allouer(page)
page.ordre = ordre
page.nb = 0
page.tab = allouer(2*ordre+2)
retourner page
rien liberer_memoire(page)
si est_feuille(page)
liberer(page.tab)
liberer(page)
sinon
pour fille dans page.tab
liberer_memoire(fille)
liberer(page.tab)
liberer(page)
```
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|===
1 || 0 | 0 | 0 | 1 | 0
---||---|---|---|---|---
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 0
---||---|---|---|---|---
4 || 0 | 0 | 0 | 0 | 1
---||---|---|---|---|---
5 || 0 | 1 | 1 | 0 | 0
```
# Les B-arbres
::::
## Les fonctions (5min matrix)
:::
```C
page recherche(page, valeur) // retourner la page contenant
// la valeur ou vide
```
* La matrice d'adjacence n'est plus forcément symétrique
$$
A_{ij}\neq A_{ji}.
$$
# Stockage
* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe orienté?
. . .
```C
page recherche(page, valeur)
si est_dans_page(page, valeur)
retourne page
sinon si est_feuille(page)
retourne vide
sinon
recherche(page.tab[position(page, valeur)], valeur)
```
* $\mathcal{O}(|V|^2)$
* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe non-orienté?
. . .
# Les B-arbres
* $\mathcal{O}\left((|V|-1)|V|/2\right)$.
## Les fonctions
# Considérations d'efficacité
```C
page inserer(page, valeur) // inserer une valeur
```
* Dans quel type de graphes la matrice d'adjacence est-elle utile?
. . .
```C
page inserer(page, valeur)
element = nouvel_element(valeur)
// on change element pour savoir s'il faut le remonter
inserer_element(page, element)
si element != vide && element.page != vide
// si on atteint le sommet
page = ajouter_niveau(page, element)
retourne page
```
* Dans les graphes denses.
* Pourquoi?
# Les B-arbres
. . .
## Les fonctions
* Dans les graphes peu denses, la matrice d'adjacence est essentiellement composée de `0`.
```C
// inserer un element et voir s'il remonte
rien inserer_element(page, element)
```
## Remarque
* Dans la majorité des cas, les grands graphes sont peu denses.
* Comment représenter un graphe autrement?
# La liste d'adjacence (non-orienté)
* Pour chaque sommet $v\in V$, stocker les sommets adjacents à $v$.
* Quelle structure de données pour la liste d'adjacence?
. . .
```C
rien inserer_element(page, element)
si est_feuille(page)
placer(page, element)
sinon
sous_page = page.tab[position(page, element)].page
inserer_element(sous_page, element)
si element != vide && element.page != vide
placer(page, element)
```
* Tableau de liste chaînée, vecteur (tableau dynamique), etc.
# Les B-arbres
## Les fonctions (5min matrix)
::: columns
```C
rien placer(page, element) // inserer un élément
```
:::: column
## Exemple
![Un graphe non-orienté.](figs/ex_graph_adj.pdf){width=80%}
::::
:::: column
## Quelle liste d'adjacence?
. . .
```C
rien placer(page, element)
i = position(page, element.clef)
pour i de 2*page.ordre à i+1
page.tab[i+1] = page.tab[i]
page.tab[i+1] = element
page.nb += 1
si page.nb > 2*page.ordre
scinder(page, element)
sinon
element = vide
```
![La liste d'adjacence.](figs/ex_graph_list_adj.pdf)
# Les B-arbres
## Les fonctions (5min matrix)
::::
```C
rien scinder(page, element) // casser une page et remonter
:::
# La liste d'adjacence (orienté)
::: columns
:::: column
## Quelle liste d'adjacence pour...
* Matrix (2min)
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
0-->1;
0-->2;
1-->2;
3-->0;
3-->1;
3-->2;
```
. . .
::::
:::: column
```C
rien scinder(page, element)
new_page = new_page(page.ordre)
new_page.nb = page.ordre
pour i de 0 à ordre inclu
new_page.tab[i] = page.tab[i+ordre+1]
element.clef = page.tab[ordre+1].clé
element.page = new_page
```
# Les B-arbres
## Les fonctions (5min matrix)
```C
page ajouter_niveau(page, element) // si on remonte à la racine...
// on doit créer une nouvelle racine
```
::::
:::
# Complexité
## Stockage
* Quelle espace est nécessaire pour stocker une liste d'adjacence (en fonction de $|E|$ et $|V|$)?
. . .
```C
page ajouter_niveau(page, element)
tmp = nouvelle_page(page.ordre)
tmp.tab[0].page = page
tmp.tab[1].clef = element.clef
tmp.tab[1].page = element.page
retourne tmp
```
$$
\mathcal{O}(|V|+|E|)
$$
# Les B-arbres: suppression
* Pour les graphes *non-orientés*: $\mathcal{O}(|V|+2|E|)$.
* Pour les graphes *orientés*: $\mathcal{O}(|V|+|E|)$.
## Cas simplissime
## Définition
![Suppression de 25.](figs/barbres_ordre2_supp1.svg){width=80%}
* Le **degré** d'un sommet $v$, est le nombre d'arêtes incidentes du sommet (pour les graphes orientés on a un degré entrant ou sortant).
* Comment retrouve-t-on le degré de chaque sommet avec la liste d'adjacence?
. . .
![25 supprimé, on décale juste 27.](figs/barbres_ordre2_supp2.svg){width=80%}
* C'est la longueur de la liste chaînée si le graphe est non-orienté.
# Les B-arbres: suppression
## Cas simple
# Parcours
* Beaucoup d'applications nécessitent de parcourir des graphes:
* trouver un chemin d'un sommet à un autre;
* trouver si le graphe est connexe.
* Il existe *deux* parcours principaux:
* en largeur (Breadth-First Search);
* en profondeur (Depth-First Search).
* Ces parcours créent *un arbre* au fil de l'exploration (si le graphe est non-connexe, cela crée une *forêt*, c.-à-d. un ensemble d'arbres).
![Suppression de 27.](figs/barbres_ordre2_supp2.svg){width=60%}
# Illustration: parcours en largeur
. . .
![Le parcours en largeur.](figs/parcours_larg.pdf){width=80%}
* On retire 27, mais....
* Chaque page doit avoir au moins 2 éléments.
* On doit déplacer des éléments dans une autre feuille! Mais comment?
# Exemple
. . .
## Étape par étape (blanc non-visité)
![La médiane de la racine descend, fusion de 20 à gauche, et suppression à droite.](figs/barbres_ordre2_supp3.svg){width=60%}
![Initialisation.](figs/parcours_larg_0.pdf){width=50%}
# Les B-arbres: suppression
## Étape par étape (gris visité)
## Cas moins simple
![On commence en `x`.](figs/parcours_larg_1.pdf){width=50%}
![Suppression de 5.](figs/barbres_ordre2_supp4.svg){width=60%}
# Exemple
. . .
## Étape par étape
* Un élément à droite, comment on fait?
* Remonter `7`, serait ok si racine, mais... c'est pas forcément.
* On redistribue les feuilles.
![On commence en `x`.](figs/parcours_larg_1.pdf){width=50%}
. . .
## Étape par étape (vert à visiter)
![Descente de `3`, remontée médiane des feuilles `2`, .](figs/barbres_ordre2_supp5.svg){width=60%}
![Visiter `w`, `t`, `y`.](figs/parcours_larg_2.pdf){width=50%}
# Les B-arbres: suppression
# Exemple
## Cas ultra moins simple
## Étape par étape
![Suppression de 3.](figs/barbres_ordre2_supp6.svg){width=60%}
![Visiter `w`, `t`, `y`.](figs/parcours_larg_2.pdf){width=50%}
. . .
## Étape par étape
![`w`, `t`, `y` visités. `u`, `s` à visiter.](figs/parcours_larg_3.pdf){width=50%}
# Exemple
## Étape par étape
![`w`, `t`, `y` visités. `u`, `s` à visiter.](figs/parcours_larg_3.pdf){width=50%}
## Étape par étape
![`u`, `s`, visités. `r` à visiter.](figs/parcours_larg_4.pdf){width=50%}
# Exemple
## Étape par étape
![`u`, `s`, visités. `r` à visiter.](figs/parcours_larg_4.pdf){width=50%}
## Étape par étape
![`r` visité. `v` à visiter.](figs/parcours_larg_5.pdf){width=50%}
* `7` seul:
* Fusionner les feuilles et redistribuer, comment?
# Exemple
## Étape par étape
![`r` visité. `v` à visiter.](figs/parcours_larg_5.pdf){width=50%}
## Étape par étape
![The end. Plus rien à visiter!](figs/parcours_larg_6.pdf){width=50%}
# En faisant ce parcours...
::: columns
:::: column
## Du parcours de l'arbre
![](figs/parcours_larg_6.pdf){width=100%}
::::
:::: column
## Quel arbre est créé par le parcours (2min)?
. . .
![Descendre `-1`, déplacer `7` à gauche, et décaler les éléments de droite au milieu.](figs/barbres_ordre2_supp7.svg){width=60%}
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
0[x]-->1[w];
0-->2[t];
0-->3[y];
2-->9[u];
1-->4[s];
4-->5[r];
5-->6[v];
```
::::
:::
## Remarques
# Les B-arbres: suppression
* Le parcours dépend du point de départ dans le graphe.
* L'arbre sera différent en fonction du noeud de départ, et de l'ordre de parcours des voisins d'un noeud.
## Cas ultra moins simple
# Le parcours en largeur
![On a pas fini...](figs/barbres_ordre2_supp7.svg){width=60%}
## L'algorithme, idée générale (3min, matrix)?
. . .
* `8` est seul, c'est plus un B-arbre :
* Fusionner le niveau 2 et redistribuer, comment?
```C
v = un sommet du graphe
i = 1
pour sommet dans graphe et sommet non-visité
visiter(v, sommet, i) // marquer sommet à distance i visité
i += 1
```
## Remarque
* `i` est la distance de plus court chemin entre `v` et les sommets en cours de visite.
# Le parcours en largeur
## L'algorithme, pseudo-code (3min, matrix)?
* Comment garder la trace de la distance?
. . .
![Fusionner `8`, `17`, `22` et descendre `12`.](figs/barbres_ordre2_supp8.svg){width=40%}
* Utilisation d'une **file d'attente**
. . .
* La profondeur a diminué de 1.
```C
initialiser(graphe) // tous les sommets sont non-visités
visiter(sommet, file) // on choisit un sommet de départ
tant que !est_vide(file)
défiler(file, (v,u))
si u != visité
ajouter (v,u) à arbre T
visiter(u, file)
```
## Que fait visiter?
```
rien visiter(x, file)
marquer x comme visité
pour chaque arête (x,w)
si w != visité
enfiler(file, (x,w))
```
# Les B-arbres: suppression
# Exercice (5min)
## Algorithme pour les feuilles!
## Appliquer l'algorithme sur le graphe
* Si la clé est supprimée d'une feuille:
* Si on a toujours `n` (ordre de l'arbre) clés dans la feuille on décale simplement les clés.
* Sinon on combine (récursivement) avec le noeud voisin et on descend la clé médiane.
![](figs/parcours_larg_0.pdf){width=50%}
# Les B-arbres: suppression
* En partant de `v`, `s`, ou `u` (par colonne de classe).
* Bien mettre à chaque étape l'état de la file.
## Cas non-feuille!
# Complexité du parcours en largeur
![Suppression de 8.](figs/barbres_ordre2_supp9.svg){width=60%}
## Étape 1
. . .
* Extraire un sommet de la file.
## Étape 2
* On sait comment effacer une valeur d'une feuille, donc?
* Traîter tous les sommets adjacents.
## Quelle est la complexité?
. . .
![Échanger le `8` avec le plus grand du sous-arbre de gauche.](figs/barbres_ordre2_supp10.svg){width=60%}
* Étape 1: $\mathcal{O}(|V|)$
* Étape 2: $\mathcal{O}(2|E|)$
* Total: $\mathcal{O}(|V| + |2|E|)$
# Exercice
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en largeur au graphe
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---3;
1---4;
2---3;
2---6;
3---6;
3---4;
3---5;
4---5;
```
# Illustration: parcours en profondeur
![Le parcours en profondeur. À quel parcours d'arbre cela ressemble-t-il?](figs/parcours_prof.pdf){width=80%}
# Parcours en profondeur
## Idée générale
* Initialiser les sommets comme non-lus
* Visiter un sommet
* Pour chaque sommet visité, on visite un sommet adjacent s'il n'est pas encore visité, et ce récursivement.
* Ensuite?
## Remarque
# Les B-arbres: suppression
* La récursivité est équivalent à l'utilisation d'une **pile**.
## Cas non-feuille!
# Parcours en profondeur
![Suppression de 8.](figs/barbres_ordre2_supp10.svg){width=60%}
## Pseudo-code (5min)
. . .
* On sait comment effacer une valeur d'une feuille!
```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?
. . .
![Yaka enlever le 8 de la feuille comme avant!](figs/barbres_ordre2_supp11.svg){width=60%}
```C
rien visiter(x, pile)
marquer x comme visité
pour chaque arête (x,w)
si w != visité
empiler(pile, (x,w))
```
# Les B-arbres: suppression
## Algorithme pour les non-feuilles!
# Exercice
* Si la clé est supprimée d'une page qui n'est pas une feuille:
* On échange la valeur avec la valeur de droite de la page de gauche
* On supprime comme pour une feuille!
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en profondeur au graphe
## Et maintenant des exos par millions!
```{.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
<!-- # Les B-arbres -->
* Un graphe vu comme espace d'états (sommet: état, arête: action)
* Labyrinthe
* Arbre des coups d'un jeu
<!-- ## Structure de données en C (3min, matrix) -->
. . .
<!-- . . . -->
* BFS (Breadth-First Search) ou DFS (Depth-First Search) 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!
<!-- ```C -->
<!-- typedef struct _page { -->
<!-- int order, nb; -->
<!-- struct _element *tab; -->
<!-- } page; -->
<!-- ``` -->
. . .
<!-- ```C -->
<!-- typedef struct element { -->
<!-- int key; -->
<!-- struct _page *pg; -->
<!-- } element; -->
<!-- ``` -->
* Quand on a un temps limité
* BFS explore beaucoup de coups dans un futur proche;
* DFS explore peu de coups dans un futur lointain.
---
title: "Graphes - Généralités"
date: "2022-05-03"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Théorie des graphes et plus courts chemins"
date: "2025-05-26"
---
# Historique
# Les graphes
**Un mini-peu d'histoire...**
\Huge
## L. Euler et les 7 ponts de Koenigsberg:
Les graphes
* Existe-t-il une promenade sympa, passant **une seule fois** par les 7 ponts et revenant au point de départ?
# Exercice
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en largeur au graphe
![Les ponts c'est beau. Source: Wikipédia, <https://bit.ly/37h0yOG>](figs/Konigsberg_bridges.png){width=50%}
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---3;
1---4;
2---3;
2---6;
3---6;
3---4;
3---5;
4---5;
```
# Illustration: parcours en profondeur
![Le parcours en profondeur. À quel parcours d'arbre cela ressemble-t-il?](figs/parcours_prof.pdf){width=80%}
# Parcours en profondeur
## Idée générale
* Initialiser les sommets comme non-lus
* Visiter un sommet
* Pour chaque sommet visité, on visite un sommet adjacent s'il n'est pas encore visité, et ce récursivement.
## Remarque
* La récursivité est équivalente à l'utilisation d'une **pile**.
# Parcours en profondeur
## Pseudo-code (5min)
. . .
* Réponse: ben non!
```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?
. . .
# Utilisation quotidienne
```C
rien visiter(x, pile)
marquer x comme visité
pour chaque arête (x,w)
si w != visité
empiler(pile, (x,w))
```
## Réseau social
# Exercice
![Source, Wikipedia: <https://bit.ly/3kG6cgo>](figs/Social_Network.svg){width=40%}
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en profondeur au graphe
* Chaque sommet est un individu.
* Chaque trait une relation d'amitié.
* Miam, Miam, Facebook.
```{.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;
```
# Utilisation quotidienne
# Interprétation des parcours
## Moteurs de recherche
* Un graphe vu comme espace d'états (sommet: état, arête: action);
* Labyrinthe;
* Arbre des coups d'un jeu.
![Source, Wikipedia: <https://bit.ly/3kG6cgo>](figs/PageRanks-Example.svg){width=40%}
. . .
* Sommet est un site.
* Liens sortants;
* Liens entrants;
* Notion d'importance d'un site: combien de liens entrants, pondérés par l'importance du site.
* Miam, Miam, Google (PageRank).
* 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!
# Introduction
. . .
## Définition, plus ou moins
* Quand on a un temps limité
* BFS explore beaucoup de coups dans un futur proche;
* DFS explore peu de coups dans un futur lointain.
* Un graphe est un ensemble de sommets, reliés par des lignes ou des flèches.
# Algorithmes de plus courts chemins
![Deux exemples de graphes.](figs/ex_graphes.png)
\Huge Plus courts chemins
* Des sommets (numérotés 1 à 6);
* Connectés ou pas par des traits ou des flèches!
# Contexte: les réseaux (informatique, transport, etc.)
# Généralités
* 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.
## Définitions
## Problème à résoudre
* Un **graphe** $G(V, E)$ est constitué
* $V$: un ensemble de sommets;
* $E$: un ensemble d'arêtes.
* Une **arête** relie une **paire** de sommets de $V$.
* Quel est le plus court chemin entre `s` et `t`?
## Remarques
# Exemples d'application de plus courts chemins
* Il y a **au plus** une arête $E$ par paire de sommets de $V$.
* La **complexité** d'un algorithme dans un graphe se mesure en terme de $|E|$ et $|V|$, le nombre d'éléments de $E$ et $V$ respectivement.
## Devenir riches!
# Généralités
* On part d'un tableau de taux de change entre devises.
* Quelle est la meilleure façon de convertir l'or en dollar?
## Notations
![Taux de change.](figs/taux_change.pdf){width=80%}
* Une arête d'un graphe **non-orienté** est représentée par une paire **non-ordonnée** $(u,v)=(v,u)$, avec $u,v\in V$.
* Les arêtes ne sont pas orientées dans un graphe non-orienté (elles sont bi-directionnelles, peuvent être parcourues dans n'importe quel ordre).
. . .
## Exemple
* 1kg d'or => 327.25 dollars
* 1kg d'or => 208.1 livres => 327 dollars
* 1kg d'or => 455.2 francs => 304.39 euros => 327.28 dollars
# Exemples d'application de plus courts chemins
::: columns
## Formulation sous forme d'un graphe: Comment (3min)?
:::: column
![Taux de change.](figs/taux_change.pdf){width=80%}
![Un graphe non-orienté.](figs/ex_graphe_non_oriente.svg)
# Exemples d'application de plus courts chemins
::::
## Formulation sous forme d'un graphe: Comment (3min)?
:::: column
![Graphes des taux de change.](figs/taux_change_graphe.pdf){width=60%}
## Que valent $V$, $|V|$, $E$, et $|E|$?
* 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.
. . .
\begin{align*}
V&=\{1, 2, 3, 4\},\\
|V|&=4,\\
E&=\{(1,2),(2,3),(2,4),(4,1)\},\\
|E|&=4.
\end{align*}
## Problème
::::
* On aimerait plutôt avoir une somme...
:::
# Généralités
# Exemples d'application de plus courts chemins
## Notations
## Conversion du problème en plus courts chemins
* Une arête d'un graphe **orienté** est représentée par une paire **ordonnée** $(u,v)\neq(v,u)$, avec $u,v\in V$.
* Les arêtes sont orientées dans un graphe orienté (étonnant non?).
* 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`.
## Exemple
![Graphe des taux de change avec logs.](figs/taux_change_graphe_log.pdf){width=60%}
* Cette conversion se base sur l'idée que
::: columns
$$
\log(u\cdot v)=\log(u)+\log(v).
$$
:::: column
# Applications de plus courts chemins
![Un graphe non-orienté.](figs/ex_graphe_oriente.svg)
## Quelles applications voyez-vous?
. . .
::::
* Déplacement d'un robot;
* Planificaiton de trajet / trafic urbain;
* Routage de télécommunications;
* Réseau électrique optimal;
* ...
:::: column
# Algorithmes de plus courts chemins
\Huge
Algorithmes de plus courts chemins
# Rappel de la problématique
* Graphe orienté;
* Source: sommet `s`;
* Destination: sommet `t`;
* Les arêtes ont des poids (coût d'utilisation, distance, etc.);
* Le coût d'un chemin est la somme des poids des arêtes d'un chemin.
## Problème à résoudre
* Quel est le plus court chemin entre `s` et `t`.
# Plus courts chemins à source unique
## Que valent $V$, $|V|$, $E$, et $|E|$?
* Soit un graphe, $G=(V, E)$, une fonction de pondération $w:E\rightarrow\mathbb{R}$, et un sommet $s\in V$
* Trouver pour tout sommet $v\in V$, le chemin de poids minimal reliant $s$ à $v$.
* Algorithmes standards:
* Dijkstra (arêtes de poids positif seulement);
* Bellman-Ford (arêtes de poids positifs ou négatifs, mais sans cycles négatifs).
* Comment résoudre le problèmes si tous les poids sont les mêmes?
. . .
\begin{align*}
V&=\{1, 2, 3, 4\},\\
|V|&=4,\\
E&=\{(1,2),(2,3),(2,4),(4,1),(4,2)\},\\
|E|&=5.
\end{align*}
* Un parcours en largeur!
::::
# Algorithme de Dijkstra
:::
## Comment chercher pour un plus court chemin?
# Généralités
. . .
## Définition
```
si distance(u,v) > distance(u,w) + distance(w,v)
on passe par w plutôt qu'aller directement
```
* Le somme $v$ est **adjacent** au sommet $u$, si et seulement si $(u,v)\in E$;
* Si un graphe non-orienté contient une arête $(u,v)$, $v$ est adjacent à $u$ et $u$ et adjacent à $v$.
# Algorithme de Dijkstra (1 à 5)
## Exemple
* $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
![Sommet $a$ adjacent à $c$, $c$ adjacent à $a$.](figs/ex_adj_non_or.svg){width=80%}
![Initialisation.](figs/dijkstra_0.png)
::::
:::: column
![Sommet $a$ adjacent à $c$.](figs/ex_adj_or.svg){width=80%}
. . .
![1 visité, `D[2]=1`, `D[4]=3`.](figs/dijkstra_1.png)
::::
:::
# Généralités
# Algorithme de Dijkstra (1 à 5)
## Définition
::: columns
* Un **graphe pondéré** ou **valué** est un graphe dont chaque arête a un
poids associé, habituellement donné par une fonction de pondération $w:E\rightarrow\mathbb{R}$.
:::: column
## Exemples
![Plus court est 2.](figs/dijkstra_1.png)
![Graphe pondéré orienté (gauche) et non-orienté (droite).](figs/ex_graph_pond.pdf){width=80%}
::::
# Généralités
:::: column
## Définition
. . .
* Dans un graphe $G(V,E)$, une **chaîne** reliant un sommet $u$ à un sommet $v$ est une suite d'arêtes entre les sommets, $w_0$, $w_1$, ..., $w_k$, telles que
$$
(w_i, w_{i+1})\in E,\quad u=w_0,\quad v=w_k,\quad \mbox{pour }0\leq i< k,
$$
avec $k$ la longueur de la chaîne (le nombre d'arêtes du chemin).
![2 visité, `D[3]=2`, `D[7]=3`.](figs/dijkstra_2.png)
## Exemples
::::
![Illustration d'une chaîne, ou pas chaîne dans un graphe.](figs/ex_graphe_chaine.pdf){width=80%}
:::
# Généralités
# Algorithme de Dijkstra (1 à 5)
## Définition
::: columns
:::: column
* Une **chaîne élémentaire** est une chaîne dont tous les sommets sont distincts, sauf les extrémités qui peuvent être égales
![Plus court est 3.](figs/dijkstra_2.png)
## Exemples
![Illustration d'une chaîne élémentaire.](figs/ex_graphe_chaine_elem.pdf){width=80%}
::::
# Généralités
:::: column
## Définition
. . .
* Une **boucle** est une arête $(v,v)$ d'un sommet vers lui-même.
![3 visité, `D[7]=3` inchangé, `D[6]=6`.](figs/dijkstra_3.png)
## Exemples
::::
![Illustration d'une boucle.](figs/ex_graphe_boucle.pdf){width=40%}
:::
# Généralités
# Algorithme de Dijkstra (1 à 5)
## Définition
* Un graphe non-orienté est dit **connexe**, s'il existe un chemin reliant n'importe quelle paire de sommets distincts.
::: columns
## Exemples
:::: 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
![Graphe connexe. Source, Wikipédia: <https://bit.ly/3yiUzUv>](figs/graphe_connexe.svg){width=80%}
![Plus court est `7`.](figs/dijkstra_4.png)
::::
:::: column
![Graphe non-connexe avec composantes connexes. Source, Wikipédia: <https://bit.ly/3KJB76d>](figs/composantes_connexes.svg){width=60%}
. . .
![7 visité, `D[5]=7`, `D[6]=6` inchangé.](figs/dijkstra_5.png)
::::
:::
# Généralités
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Plus court est 6.](figs/dijkstra_5.png)
::::
:::: column
## Définition
. . .
* Un graphe orienté est dit **fortement connexe**, s'il existe un chemin reliant n'importe quelle paire de sommets distincts.
![`6` visité, `D[5]=7` inchangé.](figs/dijkstra_6.png)
## Exemples
::::
\
:::
# Algorithme de Dijkstra (1 à 5)
::: columns
:::: column
![Graphe fortement connexe.](figs/ex_graph_fort_connexe.pdf){width=60%}
![Plus court est 5 et c'est la cible.](figs/dijkstra_6.png)
::::
:::: column
![Composantes fortement connexes. Source, Wikipédia: <https://bit.ly/3w5PL2l>](figs/composantes_fortement_connexes.svg){width=100%}
. . .
![The end, tous les sommets ont été visités.](figs/dijkstra_7.png)
::::
:::
# Généralités
# Algorithme de Dijkstra
## Définition
* Un **cycle** dans un graphe *non-orienté* est une chaîne de longueur $\leq 3$ telle que le 1er sommet de la chaîne est le même que le dernier, et dont les arêtes sont distinctes.
* Pour un graphe *orienté* on parle de **circuit**.
* Un graphe sans cycles est dit **acyclique**.
## Idée générale
## Exemples
* On assigne à chaque noeud une distance $0$ pour $s$, $\infty$ pour les autres.
* Tous les noeuds sont marqués non-visités.
* Depuis le noeud courant, on suit chaque arête du noeud vers un sommet non visité et on calcule le poids du chemin à chaque voisin et on met à jour sa distance si elle est plus petite que la distance du noeud.
* Quand tous les voisins du noeud courant ont été visités, le noeud est mis à visité (il ne sera plus jamais visité).
* Continuer avec le noeud à la distance la plus faible.
* L'algorithme est terminé losrque le noeud de destination est marqué comme visité, ou qu'on n'a plus de noeuds qu'on peut visiter et que leur distance est infinie.
![Illustration de cycles, ou pas.](figs/ex_graphe_cycle.pdf){width=100%}
# Algorithme de Dijkstra
# Question de la mort
## Pseudo-code (5min, matrix)
* Qu'est-ce qu'un graphe connexe acyclique?
\footnotesize
. . .
* Un arbre!
# Représentations
```C
tab dijkstra(graph, s, t)
pour chaque v dans graphe
distance[v] = infini
distance[s] = 0
q = ajouter(q, s) // q est une liste
tant que non_vide(q)
// sélection de u t.q. la distance dans q est min
u = min(q, distance)
si u == t // on a atteint la cible
retourne distance
q = remove(q, u)
// voisin de u encore dans q
pour chaque v dans voisinage(u, q)
// on met à jour la distance du voisin en passant par u
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
retourne distance
```
* La complexité des algorithmes sur les graphes s'expriment en fonction du nombre de sommets $V$, et du nombre d'arêtes $E$:
* Si $|E|\sim |V|^2$, on dit que le graphe est **dense**.
* Si $|E|\sim |V|$, on dit que le graphe est **peu dense**.
* Selon qu'on considère des graphes denses ou peu denses, différentes structure de données peuvent être envisagées.
# Algorithme de Dijkstra
## Question
* Cet algorithme, nous donne le plus court chemin mais...
* ne nous donne pas le chemin!
* Comment peut-on représenter un graphe informatiquement? Des idées (réflexion de quelques minutes)?
## Comment modifier l'algorithme pour avoir le chemin?
. . .
* Matrice/liste d'adjacence.
* Pour chaque nouveau noeud à visiter, il suffit d'enregistrer d'où on est venu!
* On a besoin d'un tableau `precedent`.
# Matrice d'adjacence
## Modifier le pseudo-code ci-dessus pour ce faire (3min matrix)
* Soit le graphe $G(V,E)$, avec $V=\{1, 2, 3, ..., n\}$;
* On peut représenter un graphe par une **matrice d'adjacence**, $A$, de dimension $n\times n$ définie par
$$
A_{ij}=\left\{ \begin{array}{ll}
1 & \mbox{si } i,j\in E,\\
0 & \mbox{sinon}.
\end{array} \right.
$$
# Algorithme de Dijkstra
\footnotesize
::: columns
```C
tab, tab dijkstra(graph, s, t)
pour chaque v dans graphe
distance[v] = infini
precedent[v] = indéfini
distance[s] = 0
q = ajouter(q, s)
tant que non_vide(q)
// sélection de u t.q. la distance dans q est min
u = min(q, distance)
si u == t
retourne distance, precedent
q = remove(q, u)
// voisin de u encore dans q
pour chaque v dans voisinage(u, q)
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
precedent[v] = u
retourne distance, precedent
```
:::: column
# Algorithme de Dijkstra
## Exemple
## Comment reconstruire un chemin ?
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---4;
2---5;
4---5;
5---3;
. . .
```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é
:::: column
## On peut améliorer l'algorithme
\footnotesize
* Avec une file de priorité!
## Une file de priorité est
## Quelle matrice d'adjacence?
* 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?
. . .
```
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|===
1 || 0 | 1 | 0 | 1 | 0
---||---|---|---|---|---
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 1
---||---|---|---|---|---
4 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
5 || 0 | 1 | 1 | 1 | 0
* 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)
:::
. . .
# Matrice d'adjacence
```C
struct element
data
priorite
element suivant
```
## Remarques
# Les files de priorité
* Zéro sur la diagonale.
* La matrice d'un graphe non-orienté est symétrique
## Pseudo-implémentation: enfiler (2min)
$$
A_{ij}=A_{ji}, \forall i,j\in[1,n]
$$.
. . .
::: columns
```C
element enfiler(element, data, priorite)
n_element = creer_element(data, priorite)
si est_vide(element)
retourne n_element
si priorite(n_element) > priorite(element)
n_element.suivant = element
retourne n_element
sinon
tmp = element
prec = element
tant que !est_vide(tmp)
&& priorite(n_element) < priorite(tmp)
prec = tmp
tmp = tmp.suivant
prec.suivant = n_element
n_element.suivant = tmp
retourne element
```
:::: column
# Les files de priorité
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
1---2;
1---4;
2---5;
4---5;
5---3;
## 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
:::: column
```C
distance, precedent dijkstra(graphe, s, t):
fp = file_p_vide()
distance[s] = 0
pour v dans sommets(graphe)
si v != s
distance[v] = infini
precedent[v] = indéfini
fp = enfiler(fp, v, distance[v])
tant que !est_vide(fp)
u, fp = defiler(fp)
si u == t
retourne distance, precedent
pour v dans voisinage de u
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
precedent[v] = u
fp = changer_priorite(fp, v, n_distance)
retourne distance, precedent
```
# Algorithme de Dijkstra avec file
\footnotesize
```
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|===
1 || 0 | 1 | 0 | 1 | 0
---||---|---|---|---|---
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 1
---||---|---|---|---|---
4 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
5 || 0 | 1 | 1 | 1 | 0
```C
distance dijkstra(graphe, s, t)
--------------------O(V*V)--------------------------------
distance[s] = 0
fp = file_p_vide()
pour v dans sommets(graphe) // O(|V|)
si v != s
distance[v] = infini
fp = enfiler(fp, s, distance[s]) // O(|V|)
------------------O(V * V)-------------------------------
tant que !est_vide(fp)
u, fp = defiler(fp) // O(1)
---------------------------------------------------------
pour v dans voisinage de u // O(|E|)
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
fp = changer_priorite(fp, v, n_distance) // O(|V|)
---------------------------------------------------------
retourne distance
```
::::
* Total: $\mathcal{O}(|V|^2+|E|\cdot |V|)$:
* Graphe dense: $\mathcal{O}(|V|^3)$
* Graphe peu dense: $\mathcal{O}(|V|^2)$
:::
# Algorithme de Dijkstra avec file
# Matrice d'adjacence
## On peut faire mieux
* Pour un graphe orienté (digraphe)
* 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|)$.
::: columns
# Algorithme de Dijkstra (exercice, 5min)
:::: column
![L'exercice.](figs/dijkstra_exo.png){width=60%}
## Exemple
* Donner la liste de priorité, puis...
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
2-->1;
1-->4;
2-->5;
5-->2;
4-->5;
5-->3;
```
## A chaque étape donner:
::::
* Le tableau des distances à `a`;
* Le tableau des prédécesseurs;
* L'état de la file de priorité.
:::: column
# Algorithme de Dijkstra (corrigé)
\footnotesize
![Le corrigé partie 1.](figs/dijkstra_ex_0.png)
## Quelle matrice d'adjacence?
# Algorithme de Dijkstra (corrigé)
. . .
![Le corrigé partie 2.](figs/dijkstra_ex_1.png)
```
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|===
1 || 0 | 0 | 0 | 1 | 0
---||---|---|---|---|---
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 0
---||---|---|---|---|---
4 || 0 | 0 | 0 | 0 | 1
---||---|---|---|---|---
5 || 0 | 1 | 1 | 0 | 0
```
# Algorithme de Dijkstra (corrigé)
::::
![Le corrigé partie 3.](figs/dijkstra_ex_2.png)
:::
# Algorithme de Dijkstra (corrigé)
* La matrice d'adjacence n'est plus forcément symétrique
$$
A_{ij}\neq A_{ji}.
$$
![Le corrigé partie 4.](figs/dijkstra_ex_3.png)
# Stockage
# Algorithme de Dijkstra (corrigé)
* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe orienté?
![Le corrigé partie 5.](figs/dijkstra_ex_4.png)
. . .
# Algorithme de Dijkstra (corrigé)
![Le corrigé partie 6.](figs/dijkstra_ex_5.png)
* $\mathcal{O}(|V|^2)$.
* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe non-orienté?
# Limitation de l'algorithme de Dijkstra
## Que se passe-t-il pour?
![Exemple.](figs/exemple_neg.png){width=50%}
## Quel est le problème?
. . .
* $\mathcal{O}(|V|-1)|V|/2$.
* 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.
# Considérations d'efficacité
* Dans quel type de graphes la matrice d'adjacence est utile?
# Plus cours chemin pour toute paire de sommets
## Comment faire pour avoir toutes les paires?
. . .
* Dans les graphes denses.
* Pourquoi?
* 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|)$.
. . .
* Dans les graphes peu denses, la matrice d'adjacence est essentiellement composée de `0`.
## Solution alternative: Floyd--Warshall
## Remarque
* 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.
* Dans la majorité des cas, les grands graphes sont peu denses.
* Comment représenter un graphe autrement?
# Algorithme de Floyd--Warshall
# La liste d'adjacence (non-orienté)
## Idée générale
* Pour chaque sommet $v\in V$, stocker les sommets adjacents à $v$-
* Quelle structure de données pour la liste d'adjacence?
* 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
* Tableau de liste chaînée, vecteur (tableau dynamique), etc.
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
## Exemple
![Le graphe, $D=w$.](figs/floyd_exemple.png)
![Un graphe non-orienté.](figs/ex_graph_adj.pdf){width=80%}
::::
:::: column
## Quelle liste d'adjacence?
## Que vaut $D^{(0)}$ (3min)?
. . .
![La liste d'adjacence.](figs/ex_graph_list_adj.pdf)
$$
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}
$$
::::
:::
# La liste d'adjacence (orienté)
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## Quelle liste d'adjacence pour...
## On part de $D^{(0)}$?
* Matrix (2min)
$$
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}
$$
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
0-->1;
0-->2;
1-->2;
3-->0;
3-->1;
3-->2;
```
::::
:::: column
```
## Que vaut $D^{(1)}$ (3min)?
. . .
$$
D^{(1)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & \mathbf{6} & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \mathbf{3} & \mathbf{5} & 0 & \mathbf{4} \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(0)}$
```
$$
D^{(0)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 8 & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \infty & \infty & 0 & 5 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Complexité
## Stockage
:::: column
* Quelle espace est nécessaire pour stocker une liste d'adjacence (en fonction de $|E|$ et $|V|$)?
## Que vaut $D^{(1)}$ (3min)?
. . .
$$
\mathcal{O}(|E|)
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}
$$
* Pour les graphes *non-orientés*: $\mathcal{O}2|E|$.
* Pour les graphes *orientés*: $\mathcal{O}|E|$.
## Exemple
## Définition
$$
D_{42}^{(1)}=D_{41}^{(0)}+D_{12}^{(0)}=1+2<\infty.
$$
* Le **degré** d'un sommet $v$, est le nombre d'arêtes incidentes du sommet (pour les graphes orientés on a un degré entrant ou sortant).
* Comment on retrouve le degré de chaque sommet avec la liste d'adjacence?
::::
. . .
:::
* C'est la longueur de la liste chaînée.
# Algorithme de Floyd--Warshall (exemple)
::: columns
# Parcours
:::: column
* Beaucoup d'applications nécessitent de parcourir des graphes:
* Trouver un chemin d'un sommet à un autre;
* Trouver si le graphe est connexe;
* Il existe *deux* parcours principaux:
* en largeur (Breadth-First Search);
* en profondeur (Depth-First Search).
* Ces parcours créent *un arbre* au fil de l'exploration (si le graphe est non-connexe cela crée une *forêt*, un ensemble d'arbres).
## On part de $D^{(1)}$
# Illustration: parcours en largeur
$$
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}
$$
![Le parcours en largeur.](figs/parcours_larg.pdf){width=80%}
# Exemple
::::
## Étape par étape (blanc non-visité)
:::: column
![Initialisation.](figs/parcours_larg_0.pdf){width=50%}
## Que vaut $D^{(2)}$ (3min)?
## Étape par étape (gris visité)
. . .
![On commence en `x`.](figs/parcours_larg_1.pdf){width=50%}
$$
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}
$$
# Exemple
::::
## Étape par étape
:::
![On commence en `x`.](figs/parcours_larg_1.pdf){width=50%}
# Algorithme de Floyd--Warshall (exemple)
## Étape par étape (vert à visiter)
::: columns
![Vister `w`, `t`, `y`.](figs/parcours_larg_2.pdf){width=50%}
:::: column
# Exemple
## On part de $D^{(2)}$
## Étape par étape
$$
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}
$$
![Vister `w`, `t`, `y`.](figs/parcours_larg_2.pdf){width=50%}
## Étape par étape
::::
![`w`, `t`, `y` visités. `u`, `s` à visiter.](figs/parcours_larg_3.pdf){width=50%}
:::: column
# Exemple
## Que vaut $D^{(3)}$ (3min)?
## Étape par étape
. . .
![`w`, `t`, `y` visités. `u`, `s` à visiter.](figs/parcours_larg_3.pdf){width=50%}
$$
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}
$$
## Étape par étape
::::
:::
![`u`, `s`, visités. `r` à visiter.](figs/parcours_larg_4.pdf){width=50%}
# Algorithme de Floyd--Warshall (exemple)
# Exemple
::: columns
## Étape par étape
:::: column
![`u`, `s`, visités. `r` à visiter.](figs/parcours_larg_4.pdf){width=50%}
## On part de $D^{(3)}$
## Étape par étape
$$
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}
$$
![`r` visité. `v` à visiter.](figs/parcours_larg_5.pdf){width=50%}
::::
# Exemple
:::: column
## Étape par étape
## Que vaut $D^{(4)}$ (3min)?
![`r` visité. `v` à visiter.](figs/parcours_larg_5.pdf){width=50%}
. . .
## Étape par étape
$$
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}
$$
![The end. Plus rien à visiter!](figs/parcours_larg_6.pdf){width=50%}
::::
# En faisant ce parcours...
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## Du parcours de l'arbre
## On part de $D^{(4)}$
![](figs/parcours_larg_6.pdf){width=100%}
$$
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
## Quel arbre est créé par le parcours (2min)?
## Que vaut $D^{(5)}$ (3min)?
. . .
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
0[x]-->1[w];
0-->2[t];
0-->3[y];
2-->9[u];
1-->4[s];
4-->5[r];
5-->6[v];
```
$$
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}
$$
::::
:::
## Remarques
# 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$?
* Le parcours dépend du point de départ dans le graphe.
* L'arbre sera différent en fonction du noeud de départ, et de l'ordre de parcours des voisins d'un noeud.
# Algorithme de Floyd--Warshall
# Le parcours en largeur
## The pseudo-code
## L'algorithme, idée générale (3min, matrix)?
* Quelle structure de données?
```C
int distance[n][n];
```
. . .
* Quelle initialisation?
```C
v = un sommet du graphe
i = 1
pour sommet dans graphe et sommet non-visité
visiter(v, sommet, i) // marquer sommet à distance i visité
i += 1
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
```
## Remarque
# Algorithme de Floyd--Warshall
* `i` est la distance de plus cours chemin entre `v` et les sommets en cours de visite.
## The pseudo-code
* Quel est le code pour le calcul de la matrice $D$?
# Le parcours en largeur
```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
```
## L'algorithme, pseudo-code (3min, matrix)?
# Algorithme de Floyd--Warshall
* Comment garder la trace de la distance?
## 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.
$$
* Utilisation d'une **file**
* 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.
$$
. . .
```C
initialiser(graphe) // tous sommets sont non-visités
file = visiter(sommet, vide) // sommet est un sommet du graphe au hasard
tant que !est_vide(file)
v = défiler(file)
file = visiter(v, file)
```
* Moralité: si le chemin est plus court en passant par $k$, alors il faut utiliser son prédécesseur!
## Que fait visiter?
# Algorithme de Floyd--Warshall
```
file visiter(sommet, file)
sommet = visité
pour w = chaque arête de sommet
si w != visité
file = enfiler(file, w)
retourne file
```
# Exercice (5min)
## La matrice de précédence (pseudo-code, 3min)
## Appliquer l'algorithme sur le graphe
. . .
![](figs/parcours_larg_0.pdf){width=50%}
```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
```
* En partant de `v`, `s`, ou `u` (par colonne de classe).
* Bien mettre à chaque étape l'état de la file.
# Algorithme de Floyd--Warshall (exercice)
# Complexité du parcours en largeur
## Étape 1
::: columns
* Extraire un sommet de la file;
:::: column
## Étape 2
![Le graphe, $D=w$.](figs/floyd_exemple.png)
* Traîter tous les sommets adjacents.
## Quelle est la complexité?
::::
. . .
:::: column
* Étape 1: $\mathcal{O}(|V|)$,
* Étape 2: $\mathcal{O}(2|E|)$,
* Total: $\mathcal{O}(|V| + |2|E|)$.
## Que vaut $P^{(0)}$ (3min)?
# Exercice
. . .
* Établir la liste d'adjacence et appliquer l'algorithme de parcours en largeur au graphe
$$
P^{(0)}=\begin{bmatrix}
- & 1 & 1 & - & 1 \\
2 & - & 2 & - & 2 \\
3 & 3 & - & 3 & 3 \\
4 & - & - & - & 4 \\
- & - & - & 5 & - \\
\end{bmatrix}
$$
```{.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
# Algorithme de Floyd--Warshall (exercice)
![Le parcours en profondeur. À quel parcours d'arbre cela ressemble-t-il?](figs/parcours_prof.pdf){width=80%}
# Parcours en profondeur
::: columns
## Idée générale
:::: column
* 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.
![Le graphe, $D=w$.](figs/floyd_exemple.png)
## Remarque
* La récursivité est équivalent à l'utilisation d'une **pile**.
::::
# Parcours en profondeur
:::: column
## Pseudo-code (5min)
## Que vaut $P^{(5)}$ (10min)?
. . .
```C
initialiser(graphe) // tous sommets sont non-visités
pile = visiter(sommet, vide) // sommet est un sommet du graphe au hasard
tant que !est_vide(pile)
v = dépiler(pile)
pile = visiter(v, pile)
```
## Que fait visiter?
$$
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}
$$
. . .
::::
```C
pile visiter(sommet, pile)
sommet = visité
pour w = chaque arête de sommet
si w != visité
pile = empiler(pile, w)
retourne pile
```
:::
# Exercice: retrouver le chemin entre 1 et 4 (5min)
# Exercice
$$
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}
$$
* É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;
```
## Solution
# Interprétation des parcours
* 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.
* Un graphe vu comme espace d'états (sommet: état, arête: action);
* Labyrinthe;
* Arbre des coups d'un jeu.
# Exercice complet
. . .
## Appliquer l'algorithme de Floyd--Warshall au graphe suivant
* 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!
![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...
* Quand on a un temps limité
* BFS explore beaucoup de coups dans un futur proche;
* DFS explore peu de coups dans un futur lointain.
---
title: "Graphes - Plus court chemin"
date: "2022-05-03"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Arbres couvrants"
date: "2025-06-06"
---
# Questions
# Arbres couvrants
* Qu'est-ce qu'un graphe? Un graphe orienté? Un graphe pondéré?
\Huge Les arbres couvrants
. . .
# Trouver un réseau électrique pour
* Ensemble de sommets et arêtes, avec une direction, possédant une pondération.
* Comment représenter un graphe informatiquement?
![Ces maisons n'ont pas d'électricité.](figs/arbre_couvrant_vide.png)
. . .
# Solution: pas optimale
* Liste ou matrice d'adjacence.
* Quels sont les deux parcours que nous avons vus?
![Le réseau simple, mais nul.](figs/arbre_couvrant_mal.png)
. . .
* La longueur totale des câbles est super longue!
# Solution: optimale
* Parcours en largeur et profondeur.
* Donner l'idée générale des deux parcours.
![Le meilleur réseau.](figs/arbre_couvrant_bien.png)
# Illustration: plus courts chemins
# Formalisation: Les arbres couvrants
![Illustration de plus court chemin de `s` à `t`.](figs/courts_chemin_intro.pdf)
## Application: minimisation des coûts
* Équipement d'un lotissement avec des lignes électriques/téléphoniques, des canalisations, ...
# Plus courts chemins
. . .
## Contexte: les réseaux (informatique, transport, etc.)
* Pour réduire les coûts, on cherche à minimiser la longueur totale des câbles/tuyaux.
* 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
* Les lignes/tuyaux forment un *arbre couvrant*.
* Quel est le plus court chemin entre `s` et `t`.
. . .
# Exemples d'application de plus court chemin
* La meilleure option est un *arbre couvrant minimal*.
## 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?
# Formalisation: Les arbres couvrants
![Taux de change.](figs/taux_change.pdf){width=80%}
* Qu'est-ce qu'un arbre couvrant? Des idées? De quel objet part-on? Où va-t-on?
. . .
* 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
* Un arbre couvrant d'un graphe non-orienté et connexe est:
* un arbre inclus dans le graphe qui connecte tous les sommets du graphe.
# Exemples d'application de plus court chemin
. . .
## Formulation sous forme d'un graphe: Comment (3min)?
![Exemple d'arbres couvrants d'un graphe connexe.](figs/arbre_couvrant_exemples.png)
![Taux de change.](figs/taux_change.pdf){width=80%}
# Arbres couvrants
* Quels algorithmes que nous avons déjà vus, permettent de construire des arbres couvrants?
# Exemples d'application de plus court chemin
. . .
* Les parcours en largeur et en profondeur!
. . .
## Formulation sous forme d'un graphe: Comment (3min)?
![Graphe et parcours comme arbres couvrants.](figs/arbres_couvrants_parcours.png)
![Graphes des taux de change.](figs/taux_change_graphe.pdf){width=60%}
# Arbres couvrants minimaux
* 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.
* Un *arbre couvrant minimal* est un sous-graphe d'un graphe non-orienté pondéré $G(V,E)$ tel quel:
* C'est un arbre (graphe acyclique);
* Il couvre tous les sommets de $G$ et contient $|V|-1$ arêtes;
* Le coût total associé aux arêtes de l'arbre est minimum parmi tous les arbres couvrants possibles.
. . .
## Problème
* Est-il unique?
* On aimerait plutôt avoir une somme...
. . .
* Pas forcément.
# Exemples d'application de plus court chemin
# Arbres couvrants minimaux
## Conversion du problème en plus court chemin
* Comment générer un arbre couvrant minimal?
* 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`.
![Un graphe, connexe, non-orienté, pondéré, et un arbre couvrant minimal.](figs/arbre_couvrant_minimal_exemple.png)
![Graphe des taux de change avec logs.](figs/taux_change_graphe_log.pdf){width=60%}
# Algorithme de Prim
* Cette conversion se base sur l'idée que
::: columns
$$
\log(u\cdot v)=\log(u)+\log(v).
$$
:::: column
# Applications de plus courts chemins
## Un exemple
## Quelles applications voyez-vous?
![Le graphe de départ.](figs/prim_0.png)
. . .
::::
* Déplacement d'un robot;
* Planificaiton de trajet / trafic urbain;
* Routage de télécommunications;
* Réseau électrique optimal;
* ...
:::: column
# Plus courts chemins à source unique
## On part de `e` (au hasard)
* 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?
![Le sommet `e` est couvert.](figs/prim_1.png)
. . .
::::
* Un parcours en largeur!
:::
# Algorithme de Dijkstra
# Algorithme de Prim
## Comment chercher pour un plus court chemin?
::: columns
. . .
:::: column
```
si distance(u,v) > distance(u,w) + distance(w,v)
on passe par w plutôt qu'aller directement
```
## On choisit comment?
# Algorithme de Dijkstra
![Quelle arête choisir?](figs/prim_1.png)
## 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.
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
# Algorithme de Dijkstra
::::
## Pseudo-code (5min, matrix)
:::: column
. . .
```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)
u = min(q, distance) // plus petite distance dans q
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
retourne distance
```
## L'arête `e->d`
# Algorithme de Dijkstra
![Le sommet `d` est couvert.](figs/prim_2.png)
* Cet algorithme, nous donne le plus court chemin mais...
* ne nous donne pas le chemin!
::::
## Comment modifier l'algorithme pour avoir le chemin?
:::
. . .
# Algorithme de Prim
* Pour chaque nouveau noeud à visiter, il suffit d'enregistrer d'où on est venu!
* On a besoin d'un tableau `précédent`.
::: columns
## Modifier le pseudo-code ci-dessus pour ce faire (3min matrix)
:::: column
# Algorithme de Dijkstra
## On choisit comment?
```C
tab, tab dijkstra(graph, s, t)
pour chaque v dans graphe
distance[v] = infini
précédent[v] = indéfini
q = ajouter(q, v)
distance[s] = 0
tant que non_vide(q)
u = min(q, distance) // plus petite distance dans q
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
précédent[v] = u
retourne distance, précédent
```
![Quelle arête choisir?](figs/prim_2.png)
# Algorithme de Dijkstra
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
## Comment reconstruire un chemin ?
::::
:::: column
. . .
```C
pile parcours(précédent, s, t)
sommets = vide
u = t
// on a atteint t ou on ne connait pas de chemin
si u != s && précédent[u] != indéfini
tant que vrai
sommets = empiler(sommets, u)
u = précédent[u]
si u == s // la source est atteinte
retourne sommets
retourne sommets
```
## L'arête `d->a`
![Le sommet `a` est couvert.](figs/prim_3.png)
# Algorithme de Dijkstra
::::
:::
# Algorithme de Prim
::: columns
:::: column
![Initialisation.](figs/dijkstra_0.png)
## On choisit comment?
![Quelle arête choisir?](figs/prim_3.png)
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
::::
......@@ -251,20 +184,27 @@ pile parcours(précédent, s, t)
. . .
![1 visité, `D[2]=1`, `D[4]=3`.](figs/dijkstra_1.png)
## L'arête `d->c`
![Le sommet `c` est couvert.](figs/prim_4.png)
::::
:::
# Algorithme de Dijkstra
# Algorithme de Prim
::: columns
:::: column
![Plus court est 2.](figs/dijkstra_1.png)
## On choisit comment?
![Quelle arête choisir?](figs/prim_4.png)
. . .
* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
::::
......@@ -272,308 +212,520 @@ pile parcours(précédent, s, t)
. . .
![2 visité, `D[3]=2`, `D[7]=3`.](figs/dijkstra_2.png)
## L'arête `e->b`
![Le sommet `b` est couvert.](figs/prim_5.png)
* Game over!
::::
:::
# Algorithme de Dijkstra
# Exemple d'algorithme de Prim
::: columns
:::: column
:::: {.column width="40%"}
![Plus court est 3.](figs/dijkstra_2.png)
## Un exemple
![Étape 1.](figs/prim_1.png)
::::
:::: column
```
FP | e | d | b | c | a |
----------------------------------
D | 0 | inf | inf | inf | inf |
| e | d | b | c | a |
----------------------------------
P | - | - | - | - | - |
```
## Devient?
. . .
![3 visité, `D[7]=3` inchangé, `D[6]=6`.](figs/dijkstra_3.png)
```
FP | d | b | c | a |
----------------------------
D | 4 | 5 | 5 | inf |
| e | d | b | c | a |
----------------------------------
P | - | e | e | e | - |
```
::::
:::
# Algorithme de Dijkstra
# Exemple d'algorithme de Prim
::: columns
:::: column
:::: {.column width="40%"}
![Plus court est 4 ou 7.](figs/dijkstra_3.png)
## Un exemple
![Étape 2.](figs/prim_2.png)
::::
:::: column
```
FP | d | b | c | a |
----------------------------
D | 4 | 5 | 5 | inf |
| e | d | b | c | a |
----------------------------------
P | - | e | e | e | - |
```
## Devient?
. . .
![4 visité, `D[7]=3` inchangé, `D[5]=9`.](figs/dijkstra_4.png)
```
FP | a | c | b |
----------------------
D | 2 | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Algorithme de Dijkstra
# Exemple d'algorithme de Prim
::: columns
:::: column
:::: {.column width="40%"}
![Plus court est `7`.](figs/dijkstra_4.png)
## Un exemple
![Étape 3.](figs/prim_3.png)
::::
:::: column
```
FP | a | c | b |
----------------------
D | 2 | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
## Devient?
. . .
![7 visité, `D[5]=7`, `D[6]=6` inchangé.](figs/dijkstra_5.png)
```
FP | c | b |
----------------
D | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Algorithme de Dijkstra
# Exemple d'algorithme de Prim
::: columns
:::: column
:::: {.column width="40%"}
![Plus court est 6.](figs/dijkstra_5.png)
## Un exemple
![Étape 4.](figs/prim_4.png)
::::
:::: column
```
FP | c | b |
----------------
D | 4 | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
## Devient?
. . .
![`6` visité, `D[5]=7` inchangé.](figs/dijkstra_6.png)
```
FP | b |
----------
D | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Algorithme de Dijkstra
# Exemple d'algorithme de Prim
::: columns
:::: column
:::: {.column width="40%"}
![Plus court est 5 et c'est la cible.](figs/dijkstra_6.png)
## Un exemple
![Étape 5.](figs/prim_4.png)
::::
:::: column
```
FP | b |
----------
D | 5 |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
## Devient?
. . .
![The end, tous les sommets ont été visités.](figs/dijkstra_7.png)
```
FP |
----
D |
| e | d | b | c | a |
----------------------------------
P | - | e | e | d | d |
```
::::
:::
# Algorithme de Dijkstra amélioré
# Algorithme de Prim
## On peut améliorer l'algorithme
## Structures de données
* Avec une file de priorité!
* Dans quoi allons-nous stocker les sommets?
## Une file de priorité est
. . .
* File de priorité min.
* Autre chose?
* 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?
* Tableau des distances (comme pour Dijkstra).
* Autre chose?
. . .
* On insère les éléments à haute priorité tout devant dans la file!
* Tableau des parents (presque comme pour Dijkstra).
* Autre chose?
# Les files de priorité
. . .
## Trois fonction principales
* Non.
```C
booléen est_vide(élément) // triviale
élément enfiler(élément, data, priorité)
data défiler(élément)
rien modifier_priorité(élément, data, priotié)
nombre priorité(data) // utilitaire
```
# Algorithme de Prim
## Pseudo-implémentation: structure (1min)
## Initialisation: Pseudo-code (2min)
. . .
```C
struct élément
data
priorité
élément suivant
file_priorité, distance, parent initialisation(graphe)
s_initial = aléatoire(graphe)
distance[s_initial] = 0
fp = file_p_vide()
pour s_courant dans sommets(graphe)
si s_courant != s_initial
distance[s_courant] = infini
parent[s_courant] = indéfini
fp = enfiler(fp, s_courant, distance[s_courant])
retourne fp, distance, parent
```
# Les files de priorité
# Algorithme de Prim
## Pseudo-implémentation: enfiler (2min)
\footnotesize
## Algorithme: Pseudo-code (5min)
. . .
```C
élément enfiler(élément, data, priorité)
n_élément = créer_élément(data, priorité)
si est_vide(élément)
retourne n_élément
si priorité(d) > priorité(e.d)
n_élément.suivant = élément
retourne n_élément
sinon
tmp = élément
préc = élément
tant que !est_vide(tmp) && priorité < tmp.priorité
préc = tmp
tmp = tmp.suivant
prev.suivant = n_élément
n_élément.suivant = tmp
retourne élément
distance, parent prim(graphe)
fp, distance, parent initialisation(graphe)
sommets = vide
tant que !est_vide(fp)
s_courant, fp = défiler(fp)
sommets = insérer(sommets, s_courant)
pour s_voisin dans voisinage(s_courant) et pas dans sommets
// ou dans fp
si poids(s_courant, s_voisin) < distance[s_voisin]
parent[s_voisin] = s_courant
distance[s_voisin] = poids(s_courant, s_voisin)
fp = changer_priorité(fp, s_voisin,
poids(s_courant, s_voisin))
retourne distance, parent
```
# Les files de priorité
# Exercice: algorithme de Prim
## Pseudo-implémentation: défiler (2min)
## Appliquer l'algorithme de Prim à (15min):
. . .
![En démarrant du sommet $V_1$.](figs/prim_exercice.png)
```C
data, élément défiler(élément)
si est_vide(élément)
retourne AARGL!
sinon
tmp = élément.data
n_élément = élément.suivant
libérer(élément)
retourne tmp, n_élément
```
# Exercice: algorithme de Prim
# Algorithme de Dijkstra avec file de priorité min
## Solution
```C
distance, précédent dijkstra(graphe, s, t):
distance[source] = 0
fp = file_p_vide()
pour v dans sommets(graphe)
si v != s
distance[v] = infini
précédent[v] = indéfini
fp = enfiler(fp, v, distance[v])
tant que !est_vide(fp)
u, fp = défiler(fp)
pour v dans voisinage de u
n_distance = distance[u] + w(u, v)
si n_distance < distance[v]
distance[v] = n_distance
précédent[v] = u
fp = changer_priorité(fp, v, n_distance)
retourne distance, précédent
```
![](figs/prim_solution.png)
# Algorithme de Dijkstra avec file
# Complexité de l'algorithme de Prim
\footnotesize
```C
distance dijkstra(graphe, s, t)
---------------------------------------------------------
file_priorité, distance, parent initialisation(graphe)
// choix r et initialisation
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)-------------------------------
// initialisation distance et parent en O(|V|)
fp = enfiler(fp, v, distance[v])
retourne fp, distance, parent
distance, parent prim(graphe)
fp, distance, parent initialisation(graphe) // O(|V|)
sommets = vide
tant que !est_vide(fp)
O(1) u, fp = défiler(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_priorité(fp, v, n_distance)
---------------------------------------------------------
retourne distance
u, fp = défiler(fp) // O(|V|)
sommets = insérer(sommets, u)
pour v dans voisinage de u et pas dans sommets
si poids(u, v) < distance[v] // O(|E|)
// màj distance + parent
fp = changer_priorité(fp, v, poids(u, v)) // O(|V|)
retourne distance, parent
```
* Total: $\mathcal{O}(|V|^2+|E|\cdot |V|)$:
* Graphe dense: $\mathcal{O}(|V|^3)$
* Graphe peu dense: $\mathcal{O}(|V|^2)$
* $O(|V|)+O(|E|)+O(|V|^2)=O(|E|+|V|^2)$
* Remarque: $O(|E|)$ n'est pas mutliplié par $O(|V|)$, car les arêtes ne sont traitées qu'une fois en **tout**.
# Algorithme de Kruskal
* On ajoute les arêtes de poids minimal:
* si cela ne crée pas de cycle;
* on s'arrête quand on a couvert tout le graphe.
. . .
* Comment on fait ça?
. . .
* Faisons un exemple pour voir.
# Algorithme de Kruskal: exemple
::: columns
:::: column
## Un exemple
![Le graphe de départ.](figs/kruskal_0.png)
::::
:::: column
## On part de `(a, d)` (poids le plus faible)
![Les sommets `a, d` sont couverts.](figs/kruskal_1.png)
::::
:::
# Algorithme de Kruskal: exemple
::: columns
:::: column
## On continue avec `(c, d)`
![On aurait pu choisir `(d, e)` aussi.](figs/kruskal_1.png)
::::
:::: column
## Résultat
![Les sommets `a, d, c` sont couverts.](figs/kruskal_2.png)
::::
:::
# Algorithme de Kruskal: exemple
::: columns
:::: column
## On continue avec `(d, e)`
![Le poids de `(d, e)` est le plus bas.](figs/kruskal_2.png)
::::
# Algorithme de Dijkstra avec file
:::: column
## On peut faire mieux
## Résultat
* 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|)$.
![Les sommets `a, d, c, e` sont couverts.](figs/kruskal_3.png)
# Algorithme de Dijkstra (exercice, 5min)
::::
![L'exercice.](figs/dijkstra_exo.png){width=60%}
:::
* Donner la liste de priorité, puis...
# Algorithme de Kruskal: exemple
## A chaque étape donner:
::: columns
* Le tableau des distances à `a`;
* Le tableau des prédécessueurs;
* L'état de la file de priorité.
:::: column
## On continue avec `(b, e)`
![Le poids de `(b, e)` est le plus bas.](figs/kruskal_3.png)
::::
:::: column
# Algorithme de Dijkstra (corrigé)
## Résultat
![Le corrigé partie 1.](figs/dijkstra_ex_0.png)
![Les sommets `a, d, c, e, b` sont couverts.](figs/kruskal_4.png)
# Algorithme de Dijkstra (corrigé)
::::
![Le corrigé partie 2.](figs/dijkstra_ex_1.png)
:::
# Algorithme de Dijkstra (corrigé)
# Algorithme de Kruskal: exemple
![Le corrigé partie 3.](figs/dijkstra_ex_2.png)
::: columns
# Algorithme de Dijkstra (corrigé)
:::: column
![Le corrigé partie 4.](figs/dijkstra_ex_3.png)
## Mais pourquoi pas `(c, e)`?
# Algorithme de Dijkstra (corrigé)
![Le poids de `(b, e)` ou `(a,c)` est le même.](figs/kruskal_3.png)
![Le corrigé partie 5.](figs/dijkstra_ex_4.png)
::::
# Algorithme de Dijkstra (corrigé)
:::: column
![Le corrigé partie 6.](figs/dijkstra_ex_5.png)
## Résultat: un cycle
# Limitation de l'algorithme de Dijkstra
![Les sommets `a, d, c, e` sont couverts.](figs/kruskal_cycle.png)
## Que se passe-t-il pour?
::::
![Exemple.](figs/exemple_neg.png){width=50%}
:::
## Quel est le problème?
* Comment faire pour empêcher l'ajout de `(c, e)` ou `(a, c)`?
. . .
* 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.
* Si les deux sommets sont déjà couverts nous sommes sauvés (presque)!
# Algorithme de Kruskal
## L'initialisation
* Créer un ensemble de sommets pour chaque de sommet du graphe ($V_1$, $V_2$, ...):
* $V_1=\{v_1\}$, $V_2=\{v_2\}$, ...
* S'il y a $n$ sommets, il y a $n$ $V_i$.
* Initialiser l'ensemble $A$ des arêtes "sûres" constituant l'arbre couvrant minimal, $A=\emptyset$.
* Initialiser l'ensemble des sommets couverts $F=\emptyset$.
* Trier les arêtes par poids croissant dans l'ensemble $E$.
## Mise à jour
* Tant qu'il reste plus d'un $V_i$:
* Pour $(u,v)\in E$ à poids minimal:
* Retirer $(u,v)$ de $E$.
* Si $u\in V_i$ et $v\in V_j$ avec $V_i\cap V_j=\emptyset$:
* Ajouter $(u,v)$ à $A$;
* Fusionner $V_i$ et $V_j$ dans $F$.
# Algorithme de Kruskal: exemple
::: columns
:::: column
![Couvrir cet arbre bon sang!](figs/kruskal_enonce.png)
::::
:::: column
::::
:::
# Algorithme de Kruskal: solution
![La solution!](figs/kruskal_solution.png)
# Algorithme de Kruskal: exercice
::: columns
:::: column
![Couvrir cet arbre bon sang!](figs/kruskal_exercice.png)
::::
:::: column
::::
:::
# Algorithme de Kruskal: solution
![La solution!](figs/kruskal_solution_exercice.png)
---
title: "Introduction aux algorithmes"
date: "2021-10-06"
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"
---
# Quelques algorithmes simples
# Rappel (1/2)
## Quels algos avons-nous vu la semaine passée?
. . .
* L'algorithme de la factorielle.
* L'algorithme du PPCM.
# Rappel (2/2)
## Algorithme du PPCM?
. . .
```C
int main() {
int m = 15, n = 12;
int mult_m = m, mult_n = n;
while (mult_m != mult_n) {
if (mult_m > mult_n) {
mult_n += n;
} else {
mult_m += m;
}
}
printf("Le ppcm de %d et %d est %d\n", n, m, mult_m);
}
```
# Le calcul du PGCD (1/5)
## Définition
Le plus grand commun diviseur (PGCD) de deux nombres entiers non nuls est le
plus grand entier qui les divise en même temps.
## Exemples:
```C
PGCD(3, 4) = 1,
PGCD(4, 6) = 2,
PGCD(5, 15) = 5.
```
. . .
## Mathématiquement
Décomposition en nombres premiers:
$$
36 = 2^2\cdot 3^2,\quad 90=2\cdot 5\cdot 3^2,
$$
On garde tous les premiers à la puissance la plus basse
$$
PGCD(36, 90)=2^{\min{1,2}}\cdot 3^{\min{2,2}}\cdot 5^{\min{0,1}}=18.
$$
# Le calcul du PGCD (2/5)
## Algorithme
Par groupe de 3 (5-10min):
* réfléchissez à un algorithme alternatif donnant le PGCD de deux nombres;
* écrivez l'algorithme en pseudo-code.
. . .
## Exemple d'algorithme
```C
PGCD(36, 90):
90 % 36 != 0 // otherwise 36 would be PGCD
90 % 35 != 0 // otherwise 35 would be PGCD
90 % 34 != 0 // otherwise 34 would be PGCD
...
90 % 19 != 0 // otherwise 19 would be PGCD
90 % 18 == 0 // The end!
```
* 18 modulos, 18 assignations, et 18 comparaisons.
# Le calcul du PGCD (3/5)
## Transcrivez cet exemple en algorithme (groupe de 3) et codez-le (5-10min)!
. . .
## Optimisation
## Quelques algorithmes supplémentaires
* Combien d'additions / comparaisons au pire?
* Un moyen de le rendre plus efficace?
. . .
## Tentative de correction
```C
void main() {
int n = 90, m = 78;
int gcd = 1;
for (int div = n; div >= 2; div--) { // div = m, sqrt(n)
if (n % div == 0 && m % div == 0) {
gcd = div;
break;
}
}
printf("Le pgcd de %d et %d est %d\n", n, m, gcd);
}
```
# Le calcul du PGCD (4/5)
## Réusinage: l'algorithme d'Euclide
`Dividende = Diviseur * Quotient + Reste`
```C
PGCD(35, 60):
35 = 60 * 0 + 35 // 60 -> 35, 35 -> 60
60 = 35 * 1 + 25 // 35 -> 60, 25 -> 35
35 = 25 * 1 + 10 // 25 -> 35, 20 -> 25
25 = 10 * 2 + 5 // 10 -> 25, 5 -> 10
10 = 5 * 2 + 0 // PGCD = 5!
```
. . .
## Algorithme
Par groupe de 3 (5-10min):
* analysez l'exemple ci-dessus;
* transcrivez le en pseudo-code.
# Le calcul du PGCD (5/5)
## Pseudo-code
```C
entier pgcd(m, n)
tmp_n = n
tmp_m = m
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
## Implémentez le pseudo-code et postez le code sur matrix (5min).
. . .
## Un corrigé possible
```C
#include <stdio.h>
void main() {
int n = 90;
int m = 78;
printf("n = %d et m = %d\n", n, m);
int tmp_n = n;
int tmp_m = m;
while (tmp_n%tmp_m > 0) {
int tmp = tmp_n;
tmp_n = tmp_m;
tmp_m = tmp % tmp_m;
}
printf("Le pgcd de %d et %d est %d\n", n, m, tmp_m);
}
```
# Quelques algorithmes simples
* Remplissage d'un tableau et recherche de la valeur minimal
* Anagrammes
* Palindromes
* Crible d'ératosthène
* Crible d’Ératosthène
. . .
* Ces algorithme nécessitent d'utiliser des **tableaux**.
# Collections: tableaux statiques
\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
......@@ -51,8 +221,7 @@ patat:
# 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;
......@@ -89,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)
......@@ -104,12 +274,10 @@ Trouver la valeur minimale contenue dans un tableau et l'indice de l'élément l
```C
index = 0
min = tab[0]
for i in [1; SIZE] {
if min > tab[i] {
pour i de 1 à SIZE - 1
si min > tab[i]
min = tab[i]
index = i
}
}
```
# Recherche du minimum dans un tableau (2/2)
......@@ -122,7 +290,7 @@ for i in [1; SIZE] {
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;
}
......@@ -139,201 +307,11 @@ Trier un tableau par ordre croissant.
```C
ind = 0
boucle (ind < SIZE-1) {
Trouver le minimum du tableau, tab_min[ind:SIZE].
tant que (ind < SIZE-1)
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é.
# Un type de tableau particulier
## Les chaînes de caractères
```C
string = tableau + char + magie noire
```
# Le type `char`{.C}
- Le type `char`{.C} est utilisé pour représenter un caractère.
- C'est un entier 8 bits signé.
- En particulier:
- Écrire
```C
char c = 'A';
```
- Est équivalent à:
```C
char c = 65;
```
- Les fonctions d'affichage interprètent le nombre comme sa valeur ASCII.
# Chaînes de caractères (strings)
- Chaîne de caractère `==` tableau de caractères **terminé par la valeur** `'\0'`{.C} ou `0`{.C}.
## Exemple
```C
char *str = "HELLO !";
char str[] = "HELLO !";
```
Est représenté par
| `char` | `H` | `E` | `L` | `L` | `O` | | `!` | `\0`|
|---------|------|------|------|------|------|------|------|-----|
| `ASCII` | `72` | `69` | `76` | `76` | `79` | `32` | `33` | `0` |
. . .
## A quoi sert le `\0`?
. . .
Permet de connaître la fin de la chaîne de caractères (pas le cas des autres
sortes de tableaux).
# Syntaxe
```C
char name[5];
name[0] = 'P'; // = 70;
name[1] = 'a'; // = 97;
name[2] = 'u'; // = 117;
name[3] = 'l'; // = 108;
name[4] = '\0'; // = 0;
char name[] = {'P', 'a', 'u', 'l', '\0'};
char name[5] = "Paul";
char name[] = "Paul";
char name[100] = "Paul is not 100 characters long.";
```
# Fonctions
- Il existe une grande quantités de fonction pour la manipulation de chaînes de caractères dans `string.h`.
- Fonctions principales:
```C
// longueur de la chaîne (sans le \0)
size_t strlen(char *str);
// copie jusqu'à un \0
char *strcpy(char *dest, const char *src);
// copie len char
char *strncpy(char *dest, const char *src, size_t len);
// compare len chars
int strncmp(char *str1, char *str2, size_t len);
// compare jusqu'à un \0
int strcmp(char *str1, char *str2);
```
- Pour avoir la liste complète: `man string`.
. . .
## Quels problèmes peuvent se produire avec `strlen`, `strcpy`, `strcmp`?
# Les anagrammes
## Définition
Deux mots sont des anagrammes l'un de l'autre quand ils contiennent les mêmes
lettres mais dans un ordre différent.
## Exemple
| `t` | `u` | `t` | `u` | `t` | `\0` | ` ` | ` ` |
|------|------|------|------|------|------|------|-----|
| `t` | `u` | `t` | `t` | `u` | `\0` | ` ` | ` ` |
## Problème: Trouvez un algorithme pour déterminer si deux mots sont des anagrammes.
# Les anagrammes
## Il suffit de:
1. Trier les deux mots.
2. Vérifier s'ils contiennent les mêmes lettres.
## Implémentation en live (offerte par HepiaVPN)
```C
int main() { // pseudo C
tri(mot1);
tri(mot2);
if egalite(mot1, mot2) {
// anagrammes
} else {
// pas anagrammes
}
}
```
<!-- TODO: Live implémentation hors des cours? -->
# Les palindromes
Mot qui se lit pareil de droite à gauche que de gauche à droite:
. . .
* rotor, kayak, ressasser, ...
## Problème: proposer un algorithme pour détecter un palindrome
. . .
## Solution 1
```C
while (first_index < last_index {
if (mot[first_index] != mot [last_index]) {
return false;
}
first_index += 1;
last_index -= 1;
}
return true;
```
. . .
## Solution 2
```C
mot_tmp = revert(mot);
return mot == mot_tmp;
```
# Crible d'Ératosthène
Algorithme de génération de nombres premiers.
## Exercice
* À l'aide d'un tableau de booléens,
* Générer les nombres premiers plus petits qu'un nombre $N$
## Pseudo-code
* Par groupe de trois, réfléchir à un algorithme.
## Programme en C
* Implémenter l'algorithme et le poster sur le salon `Element`.
---
title: "Introduction aux algorithmes"
date: "2021-10-13"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Introduction aux algorithmes IV"
date: "2024-10-07"
---
# Crible d'Ératosthène: solution
# Tri par sélection
\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?
## Quel est l'algorithme du tri par sélection?
. . .
* 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**.
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 `i < SIZE-1`.
. . .
# Tri par sélection
## Avantages?
## 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é.
* Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
* Réduction de la complexité.
# Un type de tableau particulier
. . .
## "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
## Les chaînes de caractères
```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
string = tableau + char + magie noire
```
# Tableau à deux dimensions (3/4)
# Le type `char`{.C}
## Exercice: déclarer et initialiser aléatoirement un tableau `50x100`
. . .
- Le type `char`{.C} est utilisé pour représenter un caractère.
- C'est un entier 8 bits signé.
- En particulier:
- Écrire
```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
}
}
char c = 'A';
```
## Exercice: afficher le tableau
. . .
- Est équivalent à:
```C
for (int i = 0; i < NX; ++i) {
for (int j = 0; j < NY; ++j) {
printf("%d ", tab[i][j]);
}
printf("\n");
}
char c = 65;
```
- Les fonctions d'affichage interprètent le nombre comme sa valeur ASCII.
# Tableau à deux dimensions (4/4)
# Chaînes de caractères (strings)
## Attention
- Chaîne de caractère `==` tableau de caractères **terminé par la valeur** `'\0'`{.C} ou `0`{.C}.
* Les éléments ne sont **jamais** initialisés.
* Les bornes ne sont **jamais** vérifiées.
## Exemple
```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?
char *str = "HELLO !";
char str[] = "HELLO !";
```
# 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` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
Est représenté par
## Exercice
| `char` | `H` | `E` | `L` | `L` | `O` | | `!` | `\0`|
|---------|------|------|------|------|------|------|------|-----|
| `ASCII` | `72` | `69` | `76` | `76` | `79` | `32` | `33` | `0` |
* 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`
## A quoi sert le `\0`?
# 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.
Permet de connaître la fin de la chaîne de caractères (pas le cas des autres
sortes de tableaux).
```C
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
# Syntaxe
```C
enum days {
monday = 2, tuesday = 8, wednesday = -2,
thursday = 1, friday = 3, saturday = 12, sunday = 9
};
char name[5];
name[0] = 'P'; // = 70;
name[1] = 'a'; // = 97;
name[2] = 'u'; // = 117;
name[3] = 'l'; // = 108;
name[4] = '\0'; // = 0;
char name[] = {'P', 'a', 'u', 'l', '\0'};
char name[5] = "Paul";
char name[] = "Paul";
char name[100] = "Paul is not 100 characters long.";
```
* S'utilise comme un type standard et un entier
```C
enum days d = monday;
(d + 2) == tuesday + tuesday; // true
```
# Fonctions
# Types énumérés (2/2)
\footnotesize
* Très utile dans les `switch ... case`{.C}
- Il existe une grande quantités de fonction pour la manipulation de chaînes de caractères dans `string.h`.
- Fonctions principales:
```C
enum days d = monday;
switch (d) {
case monday:
// trucs
break;
case tuesday:
printf("0 ou 1\n");
break;
}
// longueur de la chaîne (sans le \0)
size_t strlen(char *str);
// copie jusqu'à un \0
char *strcpy(char *dest, const char *src);
// copie len char
char *strncpy(char *dest, const char *src, size_t len);
// compare len chars
int strncmp(char *str1, char *str2, size_t len);
// compare jusqu'à un \0
int strcmp(char *str1, char *str2);
```
* 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
$$
- Pour avoir la liste complète: `man 3 string`.
. . .
$$
= 247.
$$
# Conversion de décimal à binaire (1/2)
## Convertir 11 en binaire?
## Quel problème peut se produire avec `strlen`, `strcpy`, `strcmp`?
. . .
* On décompose en puissances de 2 en partant de la plus grande possible
- Si `\0` est absent... on a un comportement indéfini.
```
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
# Les anagrammes
$$
1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot
2^0=11.
$$
## Définition
# Conversion de décimal à binaire (2/2)
Deux mots sont des anagrammes l'un de l'autre quand ils contiennent les mêmes
lettres mais dans un ordre différent.
## Convertir un nombre arbitraire en binaire: 247?
## Exemple
* Par groupe établir un algorithme.
| `t` | `u` | `t` | `u` | `t` | `\0` | ` ` | ` ` |
|------|------|------|------|------|------|------|-----|
| `t` | `u` | `t` | `t` | `u` | `\0` | ` ` | ` ` |
. . .
## Algorithme
## Problème: Trouvez un algorithme pour déterminer si deux mots sont des anagrammes.
1. Initialisation
# Les anagrammes
```C
num = 247
while (2^N < num) {
N += 1
}
```
## Il suffit de:
. . .
1. Trier les deux mots.
2. Vérifier s'ils contiennent les mêmes lettres.
2. Boucle
## Implémentation ensemble
```C
while (N >= 0) {
bit = num / 2^N
num = num % 2^N
N += 1
int main() { // pseudo C
tri(mot1);
tri(mot2);
if egalite(mot1, mot2) {
// anagrammes
} else {
// pas anagrammes
}
}
```
# 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)
# Les palindromes
## Quels sont les entiers représentables en 8 bits?
Mot qui se lit pareil de droite à gauche que de gauche à droite:
. . .
```
01111111 => 127
10000000 => -128 // par définition
```
* rotor, kayak, ressasser, ...
## Quels sont les entiers représentables sur $N$ bits?
## Problème: proposer un algorithme pour détecter un palindrome
. . .
$$
-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}.
## Solution 1
```C
struct fraction { // déclaration du type
int32_t num, denom;
while (first_index < last_index) {
if (mot[first_index] != mot [last_index]) {
return false;
}
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
first_index += 1;
last_index -= 1;
}
return true;
```
# 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}
## Solution 2
```C
fraction_t *frac; // on crée un pointeur
frac->num = 1; // seg fault...
frac->denom = -1; // mémoire pas allouée.
mot_tmp = revert(mot);
return mot == mot_tmp;
```
![La représentation mémoire de
`fraction_t`.](figs/pointer_struct.svg){width=50%}
# Types composés: `struct`{.C} (5/6)
## Initialisation
# Crible d'Ératosthène
- Avec le passage par **référence** on peut modifier un struct *en place*.
- Les champs sont accessible avec le sélecteur `->`{.C}
Algorithme de génération de nombres premiers.
```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
}
```
## Exercice
# Types composés: `struct`{.C} (6/6)
* À l'aide d'un tableau de booléens,
* Générer les nombres premiers plus petits qu'un nombre $N$
## Initialisation version copie
## Pseudo-code
* On peut allouer une fraction, l'initialiser et le retourner.
* La valeur retournée peut être copiée dans une nouvelle structure.
* Par groupe de trois, réfléchir à un algorithme.
```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);
}
```
## Programme en C
<!-- # TODO jusqu'aux vacances -->
* Implémenter l'algorithme et le poster sur le salon `Element`.
<!-- * Refactorisation -->
<!-- * Tris et complexité -->
<!-- * Récursivité -->
---
title: "Représentation des nombres et récursivité"
date: "2021-10-20"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Tableaux à deux dimensions et récursivité"
date: "2024-10-14"
---
# Rappel / devoirs: Crible d'Ératosthène
# Représentation des nombres (1/2)
* 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.
* Le nombre `247`.
# Crible d'Ératosthène: solution
## 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
\footnotesize
```C
num = 247
while (2^N < num) {
N += 1
#include <stdio.h>
#include <stdbool.h>
#define SIZE 51
int main() {
bool tab[SIZE];
for (int i=0;i<SIZE;i++) {
tab[i] = true;
}
```
. . .
2. Boucle
```C
while (N >= 0) {
bit = num / 2^N
num = num % 2^N
N += 1
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");
}
```
# 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)
# Réusinage de code (refactoring)
## Que fait la multiplication par 2?
## Le réusinage est?
. . .
* Décalage de un bit vers la gauche!
```
0110
* 0010
---------
0000
+ 01100
---------
01100
```
* 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**.
. . .
## Que fait la multiplication par $2^N$?
## Avantages?
. . .
* Décalage de $N$ bits vers la gauche!
# Entiers signés (1/2)
Pas de nombres négatifs encore...
## Comment faire?
* Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
* Réduction de la complexité.
. . .
## Solution naïve:
* On ajoute un bit de signe (le bit de poids fort):
```
00000010: +2
10000010: -2
```
## Problèmes?
## "Make it work, make it nice, make it fast", Kent Beck.
. . .
* 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)
## Exercice:
```
00000010 2
+ 10000100 + -4
---------- ----
10000110 = -6 != -2
```
# Entiers signés (2/2)
## Beaucoup mieux
* Réusiner le code se trouvant sur
[Cyberlearn](https://cyberlearn.hes-so.ch/pluginfile.php/703384/mod_resource/content/1/comprendre.c).
* Complément à un:
* on inverse tous les bits: `1001 => 0110`.
## Encore un peu mieux
# Tableau à deux dimensions (1/4)
* Complément à deux:
* on inverse tous les bits,
* on ajoute 1 (on ignore les dépassements).
## Mais qu'est-ce donc?
. . .
* Comment écrit-on `-4` en 8 bits?
. . .
```
4 = 00000100
________
-4 => 00000100
11111011
+ 00000001
----------
11111100
```
# Le complément à 2 (1/2)
* Un tableau où chaque cellule est un tableau.
## Questions:
* Comment on écrit `+0` et `-0`?
* Comment calcule-t-on `2 + (-4)`?
* Quel est le complément à 2 de `1000 0000`?
## Quels cas d'utilisation?
. . .
## Réponses
* Tableau à double entrée;
* Image;
* Écran (pixels);
* Matrice (mathématique);
* Comment on écrit `+0` et `-0`?
# Tableau à deux dimensions (2/4)
```
+0 = 00000000
-0 = 11111111 + 00000001 = 100000000 => 00000000
```
* Comment calcule-t-on `2 + (-4)`?
## Exemple: tableau à 3 lignes et 4 colonnes d'entiers
```
00000010 2
+ 11111100 + -4
---------- -----
11111110 -2
```
* En effet
+-----------+-----+-----+-----+-----+
| `indices` | `0` | `1` | `2` | `3` |
+-----------+-----+-----+-----+-----+
| `0` | `7` | `4` | `7` | `3` |
+-----------+-----+-----+-----+-----+
| `1` | `2` | `2` | `9` | `2` |
+-----------+-----+-----+-----+-----+
| `2` | `4` | `8` | `8` | `9` |
+-----------+-----+-----+-----+-----+
```
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!
# Nombres à virgule (1/3)
## Comment manipuler des nombres à virgule?
$$
0.1 + 0.2 = 0.3.
$$
Facile non?
. . .
## Et ça?
## Syntaxe
```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));
}
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
```
. . .
## Que se passe-t-il donc?
# Nombres à virgule (2/3)
## Nombres à virgule fixe
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
| $2^3$ | $2^2$ | $2^1$ | $2^0$ | `.` | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ |
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
| `1` | `0` | `1` | `0` | `.` | `0` | `1` | `0` | `1` |
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
## Qu'est-ce ça donne en décimal?
. . .
$$
2^3+2^1+\frac{1}{2^2}+\frac{1}{2^4} = 8+2+0.5+0.0625=10.5625.
$$
# Tableau à deux dimensions (3/4)
## Limites de cette représentation?
\footnotesize
. . .
* Tous les nombres `> 16`.
* Tous les nombres `< 0.0625`.
* Tous les nombres dont la décimale est pas un multiple de `0.0625`.
# Nombres à virgule (3/3)
## Nombres à virgule fixe
* Nombres de $0=0000.0000$ à $15.9375=1111.1111$.
* Beaucoup de "trous" (au moins $0.0625$) entre deux nombres.
## Solution partielle?
. . .
* Rajouter des bits.
* Bouger la virgule.
# Nombres à virgule flottante (1/2)
## Notation scientifique
* Les nombres sont représentés en terme:
* Une mantisse
* Une base
* Un exposant
$$
\underbrace{22.1214}_{\mbox{nombre}}=\underbrace{221214}_{\mbox{mantisse}}\cdot
{\underbrace{10}_{\mbox{base}}}{\overbrace{^{-4}}^{\mbox{exp.}}},
$$
. . .
On peut donc séparer la représentation en 2:
* La mantisse
* L'exposant
# Nombres à virgule flottante (2/2)
## Quel est l'avantage?
. . .
On peut représenter des nombres sur énormément d'ordres de grandeur avec un
nombre de bits fixés.
## Différence fondamentale avec la virgule fixe?
. . .
La précision des nombres est **variable**:
* On a uniquement un nombre de chiffres **significatifs**.
$$
123456\cdot 10^{23}+ 123456\cdot 10^{-23}.
$$
## Quel inconvénient y a-t-il?
. . .
Ce mélange d'échelles entraîne un **perte de précision**.
# Nombres à virgule flottante simple précision (1/4)
Aussi appelés *IEEE 754 single-precision binary floating point*.
![Nombres à virgule flottante 32 bits. Source:
[Wikipedia](https://en.wikipedia.org/wiki/Single-precision_floating-point_format#/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)
. . .
\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
* 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?
## Exercice:
* La mantisse fait $\sim 2^{53}\sim10^{16}$,
* Ou encore $\sim \log_{10}(2^{53})\sim 16$,
* Environ **seize** chiffres significatifs.
## Plus petit/plus grand nombre représentable?
. . .
* Plus petite mantisse et exposant: $\sim 2^{-52}\cdot 2^{-1022}\sim 4\cdot 10^{-324}$,
* Plus grande mantisse et exposant: $\sim 2\cdot 2^{1023}\sim \cdot 1.8\cdot 10^{308}$.
# Précision finie (1/3)
## Erreur de représentation
* Les nombres réels ont potentiellement un **nombre infini** de décimales
* $1/3=0.\overline{3}$,
* $\pi=3.1415926535...$.
* Les nombres à virgule flottante peuvent en représenter qu'un **nombre
fini**.
* $1/3\cong 0.33333$, erreur $0.00000\overline{3}$.
* $\pi\cong3.14159$, erreur $0.0000026535...$.
On rencontre donc des **erreurs de représentation** ou **erreurs
d'arrondi**.
. . .
## Et quand on calcule?
* Avec deux chiffres significatifs
\begin{align}
&8.9+(0.02+0.04)=8.96=9.0,\\
&(8.9+0.02)+0.04=8.9+0.04=8.9.
\end{align}
. . .
## Même pas associatif!
# Précision finie (2/3)
## Erreur de représentation virgule flottante
$$
(1.2)_{10} = 1.\overline{0011}\cdot 2^0\Rightarrow 0\ 01111111\
00110011001100110011010.
$$
Erreur d'arrondi dans les deux derniers bits et tout ceux qui viennent
ensuite
$$
\varepsilon_2 = (00000000000000000000011)_2.
$$
Ou en décimal
$$
\varepsilon_{10} = 4.76837158203125\cdot 10^{-8}.
$$
# Précision finie (3/3)
## Comment définir l'égalité de 2 nombres à virgule flottante?
. . .
Ou en d'autres termes, pour quel $\varepsilon>0$ (appelé `epsilon-machine`) on a
$$
1+\varepsilon = 1,
$$
pour un nombre à virgule flottante?
. . .
Pour un `float` (32 bits) la différence est à
$$
2^{-23}=1.19\cdot 10^{-7},
$$
Soit la précision de la mantisse.
## Comment le mesurer (par groupe)?
Déclarer et initialiser aléatoirement un tableau `50x100` avec des valeurs `0` à `255`
. . .
```C
float eps = 1.0;
while ((float)1.0 + (float)0.5 * eps != (float)1.0) {
eps = (float)0.5 * eps;
#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
}
}
printf("eps = %g\n", eps);
```
# Erreurs d'arrondi
Et jusqu'ici on a encore pas fait d'arithmétique!
## Multiplication avec deux chiffres significatifs, décimal
$$
(1.1)_{10}\cdot (1.1)_{10}=(1.21)_{10}=(1.2)_{10}.
$$
En continuant ce petit jeu:
$$
\underbrace{1.1\cdot 1.1\cdots 1.1}_{\mbox{10 fois}}=2.0.
$$
Alors qu'en réalité
$$
1.1^{10}=2.5937...
$$
Soit une erreur de près de 1/5e!
## Exercice: afficher le tableau
. . .
## Le même phénomène se produit (à plus petite échelle) avec les `float` ou
`double`.
# Exemple de récursivité (1/2)
## La factorielle
```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");
}
```
. . .
## 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)` |
+----------------+----------------+----------------+----------------+
# Tableau à deux dimensions (4/4)
# Exemple de récursivité (2/2)
## Attention
## La factorielle
* Les éléments ne sont **jamais** initialisés.
* Les bornes ne sont **jamais** vérifiées.
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
}
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
## Que se passe-t-il quand on fait `factorial(4)`?
\footnotesize
. . .
* Aux échecs la reine peut se déplacer horizontalement et verticalement
* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous
## On dépile les calculs
+-----+-----+-----+-----+-----+-----+-----+
| ` ` | `0` | `1` | `2` | `3` | `4` | `5` |
+-----+-----+-----+-----+-----+-----+-----+
| `0` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `1` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `2` | `*` | `*` | `R` | `*` | `*` | `*` |
+-----+-----+-----+-----+-----+-----+-----+
| `3` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `4` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
+----------------+----------------+----------------+----------------+
| `1` | | | |
+----------------+----------------+----------------+----------------+
| `factorial(2)` | `2 * 1 = 2` | | |
+----------------+----------------+----------------+----------------+
| `factorial(3)` | `factorial(3)` | `3 * 2 = 6` | |
+----------------+----------------+----------------+----------------+
| `factorial(4)` | `factorial(4)` | `factorial(4)` | `4 * 6 = 24` |
+----------------+----------------+----------------+----------------+
## Exercice
# La récursivité (1/4)
* 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`.
## Formellement
## Poster le résultat sur `Element`
* Une condition de récursivité - qui *réduit* les cas successifs vers...
* Une condition d'arrêt - qui retourne un résultat
# Types énumérés (1/2)
## Pour la factorielle, qui est qui?
* Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.
```C
int factorial(int n) {
if (n > 1) {
return n * factorial(n - 1);
} else {
return 1;
}
}
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
# 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?
* On peut aussi donner des valeurs "custom"
```C
int factorial(int n) {
if (n > 1) { // Condition de récursivité
return n * factorial(n - 1);
} else { // Condition d'arrêt
return 1;
}
}
enum days {
monday = 2, tuesday = 8, wednesday = -2,
thursday = 1, friday = 3, saturday = 12, sunday = 9
};
```
# La récursivité (3/4)
## Exercice: trouver l'$\varepsilon$-machine pour un `double`
. . .
* S'utilise comme un type standard et un entier
```C
double epsilon_machine(double eps) {
if (1.0 + eps != 1.0) {
return epsilon_machine(eps / 2.0);
} else {
return eps;
}
}
enum days d = monday;
(d + 2) == monday + monday; // true
```
# La récursivité (4/4)
# 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");
}
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
binaire(13): n = 13, n % 2 = 1, n / 2 = 6,
binaire(6): n = 6, n % 2 = 0, n / 2 = 3,
binaire(3): n = 3, n % 2 = 1, n / 2 = 1,
binaire(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!
---
title: "Récursivité et complexité"
date: "2021-11-03"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
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é
# 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;
}
}
```
. . .
## 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)` |
+----------------+----------------+----------------+----------------+
# 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) {
......@@ -29,20 +123,59 @@ patat:
}
```
# La récursivité (3/4)
## Exercice: trouver l'$\varepsilon$-machine pour un `double`
. . .
* Code impératif
Rappelez-vous vous l'avez fait en style **impératif** plus tôt.
. . .
```C
int factorial(int n) {
int f = 1;
for (int i = 1; i < n; ++i) {
f *= i;
double epsilon_machine(double eps) {
if (1.0 + eps != 1.0) {
return epsilon_machine(eps / 2.0);
} else {
return eps;
}
return f;
}
```
# 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)
## Réusiner le code du PGCD avec une fonction récursive
......@@ -151,34 +284,45 @@ int fib_imp(int n) {
}
```
# Exponentiation rapide
\Huge L'exponentiation rapide ou indienne
# Exponentiation rapide ou indienne (1/4)
## But: Calculer $x^n$
* Algorithme naîf et impératif
* Quel est l'algorithmie le plus simple que vous pouvez imaginer?
. . .
```C
int pow(x, n) {
double pow(double x, int n) {
if (0 == n) {
return 1;
}
double p = x;
for (int i = 1; i < n; ++i) {
x *= x;
p = p * x; // p *= x
}
return x;
return p;
}
```
* Combien de multiplication et d'assignations en fonction de `n`?
. . .
* Complexité? Combien de multiplication en fonction de `n`?
* `n` assignations et `n` multiplications.
# Exponentiation rapide ou indienne (2/4)
* Algorithme naïf et récursif
* Proposez un algorithme naïf et récursif
. . .
```C
int pow(x, n) {
double pow(double x, int n) {
if (n != 0) {
return x * pow(x, n-1);
} else {
......@@ -211,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
......@@ -220,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 {
......@@ -231,271 +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
min = MAX;
for (i = 0; i < N; ++i) {
if (tab[i] < min) {
min = tab[i];
}
}
return min;
```
. . .
## Réponse
$$
\mathcal{O}(N).
$$
# Quelques exercices (3/3)
## Complexité du tri par sélection?
```C
ind = 0
while (ind < SIZE-1) {
min = find_min(tab[ind:SIZE]);
swap(min, tab[ind]);
ind += 1
}
```
. . .
## Réponse
### `min = find_min`
$$
(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2).
$$
## Finalement
$$
\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{
swaps})=\mathcal{O}(N^2).
$$
---
title: "Tris"
date: "2021-11-10"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Représentation des nombres, tris, et complexité"
date: "2024-11-11"
header-includes: |
\usepackage{xcolor}
---
# Représentation des nombres
# Tri par insertion (1/3)
\Huge La représentation des nombres
## But
# Représentation des nombres (1/2)
* trier un tableau par ordre croissant
* Le nombre `247`.
## Algorithme
## 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
Prendre un élément du tableau et le mettre à sa place parmis les éléments déjà
triés du 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` |
+-------+-------+-------+-------+-------+-------+-------+-------+
![Tri par insertion d'un tableau d'entiers](figs/tri_insertion.svg)
$$
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.
$$
# Tri par insertion (2/3)
# Conversion de décimal à binaire (1/2)
## Exercice: Proposer un algorithme
## 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
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;
num = 247
N = 0
tant que (2^(N+1) < num) {
N += 1
}
```
. . .
2. Boucle
```C
tant que (N >= 0) {
bit = num / 2^N
num = num % 2^N
N -= 1
}
```
# Tri par insertion (3/3)
# Les additions en binaire
Que donne l'addition `1101` avec `0110`?
## Question: Quelle est la complexité?
* 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?
. . .
* 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)$
## 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?
. . .
* Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$
* Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$
$$
(1111)_2 = 8+4+2+1 = 15
$$
# L'algorithme à la main
* Quel est l'entier non-signé minimal représentable avec 4 bit?
## Exercice *sur papier*
. . .
* Trier par insertion le tableau `[5, -2, 1, 3, 10]`
$$
(0000)_2 = 0+0+0+0 = 0
$$
```C
* Quel est l'entier non-signé min/max représentable avec N bit?
. . .
$$
0\mbox{ et }2^N-1.
$$
* Donc `uint32_t?` maximal est?
. . .
$$
2^{32}-1=4'294'967'295
$$
# Les multiplications en binaire (1/2)
Que donne la multiplication de `1101` avec `0110`?
* La multiplication est la même que dans le système décimal
```
1101 13
* 0110 * 6
--------- --------------
0000 78
11010
110100
+ 0000000
--------- --------------
1001110 64+0+0+8+4+2+0
```
# Les multiplications en binaire (2/2)
## Que fait la multiplication par 2?
. . .
* Décalage de un bit vers la gauche!
```
0110
* 0010
---------
0000
+ 01100
---------
01100
```
. . .
## Que fait la multiplication par $2^N$?
. . .
* Décalage de $N$ bits vers la gauche!
# Entiers signés (1/2)
Pas de nombres négatifs encore...
## Comment faire?
. . .
## Solution naïve:
* On ajoute un bit de signe (le bit de poids fort):
```
00000010: +2
10000010: -2
```
## Problèmes?
. . .
* Il y a deux zéros (pas trop grave): `10000000` et `00000000`
* Les additions différentes que pour les non-signés (très grave)
```
00000010 2
+ 10000100 + -4
---------- ----
10000110 = -6 != -2
```
# Entiers signés (2/2)
## Beaucoup mieux
* Complément à un:
* on inverse tous les bits: `1001 => 0110`.
## Encore un peu mieux
* Complément à deux:
* on inverse tous les bits,
* on ajoute 1 (on ignore les dépassements).
. . .
* Comment écrit-on `-4` en 8 bits?
. . .
```
4 = 00000100
________
-4 => 00000100
11111011
+ 00000001
----------
11111100
```
# Le complément à 2 (1/2)
\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)
......@@ -121,56 +414,59 @@ Non c'est normal, faisons un exemple.
# Tri rapide ou quicksort (3/8)
\footnotesize
Deux variables sont primordiales:
```C
int low, high; // les indices min/max des tableaux à trier
entier ind_min, ind_max; // les indices min/max des tableaux à trier
```
![Un exemple de quicksort.](figs/quicksort.svg)
# Tri rapide ou quicksort (4/8)
\footnotesize
Deux variables sont primordiales:
```C
int low, high; // les indices min/max des tableaux à trier
entier ind_min, ind_max; // les indices min/max des tableaux à trier
```
## Pseudocode: quicksort
```C
void quicksort(array, low, high) {
if (more than 1 elems) {
pivot_ind = partition(array, low, high);
if (something left of pivot)
quicksort(array, low, pivot_ind - 1);
if (something right of pivot)
quicksort(array, pivot_ind + 1, high);
}
}
```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]) != 0)
quicksort(tableau, ind_pivot + 1, ind_max)
```
# Tri rapide ou quicksort (5/8)
\footnotesize
## Pseudocode: partition
```C
int partition(array, low, high) {
pivot = array[high]; // choix arbitraire
i = low;
j = high-1;
while i < j {
en remontant i trouver le premier élément > pivot;
en descendant j trouver le premier élément < pivot;
swap(array[i], array[j]);
entier partition(entier tableau[], entier ind_min, entier ind_max)
pivot = tableau[ind_max] // choix arbitraire
i = ind_min
j = ind_max-1
tant que i < j:
en remontant i trouver le premier élément > pivot
en descendant j trouver le premier élément < pivot
échanger(tableau[i], tableau[j])
// les plus grands à droite
// mettre les plus petits à gauche
}
// on met le pivot "au milieu"
swap(array[i], array[pivot]);
return i; // on retourne l'indice pivot
}
échanger(tableau[i], tableau[ind_max])
retourne i // on retourne l'indice pivot
```
# Tri rapide ou quicksort (6/8)
......@@ -195,29 +491,6 @@ void quicksort(int size, int array[size], int first,
}
```
# L'algorithme à la main
## Exercice *sur papier*
* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
```C
```
# Tri rapide ou quicksort (7/8)
......@@ -231,10 +504,10 @@ int partition(int size, int array[size], int first, int last) {
int i = first - 1, j = last;
do {
do {
i++;
i += 1;
} while (array[i] < pivot && i < j);
do {
j--;
j -= 1;
} while (array[j] > pivot && i < j);
if (j > i) {
swap(&array[i], &array[j]);
......@@ -245,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?
......@@ -259,77 +532,13 @@ 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))$.
# Tri à bulle (1/4)
## Algorithme
* Parcours du tableau et comparaison des éléments consécutifs:
- Si deux éléments consécutifs ne sont pas dans l'ordre, ils sont échangés.
* On recommence depuis le début du tableau jusqu'à avoir plus d'échanges à
faire.
## Que peut-on dire sur le dernier élément du tableau après un parcours?
. . .
* Le plus grand élément est **à la fin** du tableau.
* Plus besoin de le traiter.
* A chaque parcours on s'arrête un élément plus tôt.
# Tri à bulle (2/4)
## Exemple
![Tri à bulles d'un tableau d'entiers](figs/tri_bulles.svg)
# Tri à bulle (3/4)
## Exercice: écrire l'algorithme (poster le résultat sur matrix)
. . .
```C
void bubble_sort(int size, int array[]) {
for i in [size-1, 1] {
sorted = true;
for j in [0, i-1] {
if (array[j] > array[j+1]) {
swap(array[j], array[j+1]);
sorted = false;
}
}
if (sorted) {
return;
}
}
}
```
# Tri à bulle (4/4)
## Quelle est la complexité du tri à bulles?
. . .
* Dans le meilleurs des cas:
* Le tableau est déjà trié: $\mathcal{O}(N)$ comparaisons.
* Dans le pire des cas, $N\cdot (N-1)/2\sim\mathcal{O}(N^2)$:
$$
\sum_{i=1}^{N-1}i\mbox{ comparaison et }3\sum_{i=1}^{N-1}i \mbox{ affectations
(swap)}\Rightarrow \mathcal{O}(N^2).
$$
* En moyenne, $\mathcal{O}(N^2)$ ($N^2/2$ comparaisons).
* En moyenne: $\mathcal{O}(N\cdot \log_2(N))$. -->
# L'algorithme à la main
## Exercice *sur papier*
* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
```C
......@@ -347,4 +556,3 @@ $$
```
---
title: "Backtracking et piles"
date: "2021-11-17"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
title: "Tris et complexité"
date: "2024-11-18"
header-includes: |
\usepackage{xcolor}
---
# L'algorithme à la main
# L'algorithme du PPCM *récursif* (1/3)
## Exercice *sur papier*
## Exemple d'algorithme pour le calcul
* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
```C
PPCM(36, 90):
36 < 90 // 36 + 36
72 < 90 // 72 + 36
108 > 90 // 90 + 90
108 < 180 // 108 + 36
144 < 180 // 144 + 36
180 = 180 // The End!
```
. . .
* On incrémente de 36 à gauche et de 90 à droite jusqu'à ce que les deux
soient égaux.
# L'algorithme du PPCM *récursif* (2/3)
## Rappel de l'algorithme
```C
// on cherche i*m == j*m (i,j aussi petits que possibles)
int ppcm(int m, int n) {
int mult_m = m, mult_n = n;
while (mult_m != mult_n) {
if (mult_m > mult_n) {
mult_n += n;
} else {
mult_m += m;
}
}
return mult_m;
}
```
. . .
## Écrire l'algorithme *récursif* du PPCM (matrix)
# L'algorithme du PPCM *récursif* (3/3)
## Pseudo-code
- Algorithme du PPCM de deux nombres `n` et `m`
- `ppcm(mult_n,mult_m) = ppcm(mult_n + n, mult_m)`
si `mult_n < mult_m` (récursivité)
- `ppcm(mult_n,mult_m) = ppcm(mult_n, mult_m + m)`
si `mult_n > mult_m` (récursivité)
- `ppcm(mult_n,mult_m) = mult_n`
si `mult_n = mult_m` (condition d’arrêt)
# Problème des 8-reines
* Placer 8 reines sur un échiquier de $8 \times 8$.
* Sans que les reines ne puissent se menacer mutuellement (92 solutions).
```
## Conséquence
# Le tri par base (radix sort)
* Deux reines ne partagent pas la même rangée, colonne, ou diagonale.
* Donc chaque solution a **une** reine **par colonne** ou **ligne**.
\Huge Le tri par base (radix sort)
## Généralisation
# Tri par base (radix sort)
* Placer $N$ reines sur un échiquier de $N \times
N$.
- Exemple de **backtracking** (retour en arrière) $\Rightarrow$ récursivité.
* 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.
![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%}
# Principe de l'algorithme
# Problème des 2-reines
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.
![Le problème des 2 reines n'a pas de solution.](figs/2reines.svg){width=50%}
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.
# Comment trouver les solutions?
# Illustration sur un exemple (1/6)
* 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à.
Soit la liste de nombre entier:
. . .
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 |
* Le jeu prend fin quand on a énuméré *toutes* les possibilités de poser les
reines.
Le plus petit élément est -9. On commence donc par décaler les valeurs de 9.
# Problème des 3-reines
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 14 | 4 | 10 | 15 | 13 | 3 | 11 | 0 | 11 |
![Le problème des 3 reines n'a pas de solution non plus.](figs/3reines.svg)
# Illustration sur un exemple (2/6)
# Problème des 4-reines
* Écrivons les éléments en représentation binaire.
* La valeur maximale est 15, on a besoin de 4 bits.
![Le problème des 4 reines a une solution.](figs/4reines.svg)
| 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 |
# Problème des 4-reines, symétrie
# Illustration sur un exemple (3/6)
![Le problème des 4 reines a une autre solution (symétrie
horizontale).](figs/4reines_sym.svg)
* On considère le bit de poids faible
# Problème des 5 reines
| 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** |
## 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.
* On obtient le tableau:
```C
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 111\textcolor{red}{0} | 010\textcolor{red}{0} | 101\textcolor{red}{0} | 111\textcolor{green}{1} | 110\textcolor{green}{1} | 001\textcolor{green}{1} | 101\textcolor{green}{1} | 000\textcolor{red}{0} | 101\textcolor{green}{1} |
| \textcolor{red}{1110} | \textcolor{red}{0100} | \textcolor{red}{1010} | \textcolor{red}{0000} | \textcolor{green}{1111} | \textcolor{green}{1101} | \textcolor{green}{0011} | \textcolor{green}{1011} | \textcolor{green}{1011} |
# Illustration sur un exemple (4/6)
* On passe au 2ème bit et on obtient le tableau:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 11\textcolor{green}{1}0 | 01\textcolor{red}{0}0 | 10\textcolor{green}{1}0 | 00\textcolor{red}{0}0 | 11\textcolor{green}{1}1 | 11\textcolor{red}{0}1 | 00\textcolor{green}{1}1 | 10\textcolor{green}{1}1 | 10\textcolor{green}{1}1 |
| \textcolor{red}{0100} | \textcolor{red}{0000} | \textcolor{red}{1101} | \textcolor{green}{1110} | \textcolor{green}{1010} | \textcolor{green}{1111} | \textcolor{green}{0011} | \textcolor{green}{1011} | \textcolor{green}{1011} |
. . .
* On passe au 3ème bit et on obtient le tableau:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| 0\textcolor{green}{1}00 | 0\textcolor{red}{0}00 | 1\textcolor{green}{1}01 | 1\textcolor{green}{1}10 | 1\textcolor{red}{0}10 | 1\textcolor{green}{1}11 | 0\textcolor{red}{0}11 | 1\textcolor{red}{0}11 | 1\textcolor{red}{0}11 |
| \textcolor{red}{0000} | \textcolor{red}{1010} | \textcolor{red}{0011} | \textcolor{red}{1011} | \textcolor{red}{1011} | \textcolor{green}{0100} | \textcolor{green}{1101} | \textcolor{green}{1110} | \textcolor{green}{1111} |
# Illustration sur un exemple (5/6)
4. On passe au dernier bit et on obtient le tableau final:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| \textcolor{red}{0}000 | \textcolor{green}{1}010 | \textcolor{red}{0}011 | \textcolor{green}{1}011 | \textcolor{green}{1}011 | \textcolor{red}{0}100 | \textcolor{green}{1}101 | \textcolor{green}{1}110 | \textcolor{green}{1}111 |
| \textcolor{red}{0000} | \textcolor{red}{0011} | \textcolor{red}{0100} | \textcolor{green}{1010} | \textcolor{green}{1011} | \textcolor{green}{1011} | \textcolor{green}{1101} | \textcolor{green}{1110} | \textcolor{green}{1111} |
. . .
* En revenant à la représentation décimale, on a le tableau trié:
```
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| 0 | 3 | 4 | 10 | 11 | 11 | 13 | 14 | 15 |
# Quelques observations sur le problème
# Illustration sur un exemple (6/6)
* 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.
* Pour revenir aux valeurs initiales, il faut décaler de 9 dans l'autre sens.
# Le code du problème des 8 reines (1/N)
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| -9 | -6 | -5 | 1 | 2 | 2 | 4 | 5 | 6 |
## Quelle structure de données?
* Et alors?
. . .
Une matrice de booléens fera l'affaire:
```C
bool board[n][n];
* 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);
}
``` -->
## Quelles fonctionalités?
# Un peu plus de détails (1/2)
## La fonction `decaler()`
```python
rien decaler(entier taille, entier tab[taille], entier val):
pour i de 0 à taille-1:
tab[i] -= val
```
. . .
```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);
## La fonction `echanger()`
```python
rien echanger(entier tab[], entier tab2[])
# échanger les tableaux (sans copier les valeurs)
```
# Le code du problème des 8 reines (2/N)
# Un peu plus de détails (2/2)
## Le calcul du nombre de solutions
## La fonction `alveole_0()`
```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
```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 code du problème des 8 reines (3/N)
. . .
## Le calcul du nombre de solutions
## La fonction `alveole_1()`
```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
```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 code du problème des 8 reines (4/N)
# Le tri par fusion (merge sort)
## Compris? Alors écrivez le code et postez le!
\Huge Le tri par fusion (merge sort)
. . .
# Tri par fusion (merge sort)
## Le nombre de solutions
* 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. -->
\footnotesize
<!-- 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. -->
```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;
}
}
}
}
```
# Principe de l'algorithme
* Soit `taille` la taille du tableau à trier.
* Pour `i = 0` à `entier(log2(taille))-1`:
* Fusion des paires de sous-tableaux successifs de taille `2**i` (ou moins pour l'extrémité)
# Le code du problème des 8 reines (5/N)
. . .
\footnotesize
* 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).
## Placer devant
# Exemple de tri par fusion
```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;
}
}
}
* 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);
```
# Les piles (1/N)
## Qu'est-ce donc?
# Algorithme de fusion possible
* Structure de données abstraite...
## Une idée?
. . .
* de type `LIFO` (*Last in first out*).
* 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
![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%}
# La fonction de fusion (pseudo-code autrement)
## Des exemples de la vraie vie
\footnotesize
## Une idée?
. . .
* 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).
```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
```
# Les piles (2/N)
<!-- ## Complexité
L'algorithme présenté précédemment nécessite un certain nombre d'opérations lié à la taille $N$ du tableau.
## Fonctionnalités
Il y a essentiellement $\log_2(N)$ étapes.
. . .
A chaque étape, le tableau est parcouru une fois avec un nombre constant effectué pour chacune des cases du tableau. En effet, l'opération de fusion implique de ne parcourir qu'une seule fois chacun des deux tableaux qu'on fusionne dans un 3ème tableau.
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?).
Ainsi, la complexité du tri par fusion est $\mathcal{O}(N\cdot \log_2(N)$. -->
. . .
# L'efficacité d'un algorithmique
4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler).
5. Nombre d'éléments (length).
\Huge L'efficacité d'un algorithmique
## Comment faire les 4,5 à partir de 1 à 3?
# Efficacité d'un algorithmique
Comment mesurer l'efficacité d'un algorithme?
. . .
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.
* Mesurer le temps CPU,
* Mesurer le temps d'accès à la mémoire,
* Mesurer la place prise mémoire,
. . .
## Existe en deux goûts
Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc!
* Pile avec ou sans limite de capacité (à concurrence de la taille de la
mémoire).
## Mesure du temps CPU
# Les piles (3/N)
```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));
```
## Implémentation
# Programme simple: mesure du temps CPU
* 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.
## Preuve sur un [petit exemple](../source_codes/complexity/sum.c)
## Quelle structure de données allons nous utiliser?
```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**.
. . .
Et oui vous avez deviné: un tableau!
## Conclusion
## La structure: de quoi avons-nous besoin (pile de taille fixe)?
* 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
#define MAX_CAPACITY 500
typedef struct _stack {
int data[MAX_CAPACITY]; // les données
int top; // indice du sommet
} stack;
int sorted_list[N];
bool in_list = is_present(N, sorted_list, elem);
```
# Les piles (4/N)
## Initialisation
* 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
void stack_init(stack *s) {
s->top = -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;
}
```
## Est vide?
* 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 stack_is_empty(stack s) {
return s.top == -1;
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;
}
```
## Empiler (ajouter un élément au sommet)
# 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
void stack_push(stack *s, int val) {
s->top += 1;
s->data[s->top] = val;
for (i = 2; i < sqrt(N); ++i) {
if (N % i == 0) {
return false;
}
}
return true;
```
# Les piles (5/N)
. . .
## Dépiler (enlever l'élément du sommet)
## Réponse
. . .
$$
\mathcal{O}(\sqrt{N}).
$$
# Quelques exercices (2/3)
## Complexité de trouver le minimum d'un tableau?
```C
int stack_pop(stack *s) {
s->top -= 1;
return s->data[s->top+1];
int min = MAX;
for (i = 0; i < N; ++i) {
if (tab[i] < min) {
min = tab[i];
}
}
return min;
```
## Jeter un oeil (regarder le sommet)
. . .
## Réponse
$$
\mathcal{O}(N).
$$
# Quelques exercices (3/3)
## Complexité du tri par sélection?
```C
int stack_peek(stack *s) {
return s->data[s->top];
int ind = 0;
while (ind < SIZE-1) {
min = find_min(tab[ind:SIZE]);
swap(min, tab[ind]);
ind += 1;
}
```
## Voyez-vous des problèmes potentiels avec cette implémentation?
. . .
* Empiler avec une pile pleine.
* Dépiler avec une pile vide.
* Jeter un oeil au sommet d'une pile vide.
## 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 et files d'attente"
date: "2021-11-25"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
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)
. . .
* de type `LIFO` (*Last in first out*).
## Algorithme
![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%}
* 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.
## Des exemples de la vraie vie
## Que peut-on dire sur le dernier élément du tableau après un parcours?
. . .
* 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).
* 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.
# Les piles (2/5)
# Tri à bulle (2/4)
## Fonctionnalités
. . .
## Exemple
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?).
![Tri à bulles d'un tableau d'entiers](figs/tri_bulles.svg)
. . .
4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler).
5. Nombre d'éléments (length).
# Tri à bulle (3/4)
## Comment faire les 4,5 à partir de 1 à 3?
## Exercice: écrire l'algorithme (poster le résultat sur matrix)
. . .
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.
```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?
. . .
## Existe en deux goûts
* 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).
* Pile avec ou sans limite de capacité (à concurrence de la taille de la
mémoire).
# L'algorithme à la main
# Les piles (3/5)
## Exercice *sur papier*
## Implémentation
* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]`
* 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.
```C
## 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)
## Initialisation
. . .
```C
void stack_init(stack *s) {
s->top = -1;
}
```
## Est vide?
. . .
```C
bool stack_is_empty(stack s) {
return s.top == -1;
}
```
## Empiler (ajouter un élément au sommet)
# Tri par insertion (1/3)
. . .
## But
```C
void stack_push(stack *s, int val) {
s->top += 1;
s->data[s->top] = val;
}
```
* trier un tableau par ordre croissant
# Les piles (5/5)
## Algorithme
## Dépiler (enlever l'élément du sommet)
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
int stack_pop(stack *s) {
s->top -= 1;
return s->data[s->top+1];
}
```
# Tri par insertion (2/3)
## Jeter un oeil (regarder le sommet)
## Exercice: Proposer un algorithme (en C)
. . .
```C
int stack_peek(stack *s) {
return s->data[s->top];
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;
}
}
```
## Quelle est la complexité de ces opérations?
# Tri par insertion (3/3)
## Question: Quelle est la complexité?
. . .
## Voyez-vous des problèmes potentiels avec cette implémentation?
* 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)$
. . .
* Empiler avec une pile pleine.
* Dépiler avec une pile vide.
* Jeter un oeil au sommet d'une pile vide.
* Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$
* Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$
# Gestion d'erreur, level 0
# L'algorithme à la main
* 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.
## Exercice *sur papier*
* Trier par insertion le tableau `[5, -2, 1, 3, 10]`
```C
## La panique
* En C, on a les `assert()` pour faire paniquer un programme.
# Assertions (1/3)
```C
#include <assert.h>
void assert(int expression);
```
## Qu'est-ce donc?
- 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`)
## À quoi ça sert?
- Permet de réaliser des tests unitaires.
- Permet de tester des conditions catastrophiques d'un programme.
- **Ne permet pas** de gérer les erreurs.
# Assertions (2/3)
<!-- \footnotesize -->
## Exemple
```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];
}
```
# Assertions (3/3)
# 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)
```
## Cas typiques d'utilisation
# Complexité algorithmique du radix-sort (2/2)
- Vérification de la validité des pointeurs (typiquement `!= NULL`{.C}).
- Vérification du domaine des indices (dépassement de tableau).
\footnotesize
## Bug vs. erreur de *runtime*
<!-- Voici une liste de parcours utilitaires de tableau:
- 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, ...).
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()```.
- Mais peuvent être pratiques quand même pour ça...
- Typiquement désactivées dans le code de production.
Ainsi, la complexité du tri par base est $\mathcal{O}(b\cdot N)$. -->
# La pile dynamique
## Pseudo-code
## Comment modifier le code précédent pour avoir une taille dynamique?
```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
// 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);
* 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);
```
## Et maintenant?
# 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)
```
. . .
```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();
```
* Au final: $\mathcal{O}(N\log_2(N))$.
## Exercice: ouvrir un repo/issues pour l'implémentation
# Complexité algorithmique du quick-sort (1/2)
* Oui-oui cela est une introduction au développement collaboratif (et
hippie).
## Pseudocode: quicksort
# Le tri à deux piles (1/N)
```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)
```
## Cas pratique
![Un exemple de tri à deux piles](figs/tri_piles.svg){width=70%}
# Le tri à deux piles (2/N)
# Complexité algorithmique du quick-sort (2/2)
## Exercice: formaliser l'algorithme
## Quelle est la complexité du tri rapide?
. . .
## Algorithme de tri nécessitant 2 piles (G, D)
* 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))$.
Soit `tab` le tableau à trier:
# Le problème des 8-reines
```C
Pour tous les i = 0 à N-1
\Huge Le problème des 8-reines
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
}
# Problème des 8-reines
empiler tab[i] sur G
dépiler tout D dans G
tab est trié dans G
```
* Placer 8 reines sur un échiquier de $8 \times 8$.
* Sans que les reines ne puissent se menacer mutuellement (92 solutions).
# Le tri à deux piles (3/N)
## Conséquence
## Exercice: trier le tableau `[2, 10, 5, 20, 15]`
* Deux reines ne partagent pas la même rangée, colonne, ou diagonale.
* Donc chaque solution a **une** reine **par colonne** ou **ligne**.
```C
## Généralisation
* Placer $N$ reines sur un échiquier de $N \times
N$.
- Exemple de **backtracking** (retour en arrière) $\Rightarrow$ récursivité.
![Problème des 8-reines. Source:
[wikipedia](https://fr.wikipedia.org/wiki/Problème_des_huit_dames)](./figs/fig_recursivite_8_reines.png){width=35%}
# Problème des 2-reines
![Le problème des 2 reines n'a pas de solution.](figs/2reines.svg){width=50%}
# Comment trouver les solutions?
* On pose la première reine sur la première case disponible.
* On rend inaccessibles toutes les cases menacées.
* On pose la reine suivante sur la prochaine case non-menacée.
* Jusqu'à ce qu'on puisse plus poser de reine.
* On revient alors en arrière jusqu'au dernier coup où il y avait plus qu'une
possibilité de poser une reine.
* On recommence depuis là.
. . .
* Le jeu prend fin quand on a énuméré *toutes* les possibilités de poser les
reines.
# Problème des 3-reines
![Le problème des 3 reines n'a pas de solution non plus.](figs/3reines.svg)
# Problème des 4-reines
![Le problème des 4 reines a une solution.](figs/4reines.svg)
# Problème des 4-reines, symétrie
![Le problème des 4 reines a une autre solution (symétrie
horizontale).](figs/4reines_sym.svg)
# Problème des 5 reines
```
## Exercice: Trouver une solution au problème des 5 reines
# La calculatrice (1/8)
## Vocabulaire
* Faire une capture d'écran / une photo de votre solution et la poster sur
matrix.
```C
2 + 3 = 2 3 +,
```
`2` et `3` sont les *opérandes*, `+` l'*opérateur*.
. . .
## La notation infixe
```C
2 * (3 + 2) - 4 = 6.
```
## La notation postfixe
```C
2 3 2 + * 4 - = 6.
```
## Exercice: écrire `2 * 3 * 4 + 2` en notation `postfixe`
. . .
```C
2 3 4 * * 2 + = (2 * (3 * 4)) + 2.
```
# La calculatrice (2/8)
## De infixe à post-fixe
* Une *pile* est utilisée pour stocker *opérateurs* et *parenthèses*.
* Les opérateurs on des *priorités* différentes.
```C
^ : priorité 3
* / : priorité 2
+ - : priorité 1
( ) : priorité 0 // pas un opérateur mais bon
```
# Quelques observations sur le problème
# La calculatrice (3/8)
## De infixe à post-fixe: algorithme
* 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.
* On lit l'expression infixe de gauche à droite.
# Le code du problème des 8 reines (1/5)
* On examine le prochain caractère de l'expression infixe.
* Si opérande, le placer dans l'expression du résultat.
* Si parenthèse le mettre dans la pile (priorité 0).
* Si opérateur, comparer sa priorité avec celui du sommet de la pile:
* Si sa priorité est plus élevée, empiler.
* Sinon dépiler l'opérateur de la pile dans l'expression du résultat et
recommencer jusqu'à apparition d'un opérateur de priorité plus faible
au sommet de la pile (ou pile vide).
* Si parenthèse fermée, dépiler les opérateurs du sommet de la pile et les
placer dans l'expression du résultat, jusqu'à ce qu'une parenthèse
ouverte apparaisse au sommet, dépiler également la parenthèse.
* Si il n'y a pas de caractère dans l'expression dépiler tous les
opérateurs dans le résultat.
## Quelle structure de données?
# La calculatrice (4/8)
. . .
## De infixe à post-fixe: exemple
Une matrice de booléens fera l'affaire:
```C
Infixe Postfixe Pile Priorité
((A*B)/D-F)/(G+H) Vide Vide Néant
(A*B)/D-F)/(G+H) Vide ( 0
A*B)/D-F)/(G+H) Vide (( 0
*B)/D-F)/(G+H) A (( 0
B)/D-F)/(G+H) A ((* 2
)/D-F)/(G+H) AB ((* 2
/D-F)/(G+H) AB* ( 0
D-F)/(G+H) AB* (/ 2
-F)/(G+H) AB*D (/ 2
F)/(G+H) AB*D/ (- 1
)/(G+H) AB*D/F (- 1
/(G+H) AB*D/F- Vide Néant
bool board[n][n];
```
# La calculatrice (5/8)
## Quelles fonctionnalités?
## De infixe à post-fixe: exemple
. . .
```C
Infixe Postfixe Pile Priorité
((A*B)/D-F)/(G+H) Vide Vide Néant
--------------------------------------------------------
/(G+H) AB*D/F- Vide Néant
(G+H) AB*D/F- / 2
G+H) AB*D/F- /( 0
+H) AB*D/F-G /( 0
H) AB*D/F-G /(+ 1
) AB*D/F-GH /(+ 1
Vide AB*D/F-GH+ / 2
Vide AB*D/F-GH+/ Vide Néant
// 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);
```
# La calculatrice (6/8)
# Le code du problème des 8 reines (2/5)
\footnotesize
## Le calcul du nombre de solutions
## Exercice: écrire le code et le poster sur matrix
```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
```
* Quelle est la signature de la fonction?
# Le code du problème des 8 reines (3/5)
. . .
## Le calcul du nombre de solutions
```C
char *infix_to_postfix(char* infix) { // init and alloc stack and postfix
for (size_t i = 0; i < strlen(infix); ++i) {
if (is_operand(infix[i])) {
// we just add operands in the new postfix string
} else if (infix[i] == '(') {
// we push opening parenthesis into the stack
stack_push(&s, infix[i]);
} else if (infix[i] == ')') {
// we pop everything into the postfix
} else if (is_operator(infix[i])) {
// this is an operator. We add it to the postfix based
// on the priority of what is already in the stack and push it
}
}
// pop all the operators from the s at the end of postfix
// and end the postfix with `\0`
return postfix;
}
// 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)
# La calculatrice (7/8)
## Compris? Alors écrivez le code et postez le!
## Évaluation d'expression postfixe: algorithme
* Chaque *opérateur* porte sur les deux opérandes qui le précèdent.
* Le *résultat d'une opération* est un nouvel *opérande* qui est remis au
sommet de la pile.
## Exemple
. . .
```C
2 3 4 + * 5 - = ?
```
## Le nombre de solutions
* On parcours de gauche à droite:
\footnotesize
```C
Caractère lu Pile opérandes
2 2
3 2, 3
4 2, 3, 4
+ 2, (3 + 4)
* 2 * 7
5 14, 5
- 14 - 5 = 9
// 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;
}
}
}
}
```
# La calculatrice (8/8)
## Évaluation d'expression postfixe: algorithme
# Le code du problème des 8 reines (5/5)
1. La valeur d'un opérande est *toujours* empilée.
2. L'opérateur s'applique *toujours* au 2 opérandes au sommet.
3. Le résultat est remis au sommet.
\footnotesize
## Exercice: écrire l'algorithme (et poster sur matrix)
## Placer devant
```C
bool evaluate(char *postfix, double *val) { // init stack
for (size_t i = 0; i < strlen(postfix); ++i) {
if (is_operand(postfix[i])) {
stack_push(&s, postfix[i]);
} else if (is_operator(postfix[i])) {
double rhs = stack_pop(&s);
double lhs = stack_pop(&s);
stack_push(&s, op(postfix[i], lhs, rhs);
} }
return stack_pop(&s);
// 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;
}
}
}
```
version: "3.3"
services:
slides:
#To use dockerfile : build: .
image: omalaspinas/pandoc:latest
environment:
USER: 1000
GROUP: 1000
user: 1000:1000
container_name: slides
volumes:
- ./:/data
# entrypoint: ["make", "all"]
entrypoint: ["make"]
working_dir: /data
# user: "$(id -u):$(id -g)"
#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;
}