Skip to content
Snippets Groups Projects

Compare revisions

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

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • algorithmique/cours
  • aurelien.boyer/cours
  • jeremy.meissner/cours
  • radhwan.hassine/cours
  • yassin.elhakoun/cours-algo
  • gaspard.legouic/cours
  • joachim.bach/cours
  • gabriel.marinoja/algo-cours
  • loic.lavorel/cours
  • iliya.saroukha/cours
  • costanti.volta/cours
  • jacquesw.ndoumben/cours
12 results
Select Git revision
Show changes
Showing
with 4423 additions and 3654 deletions
--- ---
title: "B-Arbres" title: "Théorie des graphes"
date: "2022-05-03" date: "2025-05-16"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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*; * Des sommets (numérotés 1 à 6);
* Chaque page (excepté la racine) contient au moins $n$ clés; * Connectés ou pas par des traits ou des flèches!
* Chaque page qui contient $m$ clés contient soit:
* $0$ descendants;
* $m+1$ descendants.
* Toutes les pages terminales apparaissent au même niveau.
# 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**. ## Remarques
* 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.
# 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. \begin{align*}
* Sinon parcourir (par bissection ou séquentiellement) jusqu'à trouver ou descendre entre 2 éléments. 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. ![Un graphe orienté.](figs/ex_graphe_oriente.svg)
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
# 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; # Généralités
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.
# 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 * Un arbre!
struct page
entier ordre, nb
element tab[2*ordre + 2]
```
```C # Représentations
struct element
int clé * La complexité des algorithmes sur les graphes s'expriment en fonction du nombre de sommets $V$, et du nombre d'arêtes $E$:
page pg * 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 \footnotesize
## Les fonctions utilitaires (5min matrix) ## Quelle matrice d'adjacence?
. . .
```C ```
booléen est_feuille(page) // la page est elle une feuille? || 1 | 2 | 3 | 4 | 5
entier position(page, valeur) // à quelle indice on insère? ===||===|===|===|===|===
booléen est_dans_page(page, valeur) // la valeur est dans la page 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) # Matrice d'adjacence
i = 0
tant que i < page.nb && val >= page.tab[i+1].clef ## Remarques
i += 1
retourne i * 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) A_{ij}=A_{ji}, \forall i,j\in[1,n]
retourne (page.nb > 0 && page.tab[i].clef == valeur) $$.
::: 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 \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) || 1 | 2 | 3 | 4 | 5
page = allouer(page) ===||===|===|===|===|===
page.ordre = ordre 1 || 0 | 0 | 0 | 1 | 0
page.nb = 0 ---||---|---|---|---|---
page.tab = allouer(2*ordre+2) 2 || 1 | 0 | 0 | 0 | 1
retourner page ---||---|---|---|---|---
3 || 0 | 0 | 0 | 0 | 0
rien liberer_memoire(page) ---||---|---|---|---|---
si est_feuille(page) 4 || 0 | 0 | 0 | 0 | 1
liberer(page.tab) ---||---|---|---|---|---
liberer(page) 5 || 0 | 1 | 1 | 0 | 0
sinon
pour fille dans page.tab
liberer_memoire(fille)
liberer(page.tab)
liberer(page)
``` ```
# Les B-arbres ::::
## Les fonctions (5min matrix) :::
```C * La matrice d'adjacence n'est plus forcément symétrique
page recherche(page, valeur) // retourner la page contenant $$
// la valeur ou vide A_{ij}\neq A_{ji}.
``` $$
# Stockage
* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe orienté?
. . . . . .
```C * $\mathcal{O}(|V|^2)$
page recherche(page, valeur) * Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe non-orienté?
si est_dans_page(page, valeur)
retourne page . . .
sinon si est_feuille(page)
retourne vide
sinon
recherche(page.tab[position(page, valeur)], valeur)
```
# Les B-arbres * $\mathcal{O}\left((|V|-1)|V|/2\right)$.
## Les fonctions # Considérations d'efficacité
```C * Dans quel type de graphes la matrice d'adjacence est-elle utile?
page inserer(page, valeur) // inserer une valeur
```
. . . . . .
```C * Dans les graphes denses.
page inserer(page, valeur) * Pourquoi?
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
```
# Les B-arbres . . .
## Les fonctions * Dans les graphes peu denses, la matrice d'adjacence est essentiellement composée de `0`.
```C ## Remarque
// inserer un element et voir s'il remonte
rien inserer_element(page, element) * 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 * Tableau de liste chaînée, vecteur (tableau dynamique), etc.
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)
```
# Les B-arbres
## Les fonctions (5min matrix) ::: columns
```C :::: column
rien placer(page, element) // inserer un élément
``` ## Exemple
![Un graphe non-orienté.](figs/ex_graph_adj.pdf){width=80%}
::::
:::: column
## Quelle liste d'adjacence?
. . . . . .
```C ![La liste d'adjacence.](figs/ex_graph_list_adj.pdf)
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
```
# 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) \mathcal{O}(|V|+|E|)
tmp = nouvelle_page(page.ordre) $$
tmp.tab[0].page = page
tmp.tab[1].clef = element.clef
tmp.tab[1].page = element.page
retourne tmp
```
# 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.... # Exemple
* Chaque page doit avoir au moins 2 éléments.
* On doit déplacer des éléments dans une autre feuille! Mais comment?
. . . ## É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? ![On commence en `x`.](figs/parcours_larg_1.pdf){width=50%}
* Remonter `7`, serait ok si racine, mais... c'est pas forcément.
* On redistribue les feuilles.
. . . ## É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: # Exemple
* Fusionner les feuilles et redistribuer, comment?
## É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 : ```C
* Fusionner le niveau 2 et redistribuer, comment? 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: ![](figs/parcours_larg_0.pdf){width=50%}
* 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.
# 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: * Établir la liste d'adjacence et appliquer l'algorithme de parcours en profondeur au graphe
* On échange la valeur avec la valeur de droite de la page de gauche
* On supprime comme pour une feuille!
## 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 --> * Quand on a un temps limité
<!-- typedef struct element { --> * BFS explore beaucoup de coups dans un futur proche;
<!-- int key; --> * DFS explore peu de coups dans un futur lointain.
<!-- struct _page *pg; -->
<!-- } element; -->
<!-- ``` -->
--- ---
title: "Graphes - Généralités" title: "Théorie des graphes et plus courts chemins"
date: "2022-05-03" date: "2025-05-26"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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. ```{.mermaid format=pdf width=400 loc=figs/}
* Chaque trait une relation d'amitié. graph LR;
* Miam, Miam, Facebook. 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. * BFS (Breadth-First) ou DFS (Depth-First) parcourent l'espace des états à la recherche du meilleur mouvement.
* Liens sortants; * Les deux parcourent *tout* l'espace;
* Liens entrants; * Mais si l'arbre est grand, l'espace est gigantesque!
* 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 * 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); # Contexte: les réseaux (informatique, transport, etc.)
* Connectés ou pas par des traits ou des flèches!
# 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é * Quel est le plus court chemin entre `s` et `t`?
* $V$: un ensemble de sommets;
* $E$: un ensemble d'arêtes.
* Une **arête** relie une **paire** de sommets de $V$.
## Remarques # Exemples d'application de plus courts chemins
* Il y a **au plus** une arête $E$ par paire de sommets de $V$. ## Devenir riches!
* 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.
# 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*} ## Problème
V&=\{1, 2, 3, 4\},\\
|V|&=4,\\
E&=\{(1,2),(2,3),(2,4),(4,1)\},\\
|E|&=4.
\end{align*}
:::: * 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$. * Soit `taux(u, v)` le taux de change entre la devise `u` et `v`.
* Les arêtes sont orientées dans un graphe orienté (étonnant non?). * 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*} * Un parcours en largeur!
V&=\{1, 2, 3, 4\},\\
|V|&=4,\\
E&=\{(1,2),(2,3),(2,4),(4,1),(4,2)\},\\
|E|&=5.
\end{align*}
:::: # 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$; # Algorithme de Dijkstra (1 à 5)
* Si un graphe non-orienté contient une arête $(u,v)$, $v$ est adjacent à $u$ et $u$ et adjacent à $v$.
## 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 ::: columns
:::: column :::: column
![Sommet $a$ adjacent à $c$, $c$ adjacent à $a$.](figs/ex_adj_non_or.svg){width=80%} ![Initialisation.](figs/dijkstra_0.png)
:::: ::::
:::: column :::: 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 :::: column
poids associé, habituellement donné par une fonction de pondération $w:E\rightarrow\mathbb{R}$.
## 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 ![2 visité, `D[3]=2`, `D[7]=3`.](figs/dijkstra_2.png)
$$
(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, 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 ::: columns
:::: column :::: 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 :::: 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 ::: columns
:::: column :::: 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 :::: 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 ## Idée générale
* 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**.
## 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! ```C
tab dijkstra(graph, s, t)
# Représentations 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$: # Algorithme de Dijkstra
* 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.
## 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\}$; # Algorithme de Dijkstra
* 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.
$$
\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; ```C
1---4; pile parcours(precedent, s, t)
2---5; sommets = vide
4---5; u = t
5---3; // 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?
. . . . . .
``` * On insère les éléments à haute priorité tout devant dans la file!
|| 1 | 2 | 3 | 4 | 5
===||===|===|===|===|=== # Les files de priorité
1 || 0 | 1 | 0 | 1 | 0
---||---|---|---|---|--- ## Trois fonction principales
2 || 1 | 0 | 0 | 0 | 1
---||---|---|---|---|--- ```C
3 || 0 | 0 | 0 | 0 | 1 booléen est_vide(element) // triviale
---||---|---|---|---|--- element enfiler(element, data, priorite)
4 || 1 | 0 | 0 | 0 | 1 data defiler(element)
---||---|---|---|---|--- rien changer_priorite(element, data, priorite)
5 || 0 | 1 | 1 | 1 | 0 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. ## Pseudo-implémentation: enfiler (2min)
* La matrice d'un graphe non-orienté est symétrique
$$ . . .
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/} ## Pseudo-implémentation: defiler (2min)
graph LR;
1---2; . . .
1---4;
2---5; ```C
4---5; data, element defiler(element)
5---3; 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 \footnotesize
``` ```C
|| 1 | 2 | 3 | 4 | 5 distance dijkstra(graphe, s, t)
===||===|===|===|===|=== --------------------O(V*V)--------------------------------
1 || 0 | 1 | 0 | 1 | 0 distance[s] = 0
---||---|---|---|---|--- fp = file_p_vide()
2 || 1 | 0 | 0 | 0 | 1 pour v dans sommets(graphe) // O(|V|)
---||---|---|---|---|--- si v != s
3 || 0 | 0 | 0 | 0 | 1 distance[v] = infini
---||---|---|---|---|--- fp = enfiler(fp, s, distance[s]) // O(|V|)
4 || 1 | 0 | 0 | 0 | 1 ------------------O(V * V)-------------------------------
---||---|---|---|---|--- tant que !est_vide(fp)
5 || 0 | 1 | 1 | 1 | 0 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/} ## A chaque étape donner:
graph LR;
2-->1;
1-->4;
2-->5;
5-->2;
4-->5;
5-->3;
```
:::: * 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)
``` # Algorithme de Dijkstra (corrigé)
|| 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
```
:::: ![Le corrigé partie 3.](figs/dijkstra_ex_2.png)
::: # Algorithme de Dijkstra (corrigé)
* La matrice d'adjacence n'est plus forcément symétrique ![Le corrigé partie 4.](figs/dijkstra_ex_3.png)
$$
A_{ij}\neq A_{ji}.
$$
# 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)$. # Limitation de l'algorithme de Dijkstra
* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe non-orienté?
## 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. * Appliquer Dijkstra sur tous les sommets d'origine.
* Pourquoi? * 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. # Algorithme de Floyd--Warshall
* Comment représenter un graphe autrement?
# La liste d'adjacence (non-orienté) ## Idée générale
* Pour chaque sommet $v\in V$, stocker les sommets adjacents à $v$- * Soit l'ensemble de sommets $V=\{1, 2, 3, 4, ..., n\}$.
* Quelle structure de données pour la liste d'adjacence? * 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 ::: columns
:::: column :::: column
## Exemple ![Le graphe, $D=w$.](figs/floyd_exemple.png)
![Un graphe non-orienté.](figs/ex_graph_adj.pdf){width=80%}
:::: ::::
:::: column :::: column
## Que vaut $D^{(0)}$ (3min)?
## Quelle liste d'adjacence?
. . . . . .
![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 ::: columns
:::: column :::: 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 :::: column
``` ## Que vaut $D^{(1)}$ (3min)?
. . .
$$
D^{(1)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & \mathbf{6} & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \mathbf{3} & \mathbf{5} & 0 & \mathbf{4} \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
::::
:::
# Algorithme de Floyd--Warshall (exemple)
::: columns
:::: column
## On part de $D^{(0)}$
``` $$
D^{(0)}=\begin{bmatrix}
0 & 2 & 4 & \infty & 3 \\
2 & 0 & 8 & \infty & 1 \\
6 & 2 & 0 & 4 & 3 \\
1 & \infty & \infty & 0 & 5 \\
\infty & \infty & \infty & 1 & 0 \\
\end{bmatrix}
$$
:::: ::::
::: :::: column
# Complexité
## Stockage
* 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|$. ## Exemple
* Pour les graphes *orientés*: $\mathcal{O}|E|$.
## 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: ## On part de $D^{(1)}$
* 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).
# 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 ::: columns
:::: column :::: 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 :::: column
## Quel arbre est créé par le parcours (2min)? ## Que vaut $D^{(5)}$ (3min)?
. . . . . .
```{.mermaid format=pdf width=400 loc=figs/} $$
graph LR; D^{(5)}=\begin{bmatrix}
0[x]-->1[w]; 0 & 2 & 4 & \mathbf{4} & 3 \\
0-->2[t]; 2 & 0 & 6 & \mathbf{2} & 1 \\
0-->3[y]; 4 & 2 & 0 & 4 & 3 \\
2-->9[u]; 1 & 3 & 5 & 0 & 4 \\
1-->4[s]; 2 & 4 & 6 & 1 & 0 \\
4-->5[r]; \end{bmatrix}
5-->6[v]; $$
```
:::: ::::
::: :::
## 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. # Algorithme de Floyd--Warshall
* L'arbre sera différent en fonction du noeud de départ, et de l'ordre de parcours des voisins d'un noeud.
# 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 ```C
v = un sommet du graphe matrice ini_floyd_warshall(distance, n, w)
i = 1 pour i de 1 à n
pour sommet dans graphe et sommet non-visité pour j de 1 à n
visiter(v, sommet, i) // marquer sommet à distance i visité distance[i][j] = w(i,j)
i += 1 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 * Moralité: si le chemin est plus court en passant par $k$, alors il faut utiliser son prédécesseur!
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)
```
## Que fait visiter? # Algorithme de Floyd--Warshall
``` ## La matrice de précédence (pseudo-code, 3min)
file visiter(sommet, file)
sommet = visité
pour w = chaque arête de sommet
si w != visité
file = enfiler(file, w)
retourne file
```
# Exercice (5min)
## 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). # Algorithme de Floyd--Warshall (exercice)
* Bien mettre à chaque étape l'état de la file.
# 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|)$, ## Que vaut $P^{(0)}$ (3min)?
* É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 $$
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 ![Le graphe, $D=w$.](figs/floyd_exemple.png)
* Visiter un sommet
* Pour chaque sommet visité, on visite un sommet adjacent s'il est pas encore visité récursivement.
## Remarque
* La récursivité est équivalent à l'utilisation d'une **pile**. ::::
# Parcours en profondeur :::: column
## Pseudo-code (5min) ## Que vaut $P^{(5)}$ (10min)?
. . . . . .
```C $$
initialiser(graphe) // tous sommets sont non-visités P^{(5)}=\begin{bmatrix}
pile = visiter(sommet, vide) // sommet est un sommet du graphe au hasard - & 1 & 1 & 5 & 1 \\
tant que !est_vide(pile) 2 & - & 1 & 5 & 2 \\
v = dépiler(pile) 2 & 3 & - & 3 & 3 \\
pile = visiter(v, pile) 4 & 1 & 1 & - & 1 \\
``` 4 & 1 & 1 & 5 & - \\
\end{bmatrix}
## Que fait visiter? $$
. . . ::::
```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/} ## Solution
graph LR;
1---2;
1---3;
1---4;
2---3;
2---6;
3---6;
3---4;
3---5;
4---5;
```
# 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); # Exercice complet
* Labyrinthe;
* Arbre des coups d'un jeu.
. . . ## 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. ![The exorcist.](figs/floyd_exercice.png){width=50%}
* Les deux parcourent *tout* l'espace;
* Mais si l'arbre est grand, l'espace est gigantesque!
. . . * 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" title: "Arbres couvrants"
date: "2022-05-03" date: "2025-06-06"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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. ![Ces maisons n'ont pas d'électricité.](figs/arbre_couvrant_vide.png)
* Comment représenter un graphe informatiquement?
. . . # Solution: pas optimale
* Liste ou matrice d'adjacence. ![Le réseau simple, mais nul.](figs/arbre_couvrant_mal.png)
* Quels sont les deux parcours que nous avons vus?
. . . * La longueur totale des câbles est super longue!
# Solution: optimale
* Parcours en largeur et profondeur. ![Le meilleur réseau.](figs/arbre_couvrant_bien.png)
* Donner l'idée générale des deux parcours.
# 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. # Formalisation: Les arbres couvrants
* Quelle est la meilleure façon de convertir l'or en dollar?
![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 * Un arbre couvrant d'un graphe non-orienté et connexe est:
* 1kg d'or => 208.1 livres => 327 dollars * un arbre inclus dans le graphe qui connecte tous les sommets du graphe.
* 1kg d'or => 455.2 francs => 304.39 euros => 327.28 dollars
# Exemples d'application de plus court chemin . . .
## Formulation sous forme d'un graphe: Comment (3min)? ![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; * Un *arbre couvrant minimal* est un sous-graphe d'un graphe non-orienté pondéré $G(V,E)$ tel quel:
* Une arête orientée par transaction possible avec le poids égal au taux de change; * C'est un arbre (graphe acyclique);
* Trouver le chemin qui maximise le produit des poids. * 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`. ![Un graphe, connexe, non-orienté, pondéré, et un arbre couvrant minimal.](figs/arbre_couvrant_minimal_exemple.png)
* On pose `w(u,w)=-log(taux(u,v))`
* Trouver le chemin poids minimal pour les poids `w`.
![Graphe des taux de change avec logs.](figs/taux_change_graphe_log.pdf){width=60%} # Algorithme de Prim
* Cette conversion se base sur l'idée que ::: columns
$$ :::: column
\log(u\cdot v)=\log(u)+\log(v).
$$
# 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; :::: column
* Planificaiton de trajet / trafic urbain;
* Routage de télécommunications;
* Réseau électrique optimal;
* ...
# 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$ ![Le sommet `e` est couvert.](figs/prim_1.png)
* Trouver pour tout sommet $v\in V$, le chemin de poids minimal reliant $s$ à $v$.
* Algorithmes standards:
* Dijkstra (arêtes de poids positif seulement);
* Bellman-Ford (arêtes de poids positifs ou négatifs, mais sans cycles).
* Comment résoudre le problèmes si tous les poids sont les mêmes?
. . . ::::
* Un parcours en largeur! :::
# Algorithme de Dijkstra # Algorithme de Prim
## Comment chercher pour un plus court chemin? ::: columns
. . . :::: column
``` ## On choisit comment?
si distance(u,v) > distance(u,w) + distance(w,v)
on passe par w plutôt qu'aller directement
```
# 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. * L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité.
* Tous les noeuds sont marqués non-visités.
* Depuis du noeud courant, on suit chaque arête du noeud vers un sommet non visité et on calcule le poids du chemin à chaque voisin et on met à jour sa distance si elle est plus petite que la distance du noeud.
* Quand tous les voisins du noeud courant ont été visités, le noeud est mis à visité (il ne sera plus jamais visité).
* Continuer avec le noeud à la distance la plus faible.
* L'algorithme est terminé losrque le noeud de destination est marqué comme visité, ou qu'on a plus de noeuds qu'on peut visiter et que leur distance est infinie.
# Algorithme de Dijkstra ::::
## Pseudo-code (5min, matrix) :::: column
. . . . . .
```C ## L'arête `e->d`
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
```
# 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! ::: columns
* On a besoin d'un tableau `précédent`.
## Modifier le pseudo-code ci-dessus pour ce faire (3min matrix) :::: column
# Algorithme de Dijkstra ## On choisit comment?
```C ![Quelle arête choisir?](figs/prim_2.png)
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
```
# 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 ## L'arête `d->a`
pile parcours(précédent, s, t)
sommets = vide ![Le sommet `a` est couvert.](figs/prim_3.png)
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
```
# Algorithme de Dijkstra ::::
:::
# Algorithme de Prim
::: columns ::: columns
:::: column :::: 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) ...@@ -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 ::: columns
:::: column :::: 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) ...@@ -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 ::: columns
:::: column :::: {.column width="40%"}
![Plus court est 3.](figs/dijkstra_2.png) ## Un exemple
![Étape 1.](figs/prim_1.png)
:::: ::::
:::: column :::: 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 ::: columns
:::: column :::: {.column width="40%"}
![Plus court est 4 ou 7.](figs/dijkstra_3.png) ## Un exemple
![Étape 2.](figs/prim_2.png)
:::: ::::
:::: column :::: 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 ::: columns
:::: column :::: {.column width="40%"}
![Plus court est `7`.](figs/dijkstra_4.png) ## Un exemple
![Étape 3.](figs/prim_3.png)
:::: ::::
:::: column :::: 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 ::: columns
:::: column :::: {.column width="40%"}
![Plus court est 6.](figs/dijkstra_5.png) ## Un exemple
![Étape 4.](figs/prim_4.png)
:::: ::::
:::: column :::: 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 ::: 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 :::: 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 # Algorithme de Prim
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
```
## Pseudo-implémentation: structure (1min) ## Initialisation: Pseudo-code (2min)
. . . . . .
```C ```C
struct élément file_priorité, distance, parent initialisation(graphe)
data s_initial = aléatoire(graphe)
priorité distance[s_initial] = 0
élément suivant 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 ```C
élément enfiler(élément, data, priorité) distance, parent prim(graphe)
n_élément = créer_élément(data, priorité) fp, distance, parent initialisation(graphe)
si est_vide(élément) sommets = vide
retourne n_élément tant que !est_vide(fp)
si priorité(d) > priorité(e.d) s_courant, fp = défiler(fp)
n_élément.suivant = élément sommets = insérer(sommets, s_courant)
retourne n_élément pour s_voisin dans voisinage(s_courant) et pas dans sommets
sinon // ou dans fp
tmp = élément si poids(s_courant, s_voisin) < distance[s_voisin]
préc = élément parent[s_voisin] = s_courant
tant que !est_vide(tmp) && priorité < tmp.priorité distance[s_voisin] = poids(s_courant, s_voisin)
préc = tmp fp = changer_priorité(fp, s_voisin,
tmp = tmp.suivant poids(s_courant, s_voisin))
prev.suivant = n_élément retourne distance, parent
n_élément.suivant = tmp
retourne élément
``` ```
# 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 # Exercice: algorithme de Prim
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
```
# Algorithme de Dijkstra avec file de priorité min ## Solution
```C ![](figs/prim_solution.png)
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(i, 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
```
# Algorithme de Dijkstra avec file # Complexité de l'algorithme de Prim
\footnotesize \footnotesize
```C ```C
distance dijkstra(graphe, s, t) file_priorité, distance, parent initialisation(graphe)
--------------------------------------------------------- // choix r et initialisation
pour v dans sommets(graphe) pour v dans sommets(graphe)
O(V) si v != s // initialisation distance et parent en O(|V|)
distance[v] = infini fp = enfiler(fp, v, distance[v])
O(V) fp = enfiler(fp, v, distance[v]) // notre impl est nulle retourne fp, distance, parent
------------------O(V * V)------------------------------- distance, parent prim(graphe)
fp, distance, parent initialisation(graphe) // O(|V|)
sommets = vide
tant que !est_vide(fp) tant que !est_vide(fp)
O(1) u, fp = défiler(fp) u, fp = défiler(fp) // O(|V|)
--------------------------------------------------------- sommets = insérer(sommets, u)
O(E) pour v dans voisinage de u pour v dans voisinage de u et pas dans sommets
n_distance = distance[u] + w(i, v) si poids(u, v) < distance[v] // O(|E|)
si n_distance < distance[v] // màj distance + parent
distance[v] = n_distance fp = changer_priorité(fp, v, poids(u, v)) // O(|V|)
O(V) fp = changer_priorité(fp, v, n_distance) retourne distance, parent
---------------------------------------------------------
retourne distance
``` ```
* Total: $\mathcal{O}(|V|^2+|E|\cdot |V|)$: * $O(|V|)+O(|E|)+O(|V|^2)=O(|E|+|V|^2)$
* Graphe dense: $\mathcal{O}(|V|^3)$ * Remarque: $O(|E|)$ n'est pas mutliplié par $O(|V|)$, car les arêtes ne sont traitées qu'une fois en **tout**.
* Graphe peu dense: $\mathcal{O}(|V|^2)$
# 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é: ![Les sommets `a, d, c, e` sont couverts.](figs/kruskal_3.png)
* Tas binaire: $\mathcal{O}(|V|\log|V|+|E|\log|V|)$.
* Tas de Fibonnacci: $\mathcal{O}(|V|+|E|\log|V|)$
* Graphe dense: $\mathcal{O}(|V|^2\log|V|)$.
* Graphe peu dense: $\mathcal{O}(|V|\log|V|)$.
# Algorithme de Dijkstra (exercice, 5min) ::::
![L'exercice.](figs/dijkstra_exo.png){width=60%} :::
* Donner la liste de priorité, puis... # Algorithme de Kruskal: exemple
## A chaque étape donner: ::: columns
* Le tableau des distances à `a`; :::: column
* Le tableau des prédécessueurs;
* L'état de la file de priorité. ## 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`. * Si les deux sommets sont déjà couverts nous sommes sauvés (presque)!
* Ce problème n'apparaît que s'il y a des poids négatifs.
# Algorithme de Kruskal
## L'initialisation
* Créer un ensemble de sommets pour chaque de sommet du graphe ($V_1$, $V_2$, ...):
* $V_1=\{v_1\}$, $V_2=\{v_2\}$, ...
* S'il y a $n$ sommets, il y a $n$ $V_i$.
* Initialiser l'ensemble $A$ des arêtes "sûres" constituant l'arbre couvrant minimal, $A=\emptyset$.
* Initialiser l'ensemble des sommets couverts $F=\emptyset$.
* Trier les arêtes par poids croissant dans l'ensemble $E$.
## Mise à jour
* Tant qu'il reste plus d'un $V_i$:
* Pour $(u,v)\in E$ à poids minimal:
* Retirer $(u,v)$ de $E$.
* Si $u\in V_i$ et $v\in V_j$ avec $V_i\cap V_j=\emptyset$:
* Ajouter $(u,v)$ à $A$;
* Fusionner $V_i$ et $V_j$ dans $F$.
# Algorithme de Kruskal: exemple
::: columns
:::: column
![Couvrir cet arbre bon sang!](figs/kruskal_enonce.png)
::::
:::: column
::::
:::
# Algorithme de Kruskal: solution
![La solution!](figs/kruskal_solution.png)
# Algorithme de Kruskal: exercice
::: columns
:::: column
![Couvrir cet arbre bon sang!](figs/kruskal_exercice.png)
::::
:::: column
::::
:::
# Algorithme de Kruskal: solution
![La solution!](figs/kruskal_solution_exercice.png)
--- ---
title: "Introduction aux algorithmes" title: "Introduction aux algorithmes III"
date: "2021-10-06" date: "2024-09-30"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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 * Remplissage d'un tableau et recherche de la valeur minimal
* Anagrammes * Anagrammes
* Palindromes * Palindromes
* Crible d'ératosthène * Crible d’Ératosthène
. . .
* Ces algorithme nécessitent d'utiliser des **tableaux**.
# Collections: tableaux statiques # Collections: tableaux statiques
\footnotesize
* Objets de même type: leur nombre est **connu à la compilation**; * 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 ```C
#define SIZE 10 #define SIZE 10
...@@ -51,8 +221,7 @@ patat: ...@@ -51,8 +221,7 @@ patat:
# Remarques # Remarques
* Depuis `C99` possibilité d'avoir des tableaux dont la taille est *inconnue à * Depuis `C99` la taille peut être *inconnue à la compilation* (VLA);
la compilation*;
```C ```C
int size; int size;
...@@ -89,6 +258,7 @@ for (int i = 0; i < SIZE; ++i) { ...@@ -89,6 +258,7 @@ for (int i = 0; i < SIZE; ++i) {
tab[i] = rand() / (double)RAND_MAX * 10.0 - 5.0; tab[i] = rand() / (double)RAND_MAX * 10.0 - 5.0;
// tab[i] contient un double dans [-5;5] // tab[i] contient un double dans [-5;5]
} }
int other_tab[4] = {0}; // pareil que {0, 0, 0, 0}
``` ```
# Recherche du minimum dans un tableau (1/2) # Recherche du minimum dans un tableau (1/2)
...@@ -104,12 +274,10 @@ Trouver la valeur minimale contenue dans un tableau et l'indice de l'élément l ...@@ -104,12 +274,10 @@ Trouver la valeur minimale contenue dans un tableau et l'indice de l'élément l
```C ```C
index = 0 index = 0
min = tab[0] min = tab[0]
for i in [1; SIZE] { pour i de 1 à SIZE - 1
if min > tab[i] { si min > tab[i]
min = tab[i] min = tab[i]
index = i index = i
}
}
``` ```
# Recherche du minimum dans un tableau (2/2) # Recherche du minimum dans un tableau (2/2)
...@@ -122,7 +290,7 @@ for i in [1; SIZE] { ...@@ -122,7 +290,7 @@ for i in [1; SIZE] {
int index = 0; int index = 0;
float min = tab[0]; float min = tab[0];
for (int i = 1; i < SIZE; ++i) { for (int i = 1; i < SIZE; ++i) {
if min > tab[i] { if (min > tab[i]) {
min = tab[i]; min = tab[i];
index = i; index = i;
} }
...@@ -139,201 +307,11 @@ Trier un tableau par ordre croissant. ...@@ -139,201 +307,11 @@ Trier un tableau par ordre croissant.
```C ```C
ind = 0 ind = 0
boucle (ind < SIZE-1) { tant que (ind < SIZE-1)
Trouver le minimum du tableau, tab_min[ind:SIZE]. Trouver le minimum du tableau, tab_min = min([ind:SIZE]).
Échanger tab_min avec tab[ind] Échanger tab_min avec tab[ind]
ind += 1 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" title: "Introduction aux algorithmes IV"
date: "2021-10-13" date: "2024-10-07"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# Crible d'Ératosthène: solution # Tri par sélection
\footnotesize ## Quel est l'algorithme du tri par sélection?
```C
#include <stdio.h>
#include <stdbool.h>
#define SIZE 51
int main() {
bool tab[SIZE];
for (int i=0;i<SIZE;i++) {
tab[i] = true;
}
for (int i = 2; i < SIZE; i++) {
if (tab[i]) {
printf("%d ", i);
int j = i;
while (j < SIZE) {
j += i;
tab[j] = false;
}
}
}
printf("\n");
}
```
# Réusinage de code (refactoring)
## Le réusinage est?
. . . . . .
* le processus de restructuration d'un programme: 1. Soit un tableau d'entiers, `tab[0:SIZE-1]` et `i = 0`.
* en modifiant son design, 2. Trouver l'indice, `j`, de `tab[i:SIZE-1]` où la valeur est minimale.
* en modifiant sa structure, 3. Échanger `tab[i]` et `tab[j]`.
* en modifiant ses algorithmes 4. `i += 1` et revenir à 2, tant que `i < SIZE-1`.
* mais en **conservant ses fonctionalités**.
. . . # 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é, # Un type de tableau particulier
* Amélioration de la maintenabilité,
* Réduction de la complexité.
. . . ## Les chaînes de caractères
## "Make it work, make it nice, make it fast", Kent Beck.
. . .
## Exercice:
* Réusiner le code se trouvant sur
[Cyberlearn](https://cyberlearn.hes-so.ch/mod/resource/view.php?id=1627712).
# Tableau à deux dimensions (1/4)
## Mais qu'est-ce donc?
. . .
* Un tableau où chaque cellule est un tableau.
## Quels cas d'utilisation?
. . .
* Tableau à double entrée;
* Image;
* Écran (pixels);
* Matrice (mathématique);
# Tableau à deux dimensions (2/4)
## Exemple: tableau à 3 lignes et 4 colonnes d'entiers
+-----------+-----+-----+-----+-----+
| `indices` | `0` | `1` | `2` | `3` |
+-----------+-----+-----+-----+-----+
| `0` | `7` | `4` | `7` | `3` |
+-----------+-----+-----+-----+-----+
| `1` | `2` | `2` | `9` | `2` |
+-----------+-----+-----+-----+-----+
| `2` | `4` | `8` | `8` | `9` |
+-----------+-----+-----+-----+-----+
## Syntaxe
```C ```C
int tab[3][4]; // déclaration d'un tableau 4x3 string = tableau + char + magie noire
tab[2][1]; // accès à la case 2, 1
tab[2][1] = 14; // assignation de 14 à la position 2, 1
``` ```
# Tableau à deux dimensions (3/4) # 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 ```C
#define NX 50 char c = 'A';
#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
}
}
``` ```
- Est équivalent à:
## Exercice: afficher le tableau
. . .
```C ```C
for (int i = 0; i < NX; ++i) { char c = 65;
for (int j = 0; j < NY; ++j) {
printf("%d ", tab[i][j]);
}
printf("\n");
}
``` ```
- 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. ## Exemple
* Les bornes ne sont **jamais** vérifiées.
```C ```C
int tab[3][2] = { {1, 2}, {3, 4}, {5, 6} }; char *str = "HELLO !";
printf("%d\n", tab[2][1]); // affiche? char str[] = "HELLO !";
printf("%d\n", tab[10][9]); // affiche?
printf("%d\n", tab[3][1]); // affiche?
``` ```
# La couverture de la reine Est représenté par
* Aux échecs la reine peut se déplacer horizontalement et verticalement
* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous
+-----+-----+-----+-----+-----+-----+-----+
| ` ` | `0` | `1` | `2` | `3` | `4` | `5` |
+-----+-----+-----+-----+-----+-----+-----+
| `0` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `1` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `2` | `*` | `*` | `R` | `*` | `*` | `*` |
+-----+-----+-----+-----+-----+-----+-----+
| `3` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `4` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
## Exercice | `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). Permet de connaître la fin de la chaîne de caractères (pas le cas des autres
* En `C` les variantes sont des entiers numérotés à partir de 0. sortes de tableaux).
```C # Syntaxe
enum days {
monday, tuesday, wednesday,
thursday, friday, saturday, sunday
};
```
* On peut aussi donner des valeurs "custom"
```C ```C
enum days { char name[5];
monday = 2, tuesday = 8, wednesday = -2, name[0] = 'P'; // = 70;
thursday = 1, friday = 3, saturday = 12, sunday = 9 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 # Fonctions
enum days d = monday;
(d + 2) == tuesday + tuesday; // true
```
# 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 ```C
enum days d = monday; // longueur de la chaîne (sans le \0)
switch (d) { size_t strlen(char *str);
case monday: // copie jusqu'à un \0
// trucs char *strcpy(char *dest, const char *src);
break; // copie len char
case tuesday: char *strncpy(char *dest, const char *src, size_t len);
printf("0 ou 1\n"); // compare len chars
break; 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) - Pour avoir la liste complète: `man 3 string`.
* 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
$$
. . . . . .
$$ ## Quel problème peut se produire avec `strlen`, `strcpy`, `strcmp`?
= 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 - Si `\0` est absent... on a un comportement indéfini.
``` # Les anagrammes
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
$$ ## Définition
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) 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 ## Il suffit de:
num = 247
while (2^N < num) {
N += 1
}
```
. . . 1. Trier les deux mots.
2. Vérifier s'ils contiennent les mêmes lettres.
2. Boucle ## Implémentation ensemble
```C ```C
while (N >= 0) { int main() { // pseudo C
bit = num / 2^N tri(mot1);
num = num % 2^N tri(mot2);
N += 1 if egalite(mot1, mot2) {
// anagrammes
} else {
// pas anagrammes
}
} }
``` ```
# Les additions en binaire # Les palindromes
Que donne l'addition `1101` avec `0110`?
* L'addition est la même que dans le système décimal
```
1101 8+4+0+1 = 13
+ 0110 + 0+4+2+0 = 6
------- -----------------
10011 16+0+0+2+1 = 19
```
* Les entiers sur un ordinateur ont une précision **fixée** (ici 4 bits).
* Que se passe-t-il donc ici?
. . .
## Dépassement de capacité: le nombre est "tronqué"
* `10011 (19) -> 0011 (3)`.
* On fait "le tour"."
# Entier non-signés minimal/maximal
* Quel est l'entier non-signé maximal représentable avec 4 bit?
. . .
$$
(1111)_2 = 8+4+2+1 = 15
$$
* Quel est l'entier non-signé minimal représentable avec 4 bit?
. . .
$$
(0000)_2 = 0+0+0+0 = 0
$$
* Quel est l'entier non-signé min/max représentable avec N bit?
. . .
$$
0\mbox{ et }2^N-1.
$$
* Donc `uint32_t?` maximal est?
. . .
$$
4294967295
$$
# Les multiplications en binaire (1/2)
Que donne la multiplication de `1101` avec `0110`?
* L'addition est la même que dans le système décimal
```
1101 13
* 0110 * 6
--------- --------------
0000 78
11010
110100
+ 0000000
--------- --------------
1001110 64+0+0+8+4+2+0
```
# Les multiplications en binaire (2/2)
## Que fait la multiplication par 2?
. . .
* Décalage de un bit vers la gauche!
```
0110
* 0010
---------
0000
+ 01100
---------
01100
```
. . .
## Que fait la multiplication par $2^N$?
. . .
* Décalade de $N$ bits vers la gauche!
# Entiers signés (1/2)
Pas de nombres négatifs encore...
## Comment faire?
. . .
## Solution naïve:
* On ajoute un bit de signe (le bit de poids fort):
```
00000010: +2
10000010: -2
```
## Problèmes?
. . .
* Il y a deux zéros (pas trop grave): `10000000` et `00000000`
* Les additions différentes que pour les non-signés (très grave)
```
00000010 2
+ 10000100 + -4
---------- ----
10000110 = -6 != -2
```
# Entiers signés (2/2)
## Beaucoup mieux
* Complément à un:
* on inverse tous les bits: `1001 => 0110`.
## Encore un peu mieux
* Complément à deux:
* on inverse tous les bits,
* on ajoute 1 (on ignore les dépassements).
. . .
* Comment écrit-on `-4` en 8 bits?
. . .
```
4 = 00000100
________
-4 => 00000100
11111011
+ 00000001
----------
11111100
```
# Le complément à 2 (1/2)
## Questions:
* Comment on écrit `+0` et `-0`?
* Comment calcule-t-on `2 + (-4)`?
* Quel est le complément à 2 de `1000 0000`?
. . .
## Réponses
* Comment on écrit `+0` et `-0`?
```
+0 = 00000000
-0 = 11111111 + 00000001 = 100000000 => 00000000
```
* Comment calcule-t-on `2 + (-4)`?
```
00000010 2
+ 11111100 + -4
---------- -----
11111110 -2
```
* En effet
```
11111110 => 00000001 + 00000001 = 00000010 = 2.
```
# Le complément à 2 (1/2)
## Quels sont les entiers représentables en 8 bits? Mot qui se lit pareil de droite à gauche que de gauche à droite:
. . . . . .
``` * rotor, kayak, ressasser, ...
01111111 => 127
10000000 => -128 // par définition
```
## Quels sont les entiers représentables sur $N$ bits? ## Problème: proposer un algorithme pour détecter un palindrome
. . . . . .
$$ ## Solution 1
-2^{N-1} ... 2^{N-1}-1.
$$
## Remarque: dépassement de capacité en `C`
* Comportement indéfini!
<!-- # TODO --
<!-- ## Entiers, entiers non-signés -->
<!-- ## Complément à 1, 2 -->
<!-- ## Nombres à virgule flottante, simple/double précision -->
# Types composés: `struct`{.C} (1/6)
## Fractions
* Numérateur: `int num`;
* Dénominateur: `int denom`.
## Addition
```C
int num1 = 1, denom1 = 2;
int num2 = 1, denom2 = 3;
int num3 = num1 * denom2 + num2 * denom1;
int denom3 = denom1 * denom2;
```
## Pas super pratique....
# Types composés: `struct`{.C} (2/6)
## On peut faire mieux
* Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}.
```C ```C
struct fraction { // déclaration du type while (first_index < last_index) {
int32_t num, denom; if (mot[first_index] != mot [last_index]) {
return false;
} }
first_index += 1;
struct fraction frac; // déclaration de frac last_index -= 1;
``` }
return true;
# Types composés: `struct`{.C} (3/6)
## Simplifications
- `typedef`{.C} permet de définir un nouveau type.
```C
typedef unsinged int uint;
typedef struct fraction fraction_t;
typedef struct fraction {
int32_t num, denom;
} fraction_t;
```
- L'initialisation peut aussi se faire avec
```C
fraction_t frac = {1, -2}; // num = 1, denom = -2
fraction_t frac = {.denom = 1, .num = -2};
fraction_t frac = {.denom = 1}; // argl! .num non initialisé
fraction_t frac2 = frac; // copie
``` ```
# Types composés: `struct`{.C} (4/6) . . .
## Pointeurs
- Comme pour tout type, on peut avoir des pointeurs vers un `struct`{.C}. ## Solution 2
- Les champs sont accessible avec le sélecteur `->`{.C}
```C ```C
fraction_t *frac; // on crée un pointeur mot_tmp = revert(mot);
frac->num = 1; // seg fault... return mot == mot_tmp;
frac->denom = -1; // mémoire pas allouée.
``` ```
![La représentation mémoire de # Crible d'Ératosthène
`fraction_t`.](figs/pointer_struct.svg){width=50%}
# Types composés: `struct`{.C} (5/6)
## Initialisation
- Avec le passage par **référence** on peut modifier un struct *en place*. Algorithme de génération de nombres premiers.
- Les champs sont accessible avec le sélecteur `->`{.C}
```C ## Exercice
void fraction_init(fraction_t *frac,
int32_t re, int32_t im)
{
// frac a déjà été allouée
frac->num = frac;
frac->denom = denom;
}
int main() {
fraction_t frac; // on alloue une fraction
fraction_init(&frac, 2, -1); // on l'initialise
}
```
# Types composés: `struct`{.C} (6/6) * À 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. * Par groupe de trois, réfléchir à un algorithme.
* La valeur retournée peut être copiée dans une nouvelle structure.
```C ## Programme en C
fraction_t fraction_create(int32_t re, int32_t im) {
fraction_t frac;
frac.num = re;
frac.denom = im;
return frac;
}
int main() {
// on crée une fraction et on l'initialise
// en copiant la fraction créé par fraction_create
// deux allocation et une copie
fraction_t frac = fraction_create(2, -1);
}
```
<!-- # TODO jusqu'aux vacances --> * 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é" title: "Tableaux à deux dimensions et récursivité"
date: "2021-10-20" date: "2024-10-14"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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 \footnotesize
+--------+--------+--------+
| $10^2$ | $10^1$ | $10^0$ |
+--------+--------+--------+
| `2` | `4` | `7` |
+--------+--------+--------+
$$
247 = 2\cdot 10^2 + 4\cdot 10^1 + 7\cdot 10^0.
$$
# Représentation des nombres (2/2)
* Le nombre `11110111`.
## Nombres binaires: Les nombres en base 2
+-------+-------+-------+-------+-------+-------+-------+-------+
| $2^7$ | $2^6$ | $2^5$ | $2^4$ | $2^3$ | $2^2$ | $2^1$ | $2^0$ |
+-------+-------+-------+-------+-------+-------+-------+-------+
| `1` | `1` | `1` | `1` | `0` | `1` | `1` | `1` |
+-------+-------+-------+-------+-------+-------+-------+-------+
$$
1\cdot 2^7 + 1\cdot 2^6 +1\cdot 2^5 +1\cdot 2^4 +0\cdot 2^3 +1\cdot 2^2
+1\cdot 2^1 +1\cdot 2^0
$$
. . .
$$
= 247.
$$
# Conversion de décimal à binaire (1/2)
## Convertir 11 en binaire?
. . .
* On décompose en puissances de 2 en partant de la plus grande possible
```
11 / 8 = 1, 11 % 8 = 3
3 / 4 = 0, 3 % 4 = 3
3 / 2 = 1, 3 % 2 = 1
1 / 1 = 1, 1 % 1 = 0
```
* On a donc
$$
1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot
2^0=11.
$$
# Conversion de décimal à binaire (2/2)
## Convertir un nombre arbitraire en binaire: 247?
* Par groupe établir un algorithme.
. . .
## Algorithme
1. Initialisation
```C ```C
num = 247 #include <stdio.h>
while (2^N < num) { #include <stdbool.h>
N += 1 #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;
2. Boucle while (j < SIZE) {
j += i;
```C tab[j] = false;
while (N >= 0) { }
bit = num / 2^N }
num = num % 2^N }
N += 1 printf("\n");
} }
``` ```
# Les additions en binaire # Réusinage de code (refactoring)
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? ## Le réusinage est?
. . . . . .
* Décalage de un bit vers la gauche! * le processus de restructuration d'un programme:
* en modifiant son design,
``` * en modifiant sa structure,
0110 * en modifiant ses algorithmes
* 0010 * mais en **conservant ses fonctionalités**.
---------
0000
+ 01100
---------
01100
```
. . . . . .
## Que fait la multiplication par $2^N$? ## Avantages?
. . . . . .
* Décalage de $N$ bits vers la gauche! * Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
# Entiers signés (1/2) * Réduction de la complexité.
Pas de nombres négatifs encore...
## Comment faire?
. . . . . .
## Solution naïve: ## "Make it work, make it nice, make it fast", Kent Beck.
* 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` ## Exercice:
* Les additions différentes que pour les non-signés (très grave)
``` * Réusiner le code se trouvant sur
00000010 2 [Cyberlearn](https://cyberlearn.hes-so.ch/pluginfile.php/703384/mod_resource/content/1/comprendre.c).
+ 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 # Tableau à deux dimensions (1/4)
* Complément à deux: ## Mais qu'est-ce donc?
* on inverse tous les bits,
* on ajoute 1 (on ignore les dépassements).
. . . . . .
* Comment écrit-on `-4` en 8 bits? * Un tableau où chaque cellule est un tableau.
. . .
```
4 = 00000100
________
-4 => 00000100
11111011
+ 00000001
----------
11111100
```
# Le complément à 2 (1/2)
## Questions: ## Quels cas d'utilisation?
* Comment on écrit `+0` et `-0`?
* Comment calcule-t-on `2 + (-4)`?
* Quel est le complément à 2 de `1000 0000`?
. . . . . .
## Réponses * Tableau à double entrée;
* Image;
* Écran (pixels);
* Matrice (mathématique);
* Comment on écrit `+0` et `-0`? # Tableau à deux dimensions (2/4)
``` ## Exemple: tableau à 3 lignes et 4 colonnes d'entiers
+0 = 00000000
-0 = 11111111 + 00000001 = 100000000 => 00000000
```
* Comment calcule-t-on `2 + (-4)`?
``` +-----------+-----+-----+-----+-----+
00000010 2 | `indices` | `0` | `1` | `2` | `3` |
+ 11111100 + -4 +-----------+-----+-----+-----+-----+
---------- ----- | `0` | `7` | `4` | `7` | `3` |
11111110 -2 +-----------+-----+-----+-----+-----+
``` | `1` | `2` | `2` | `9` | `2` |
* En effet +-----------+-----+-----+-----+-----+
| `2` | `4` | `8` | `8` | `9` |
+-----------+-----+-----+-----+-----+
``` ## Syntaxe
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?
```C ```C
#include <stdio.h> int tab[3][4]; // déclaration d'un tableau 3 x 4
#include <stdlib.h> tab[2][1]; // accès case: ligne 2, colonne 1
int main(int argc, char *argv[]) { tab[2][1] = 14; // assignation de 14 à la position 2, 1
float a = atof(argv[1]);
float b = atof(argv[2]);
printf("%.10f\n", (double)(a + b));
}
``` ```
. . . # Tableau à deux dimensions (3/4)
## Que se passe-t-il donc?
# Nombres à virgule (2/3)
## Nombres à virgule fixe
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
| $2^3$ | $2^2$ | $2^1$ | $2^0$ | `.` | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ |
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
| `1` | `0` | `1` | `0` | `.` | `0` | `1` | `0` | `1` |
+-------+-------+-------+-------+-----+----------+----------+----------+----------+
## Qu'est-ce ça donne en décimal?
. . .
$$
2^3+2^1+\frac{1}{2^2}+\frac{1}{2^4} = 8+2+0.5+0.0625=10.5625.
$$
## Limites de cette représentation? \footnotesize
. . . ## Exercice:
* 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?
* La mantisse fait $\sim 2^{53}\sim10^{16}$, Déclarer et initialiser aléatoirement un tableau `50x100` avec des valeurs `0` à `255`
* Ou encore $\sim \log_{10}(2^{53})\sim 16$,
* Environ **seize** chiffres significatifs.
## Plus petit/plus grand nombre représentable?
. . .
* Plus petite mantisse et exposant: $\sim 2^{-52}\cdot 2^{-1022}\sim 4\cdot 10^{-324}$,
* Plus grande mantisse et exposant: $\sim 2\cdot 2^{1023}\sim \cdot 1.8\cdot 10^{308}$.
# Précision finie (1/3)
## Erreur de représentation
* Les nombres réels ont potentiellement un **nombre infini** de décimales
* $1/3=0.\overline{3}$,
* $\pi=3.1415926535...$.
* Les nombres à virgule flottante peuvent en représenter qu'un **nombre
fini**.
* $1/3\cong 0.33333$, erreur $0.00000\overline{3}$.
* $\pi\cong3.14159$, erreur $0.0000026535...$.
On rencontre donc des **erreurs de représentation** ou **erreurs
d'arrondi**.
. . .
## Et quand on calcule?
* Avec deux chiffres significatifs
\begin{align}
&8.9+(0.02+0.04)=8.96=9.0,\\
&(8.9+0.02)+0.04=8.9+0.04=8.9.
\end{align}
. . .
## Même pas associatif!
# Précision finie (2/3)
## Erreur de représentation virgule flottante
$$
(1.2)_{10} = 1.\overline{0011}\cdot 2^0\Rightarrow 0\ 01111111\
00110011001100110011010.
$$
Erreur d'arrondi dans les deux derniers bits et tout ceux qui viennent
ensuite
$$
\varepsilon_2 = (00000000000000000000011)_2.
$$
Ou en décimal
$$
\varepsilon_{10} = 4.76837158203125\cdot 10^{-8}.
$$
# Précision finie (3/3)
## Comment définir l'égalité de 2 nombres à virgule flottante?
. . .
Ou en d'autres termes, pour quel $\varepsilon>0$ (appelé `epsilon-machine`) on a
$$
1+\varepsilon = 1,
$$
pour un nombre à virgule flottante?
. . .
Pour un `float` (32 bits) la différence est à
$$
2^{-23}=1.19\cdot 10^{-7},
$$
Soit la précision de la mantisse.
## Comment le mesurer (par groupe)?
. . . . . .
```C ```C
float eps = 1.0; #define NX 50
while ((float)1.0 + (float)0.5 * eps != (float)1.0) { #define NY 100
eps = (float)0.5 * eps; 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 ## Exercice: afficher le tableau
Et jusqu'ici on a encore pas fait d'arithmétique!
## Multiplication avec deux chiffres significatifs, décimal
$$
(1.1)_{10}\cdot (1.1)_{10}=(1.21)_{10}=(1.2)_{10}.
$$
En continuant ce petit jeu:
$$
\underbrace{1.1\cdot 1.1\cdots 1.1}_{\mbox{10 fois}}=2.0.
$$
Alors qu'en réalité
$$
1.1^{10}=2.5937...
$$
Soit une erreur de près de 1/5e!
. . . . . .
## Le même phénomène se produit (à plus petite échelle) avec les `float` ou
`double`.
# Exemple de récursivité (1/2)
## La factorielle
```C ```C
int factorial(int n) { for (int i = 0; i < NX; ++i) {
if (n > 1) { for (int j = 0; j < NY; ++j) {
return n * factorial(n - 1); printf("%d ", tab[i][j]);
} else {
return 1;
} }
printf("\n");
} }
``` ```
. . . # Tableau à deux dimensions (4/4)
## 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) ## Attention
## La factorielle * Les éléments ne sont **jamais** initialisés.
* Les bornes ne sont **jamais** vérifiées.
```C ```C
int factorial(int n) { int tab[3][2] = { {1, 2}, {3, 4}, {5, 6} };
if (n > 1) { printf("%d\n", tab[2][1]); // affiche?
return n * factorial(n - 1); printf("%d\n", tab[10][9]); // affiche?
} else { printf("%d\n", tab[3][1]); // affiche?
return 1;
}
}
``` ```
. . . # 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` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
+----------------+----------------+----------------+----------------+ ## Exercice
| `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) * 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... # Types énumérés (1/2)
* Une condition d'arrêt - qui retourne un résultat
## 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 ```C
int factorial(int n) { enum days {
if (n > 1) { monday, tuesday, wednesday,
return n * factorial(n - 1); thursday, friday, saturday, sunday
} else { };
return 1;
}
}
``` ```
* On peut aussi donner des valeurs "custom"
# 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 ```C
int factorial(int n) { enum days {
if (n > 1) { // Condition de récursivité monday = 2, tuesday = 8, wednesday = -2,
return n * factorial(n - 1); thursday = 1, friday = 3, saturday = 12, sunday = 9
} else { // Condition d'arrêt };
return 1;
}
}
``` ```
* S'utilise comme un type standard et un entier
# La récursivité (3/4)
## Exercice: trouver l'$\varepsilon$-machine pour un `double`
. . .
```C ```C
double epsilon_machine(double eps) { enum days d = monday;
if (1.0 + eps != 1.0) { (d + 2) == monday + monday; // true
return epsilon_machine(eps / 2.0);
} else {
return eps;
}
}
``` ```
# 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 ```C
void recurse(int n) { enum days d = monday;
printf("%d ", n % 2); switch (d) {
if (n / 2 != 0) { case monday:
recurse(n / 2); // trucs
} else { break;
printf("\n"); case tuesday:
} printf("0 ou 1\n");
break;
} }
recurse(13);
``` ```
* Le compilateur vous prévient qu'il en manque!
. . . # Utilisation des types énumérés
```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.
// 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é" title: "Récursivité et représentation des nombres"
date: "2021-11-03" date: "2024-10-29"
patat:
eval:
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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 ```C
int factorial(int n) { int factorial(int n) {
...@@ -29,20 +123,59 @@ patat: ...@@ -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 ```C
int factorial(int n) { double epsilon_machine(double eps) {
int f = 1; if (1.0 + eps != 1.0) {
for (int i = 1; i < n; ++i) { return epsilon_machine(eps / 2.0);
f *= i; } 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) # Exercice: réusinage et récursivité (1/4)
## Réusiner le code du PGCD avec une fonction récursive ## Réusiner le code du PGCD avec une fonction récursive
...@@ -151,34 +284,45 @@ int fib_imp(int n) { ...@@ -151,34 +284,45 @@ int fib_imp(int n) {
} }
``` ```
# Exponentiation rapide
\Huge L'exponentiation rapide ou indienne
# Exponentiation rapide ou indienne (1/4) # Exponentiation rapide ou indienne (1/4)
## But: Calculer $x^n$ ## But: Calculer $x^n$
* Algorithme naîf et impératif * Quel est l'algorithmie le plus simple que vous pouvez imaginer?
. . .
```C ```C
int pow(x, n) { double pow(double x, int n) {
if (0 == n) { if (0 == n) {
return 1; return 1;
} }
double p = x;
for (int i = 1; i < n; ++i) { 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) # Exponentiation rapide ou indienne (2/4)
* Algorithme naïf et récursif * Proposez un algorithme naïf et récursif
. . .
```C ```C
int pow(x, n) { double pow(double x, int n) {
if (n != 0) { if (n != 0) {
return x * pow(x, n-1); return x * pow(x, n-1);
} else { } else {
...@@ -211,8 +355,8 @@ $$ ...@@ -211,8 +355,8 @@ $$
## Le vrai algorithme ## Le vrai algorithme
* Si n est pair: calculer $\left(x^{n/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$. * 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 ## Exercice: écrire l'algorithme récursif correspondant
...@@ -220,8 +364,8 @@ $$ ...@@ -220,8 +364,8 @@ $$
```C ```C
double pow(double x, int n) { double pow(double x, int n) {
if (1 == n) { if (0 == n) {
return x; return 1;
} else if (n % 2 == 0) { } else if (n % 2 == 0) {
return pow(x, n / 2) * pow(x, n/2); return pow(x, n / 2) * pow(x, n/2);
} else { } else {
...@@ -231,271 +375,3 @@ double pow(double x, int n) { ...@@ -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" title: "Représentation des nombres, tris, et complexité"
date: "2021-11-10" date: "2024-11-11"
patat: header-includes: |
eval: \usepackage{xcolor}
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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 ```C
void tri_insertion(int N, int tab[N]) { num = 247
for (int i = 1; i < N; i++) { N = 0
int tmp = tab[i];
int pos = i; tant que (2^(N+1) < num) {
while (pos > 0 && tab[pos - 1] > tmp) { N += 1
tab[pos] = tab[pos - 1];
pos = pos - 1;
}
tab[pos] = tmp;
} }
```
. . .
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) ## Dépassement de capacité: le nombre est "tronqué"
* Placer: en moyenne $i$ comparaisons et affectations à l'étape $i$
* Moyenne: $\mathcal{O}(N^2)$ * `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) # Tri rapide ou quicksort (1/8)
...@@ -121,56 +414,59 @@ Non c'est normal, faisons un exemple. ...@@ -121,56 +414,59 @@ Non c'est normal, faisons un exemple.
# Tri rapide ou quicksort (3/8) # Tri rapide ou quicksort (3/8)
\footnotesize
Deux variables sont primordiales: Deux variables sont primordiales:
```C ```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) ![Un exemple de quicksort.](figs/quicksort.svg)
# Tri rapide ou quicksort (4/8) # Tri rapide ou quicksort (4/8)
\footnotesize
Deux variables sont primordiales: Deux variables sont primordiales:
```C ```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 ## Pseudocode: quicksort
```C ```python
void quicksort(array, low, high) { rien quicksort(entier tableau[], entier ind_min, entier ind_max)
if (more than 1 elems) { si (longueur(tab) > 1)
pivot_ind = partition(array, low, high); ind_pivot = partition(tableau, ind_min, ind_max)
if (something left of pivot) si (longueur(tableau[ind_min:ind_pivot-1]) != 0)
quicksort(array, low, pivot_ind - 1); quicksort(tableau, ind_min, pivot_ind - 1)
if (something right of pivot) si (longueur(tableau[ind_pivot+1:ind_max]) != 0)
quicksort(array, pivot_ind + 1, high); quicksort(tableau, ind_pivot + 1, ind_max)
}
}
``` ```
# Tri rapide ou quicksort (5/8) # Tri rapide ou quicksort (5/8)
\footnotesize
## Pseudocode: partition ## Pseudocode: partition
```C ```C
int partition(array, low, high) { entier partition(entier tableau[], entier ind_min, entier ind_max)
pivot = array[high]; // choix arbitraire pivot = tableau[ind_max] // choix arbitraire
i = low; i = ind_min
j = high-1; j = ind_max-1
while i < j { tant que i < j:
en remontant i trouver le premier élément > pivot; en remontant i trouver le premier élément > pivot
en descendant j trouver le premier élément < pivot; en descendant j trouver le premier élément < pivot
swap(array[i], array[j]); échanger(tableau[i], tableau[j])
// les plus grands à droite // les plus grands à droite
// mettre les plus petits à gauche // mettre les plus petits à gauche
}
// on met le pivot "au milieu" // on met le pivot "au milieu"
swap(array[i], array[pivot]); échanger(tableau[i], tableau[ind_max])
return i; // on retourne l'indice pivot retourne i // on retourne l'indice pivot
}
``` ```
# Tri rapide ou quicksort (6/8) # Tri rapide ou quicksort (6/8)
...@@ -195,29 +491,6 @@ void quicksort(int size, int array[size], int first, ...@@ -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) # Tri rapide ou quicksort (7/8)
...@@ -231,10 +504,10 @@ int partition(int size, int array[size], int first, int last) { ...@@ -231,10 +504,10 @@ int partition(int size, int array[size], int first, int last) {
int i = first - 1, j = last; int i = first - 1, j = last;
do { do {
do { do {
i++; i += 1;
} while (array[i] < pivot && i < j); } while (array[i] < pivot && i < j);
do { do {
j--; j -= 1;
} while (array[j] > pivot && i < j); } while (array[j] > pivot && i < j);
if (j > i) { if (j > i) {
swap(&array[i], &array[j]); swap(&array[i], &array[j]);
...@@ -245,7 +518,7 @@ int partition(int size, int array[size], int first, int last) { ...@@ -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? ## Quelle est la complexité du tri rapide?
...@@ -259,77 +532,13 @@ int partition(int size, int array[size], int first, int last) { ...@@ -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. * Chaque fois le tableau est séparé en $2$ parties égales.
* On a $\log_2(N)$ partitions, et $N$ boucles $\Rightarrow N\cdot * On a $\log_2(N)$ partitions, et $N$ boucles $\Rightarrow N\cdot
\log_2(N)$. \log_2(N)$.
* En moyenne: $\mathcal{O}(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).
# L'algorithme à la main # L'algorithme à la main
## Exercice *sur papier* ## 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 ```C
...@@ -347,4 +556,3 @@ $$ ...@@ -347,4 +556,3 @@ $$
``` ```
--- ---
title: "Backtracking et piles" title: "Tris et complexité"
date: "2021-11-17" date: "2024-11-18"
patat: header-includes: |
eval: \usepackage{xcolor}
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# 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 ```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. \Huge Le tri par base (radix sort)
* Donc chaque solution a **une** reine **par colonne** ou **ligne**.
## Généralisation # Tri par base (radix sort)
* Placer $N$ reines sur un échiquier de $N \times * N'utilise pas la notion de comparaisons, mais celle de classement successif dans des catégories (alvéoles).
N$. * Pour simplifier
- Exemple de **backtracking** (retour en arrière) $\Rightarrow$ récursivité. * 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: # Principe de l'algorithme
[wikipedia](https://fr.wikipedia.org/wiki/Problème_des_huit_dames)](./figs/fig_recursivite_8_reines.png){width=35%}
# 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. Soit la liste de nombre entier:
* 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à.
. . . | 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 Le plus petit élément est -9. On commence donc par décaler les valeurs de 9.
reines.
# 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 * On considère le bit de poids faible
horizontale).](figs/4reines_sym.svg)
# 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 * On obtient le tableau:
matrix.
```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. * Pour revenir aux valeurs initiales, il faut décaler de 9 dans l'autre sens.
* On place les reines sur des colonnes successives.
* On a pas besoin de "regarder en arrière" (on place "devant" uniquement).
* Trois étapes:
* On place une reine dans une case libre.
* On met à jour le tableau.
* Quand on a plus de cases libres on "revient dans le temps" ou c'est qu'on
a réussi.
# Le code du problème des 8 reines (1/N) | 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: * Et alors rien. C'est fini.
```C
bool board[n][n]; # 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 ## La fonction `echanger()`
// Pour chaque ligne placer la reine sur toutes les colonnes
// et compter les solutions ```python
void nbr_solutions(board, column, counter); rien echanger(entier tab[], entier tab2[])
// Copier un tableau dans un autre # échanger les tableaux (sans copier les valeurs)
void copy(board_in, board_out);
// Placer la reine à li, co et rendre inaccessible devant
void placer_devant(board, li, co);
``` ```
# Le code du problème des 8 reines (2/N) # Un peu plus de détails (2/2)
## Le calcul du nombre de solutions ## La fonction `alveole_0()`
```C ```python
// Calcule le nombre de solutions au problème des <n> reines rien alveole_0(entier taille, entier tab[taille],
nbr_solutions(board, column, count) entier tab_tmp[taille], entier pos):
// pour chaque ligne entier k = 0
// si la case libre pour i de 0 à taille-1:
// si column < n - 1 si bit(tab[i], pos) == 0:
// copier board dans un "new" board, tab_tmp[k] = tab[i]
// y poser une reine k = k + 1
// et mettre à jour ce "new" board
// nbr_solutions(new_board, column+1, count)
// sinon
// on a posé la n-ème et on a gagné
// count += 1
``` ```
# Le code du problème des 8 reines (3/N) . . .
## Le calcul du nombre de solutions ## La fonction `alveole_1()`
```C ```python
// Placer une reine et mettre à jour rien alveole_1(entier taille, entier tab[taille],
placer_devant(board, ligne, colonne) entier tab_tmp[taille], entier pos):
// board est occupé à ligne/colonne # pareil que alveole_0 mais dans l'autre sens
// toutes les cases des colonnes
// suivantes sont mises à jour
``` ```
# 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 # Principe de l'algorithme
// 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;
}
}
}
}
```
* 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 * Soit la liste de nombres entiers stockés dans un tableau de taille 9:
// 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)> | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
void prises_devant(int n, bool board[n][n], int li, int co) { |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
board[li][co] = false; // position de la reine | 5 | -5 | 1 | 6 | 4 | -6 | 2 | -9 | 2 |
for (int j = 1; j < n-co; j++) {
// horizontale et diagonales à droite de la reine . . .
if (j <= li) {
board[li-j][co+j] = false; * Fusion des éléments successifs (ce qui revient à les mettre dans l'ordre):
}
board[li][co+j] = false; | étape | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
if (li+j < n) { |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
board[li+j][co+j] = false; | 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: # La fonction de fusion (pseudo-code autrement)
[Wikipedia](https://upload.wikimedia.org/wikipedia/commons/e/e1/Stack_%28data_structure%29_LIFO.svg)](figs/Stack.svg){width=70%}
## Des exemples de la vraie vie \footnotesize
## Une idée?
. . . . . .
* Pile d'assiettes, de livres, ... ```python
* Adresses visitées par un navigateur web. # hyp: tab_g et tab_d sont triés
* Les calculatrices du passé (en polonaise inverse). rien fusion(entier tab_g[], entier tab_d[], entier res[]):
* Les boutons *undo* de vos éditeurs de texte (aka *u* dans vim). 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. Ainsi, la complexité du tri par fusion est $\mathcal{O}(N\cdot \log_2(N)$. -->
2. Dépiler (pop): retirer l'élément du sommet de la pile et le retrouner.
3. Liste vide? (is_empty?).
. . . # L'efficacité d'un algorithmique
4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler). \Huge L'efficacité d'un algorithmique
5. Nombre d'éléments (length).
## 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. * Mesurer le temps CPU,
5. Dépiler jusqu'à ce que la pile soit vide, puis empiler à nouveau. * 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 ## Mesure du temps CPU
mémoire).
# 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 ## Preuve sur un [petit exemple](../source_codes/complexity/sum.c)
abstraite).
* Pas de choix unique d'implémentation.
## 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 ```C
#define MAX_CAPACITY 500 int sorted_list[N];
typedef struct _stack { bool in_list = is_present(N, sorted_list, elem);
int data[MAX_CAPACITY]; // les données
int top; // indice du sommet
} stack;
``` ```
# Les piles (4/N) * Plus `N` est grand, plus l'algorithme prend de temps sauf si...
## Initialisation
. . . . . .
* 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 ```C
void stack_init(stack *s) { bool is_present(int n, int tab[], int elem) {
s->top = -1; 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 ```C
bool stack_is_empty(stack s) { bool is_present_binary_search(int n, int tab[], int elem) {
return s.top == -1; 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 ```C
void stack_push(stack *s, int val) { for (i = 2; i < sqrt(N); ++i) {
s->top += 1; if (N % i == 0) {
s->data[s->top] = val; 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 ```C
int stack_pop(stack *s) { int min = MAX;
s->top -= 1; for (i = 0; i < N; ++i) {
return s->data[s->top+1]; 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 ```C
int stack_peek(stack *s) { int ind = 0;
return s->data[s->top]; 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. ## Réponse
* Dépiler avec une pile vide.
* Jeter un oeil au sommet d'une pile vide. ### `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" title: "Tris, complexité, backtracking et assertions"
date: "2021-11-25" date: "2024-11-25"
patat: header-includes: |
eval: \usepackage{xcolor}
tai:
command: fish
fragment: false
replace: true
ccc:
command: fish
fragment: false
replace: true
images:
backend: auto
--- ---
# Les piles (1/5) # Le tri à bulle
## Qu'est-ce donc? \Huge Le tri à bulle
* Structure de données abstraite... # Tri à bulle (1/4)
. . . ## Algorithme
* de type `LIFO` (*Last in first out*).
![Une pile où on ajoute A, puis B avant de les retirer. Source: * Parcours du tableau et comparaison des éléments consécutifs:
[Wikipedia](https://upload.wikimedia.org/wikipedia/commons/e/e1/Stack_%28data_structure%29_LIFO.svg)](figs/Stack.svg){width=70%} - 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, ... * Le plus grand élément est **à la fin** du tableau.
* Adresses visitées par un navigateur web. * Plus besoin de le traiter.
* Les calculatrices du passé (en polonaise inverse). * A chaque parcours on s'arrête un élément plus tôt.
* Les boutons *undo* de vos éditeurs de texte (aka *u* dans vim).
# Les piles (2/5) # Tri à bulle (2/4)
## Fonctionnalités ## Exemple
. . .
1. Empiler (push): ajouter un élément sur la pile. ![Tri à bulles d'un tableau d'entiers](figs/tri_bulles.svg)
2. Dépiler (pop): retirer l'élément du sommet de la pile et le retrouner.
3. Liste vide? (is_empty?).
. . .
4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler). # Tri à bulle (3/4)
5. Nombre d'éléments (length).
## 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. ```C
5. Dépiler jusqu'à ce que la pile soit vide, puis empiler à nouveau. 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 # L'algorithme à la main
mémoire).
# 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 ```C
abstraite).
* Pas de choix unique d'implémentation.
## Quelle structure de données allons nous utiliser?
. . .
Et oui vous avez deviné: un tableau!
## La structure: de quoi avons-nous besoin (pile de taille fixe)?
. . .
```C
#define MAX_CAPACITY 500
typedef struct _stack {
int data[MAX_CAPACITY]; // les données
int top; // indice du sommet
} stack;
```
# Les piles (4/5)
## 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 * trier un tableau par ordre croissant
void stack_push(stack *s, int val) {
s->top += 1;
s->data[s->top] = val;
}
```
# 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 # Tri par insertion (2/3)
int stack_pop(stack *s) {
s->top -= 1;
return s->data[s->top+1];
}
```
## Jeter un oeil (regarder le sommet) ## Exercice: Proposer un algorithme (en C)
. . . . . .
```C ```C
int stack_peek(stack *s) { void tri_insertion(int N, int tab[N]) {
return s->data[s->top]; 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. * Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$
* Dépiler avec une pile vide. * Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$
* Jeter un oeil au sommet d'une pile vide.
# Gestion d'erreur, level 0 # L'algorithme à la main
* Il y a plusieurs façon de traiter les erreur: ## Exercice *sur papier*
* Ne rien faire (laisser la responsabilité à l'utilisateur).
* Faire paniquer le programme (il plante plus ou moins violemment). * Trier par insertion le tableau `[5, -2, 1, 3, 10]`
* Utiliser des codes d'erreurs.
```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}). \footnotesize
- Vérification du domaine des indices (dépassement de tableau).
## 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). 1. Recherche de la valeur minimum ```val_min```
- 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, ...). 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... Ainsi, la complexité du tri par base est $\mathcal{O}(b\cdot N)$. -->
- Typiquement désactivées dans le code de production.
# 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 * Au final: $\mathcal{O}(N\cdot (b+4))$.
// alloue une zone mémoire de size octets
void *malloc(size_t size); # Complexité algorithmique du merge-sort (1/2)
// change la taille allouée à size octets (contiguïté garantie)
void *realloc(void *ptr, size_t size); ## 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 * Au final: $\mathcal{O}(N\log_2(N))$.
stack_create(); // crée une pile avec une taille par défaut
// vérifie si la pile est pleine et réalloue si besoin
stack_push();
// vérifie si la pile est vide/trop grande
// et réalloue si besoin
stack_pop();
```
## Exercice: ouvrir un repo/issues pour l'implémentation # Complexité algorithmique du quick-sort (1/2)
* Oui-oui cela est une introduction au développement collaboratif (et ## Pseudocode: quicksort
hippie).
# 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 \Huge Le problème des 8-reines
Pour tous les i = 0 à N-1
tant que (tab[i] > que le sommet de G) { # Problème des 8-reines
dépiler G dans D
}
tant que (tab[i] < que le sommet de D) {
dépiler de D dans G
}
empiler tab[i] sur G * Placer 8 reines sur un échiquier de $8 \times 8$.
dépiler tout D dans G * Sans que les reines ne puissent se menacer mutuellement (92 solutions).
tab est trié dans G
```
# 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) * Faire une capture d'écran / une photo de votre solution et la poster sur
matrix.
## Vocabulaire
```C ```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) * Une reine par colonne au plus.
* On place les reines sur des colonnes successives.
## De infixe à post-fixe: algorithme * 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. ## Quelle structure de données?
* 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.
# La calculatrice (4/8) . . .
## De infixe à post-fixe: exemple Une matrice de booléens fera l'affaire:
```C ```C
Infixe Postfixe Pile Priorité bool board[n][n];
((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
``` ```
# La calculatrice (5/8) ## Quelles fonctionnalités?
## De infixe à post-fixe: exemple . . .
```C ```C
Infixe Postfixe Pile Priorité // Pour chaque ligne placer la reine sur toutes les colonnes
((A*B)/D-F)/(G+H) Vide Vide Néant // et compter les solutions
-------------------------------------------------------- void nbr_solutions(board, column, counter);
/(G+H) AB*D/F- Vide Néant // Copier un tableau dans un autre
(G+H) AB*D/F- / 2 void copy(board_in, board_out);
G+H) AB*D/F- /( 0 // Placer la reine à li, co et rendre inaccessible devant
+H) AB*D/F-G /( 0 void placer_devant(board, li, co);
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
``` ```
# 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 ```C
char *infix_to_postfix(char* infix) { // init and alloc stack and postfix // Placer une reine et mettre à jour
for (size_t i = 0; i < strlen(infix); ++i) { rien placer_devant(board, ligne, colonne)
if (is_operand(infix[i])) { board est occupé à ligne/colonne
// we just add operands in the new postfix string toutes les cases des colonnes
} else if (infix[i] == '(') { suivantes sont mises à jour
// 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;
}
``` ```
# 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 ## Le nombre de solutions
2 3 4 + * 5 - = ?
```
* On parcours de gauche à droite: \footnotesize
```C ```C
Caractère lu Pile opérandes // Calcule le nombre de solutions au problème des <n> reines
2 2 void nb_sol(int n, bool board[n][n], int co, int *ptr_cpt) {
3 2, 3 for (int li = 0; li < n; li++) {
4 2, 3, 4 if (board[li][co]) {
+ 2, (3 + 4) if (co < n-1) {
* 2 * 7 bool new_board[n][n]; // alloué à chaque nouvelle tentative
5 14, 5 copy(n, board, new_board);
- 14 - 5 = 9 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. \footnotesize
2. L'opérateur s'applique *toujours* au 2 opérandes au sommet.
3. Le résultat est remis au sommet.
## Exercice: écrire l'algorithme (et poster sur matrix) ## Placer devant
```C ```C
bool evaluate(char *postfix, double *val) { // init stack // Retourne une copie du tableau <board> complété avec les positions
for (size_t i = 0; i < strlen(postfix); ++i) { // prises sur la droite droite par une reine placée en <board(li,co)>
if (is_operand(postfix[i])) { void placer_devant(int n, bool board[n][n], int li, int co) {
stack_push(&s, postfix[i]); board[li][co] = false; // position de la reine
} else if (is_operator(postfix[i])) { for (int j = 1; j < n-co; j++) {
double rhs = stack_pop(&s); // horizontale et diagonales à droite de la reine
double lhs = stack_pop(&s); if (j <= li) {
stack_push(&s, op(postfix[i], lhs, rhs); board[li-j][co+j] = false;
} } }
return stack_pop(&s); board[li][co+j] = false;
if (li+j < n) {
board[li+j][co+j] = false;
}
}
} }
``` ```
version: "3.3" version: "3.3"
services: services:
slides: slides:
#To use dockerfile : build: .
image: omalaspinas/pandoc:latest image: omalaspinas/pandoc:latest
environment: user: 1000:1000
USER: 1000
GROUP: 1000
container_name: slides container_name: slides
volumes: volumes:
- ./:/data - ./:/data
# entrypoint: ["make", "all"] entrypoint: ["make"]
working_dir: /data 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;
}