Vous êtes sur la page 1sur 123

Algorithmique

Cristina Sirangelo, ENS-Cachan

Préparation à l'option Informatique


de l'agrégation de mathématiques
Plan
1. Analyse et complexité des algorithmes (S. Haddad)
2. Types abstraits et structures de données
3. Algorithmes de tri
4. Techniques classiques de conception d’algorithmes
5. Algorithmes de graphes
Le problème du tri
Soit U un domaine totalement ordonné

Problème du tri:
Entrée: une séquence a1... an d'éléments de U

Sortie: une permutation b1... bn de la séquence d’entrée, telle que b1 ≤ b2 ≤ ... ≤ bn

Les éléments de la séquence d’entrée sont aussi appelés clefs


Ils peuvent être accompagnés de données satellites (un deuxième champ valeur)
Tri interne et tri externe
Tri interne:

• Éléments à trier en mémoire centrale


• Représentation de la séquence d'entrée/sortie: un tableau de n éléments
• Algorithme de tri sur place:
• tableau de sortie = tableau d’entrée réorganisé
• au plus un nombre constant d'éléments sont stockés à l'extérieur du tableau pendant la
réorganisation

Tri externe:

• Éléments à trier disponibles en mémoire secondaire (trop volumineux pour être tous en
mémoire centrale en même temps)
• Coût mesuré en nombre d'accès au disque
Tri interne
Deux types
Tri par comparaisons
Ex.
Tri par sélection (Selection sort)
Tri à bulles (Bubble sort)
Tri par insertion (Insertion sort)
Tri fusion (Merge sort)
Tri par tas (Heapsort)
Tri rapide (Quicksort)

Tri en temps linéaire (non basé sur les comparaisons)


Ex.
Tri par dénombrement (Counting sort)
Tri par base (Radix sort)
Tri par paquets (Bucket sort)
Tri par comparaisons
Algorithme de tri par comparaisons: algorithme de tri uniquement basé sur des
comparaisons entre les éléments de l’entrée
• peut être vu comme un arbre de décision
• ensemble de permutations associé à chaque noeud de l’arbre
• nombre de feuilles de l’arbre = nombre de permutations possibles (n! pour une séquence de
taille n)

• profondeur d’une feuille de l’arbre de décision = nombre de comparaisons nécessaires à


“découvrir” la permutation qui trie la séquence d’entrée
• hauteur (moyenne) de l’arbre: nombre de comparaisons nécessaires dans le cas pire (moyen)
• hauteur de l’arbre de décision: au moins log N où N est le nombre de feuilles (aussi en
moyenne)

• nombre de comparaisons nécessaires au tri : au moins log n! ( ∼ n log n) , aussi en


moyenne
Pour plus de détails: le cours de S. Haddad
Tri par comparaisons
Tri par comparaisons Complexité

cas pire cas moyen


Tri par sélection (Selection sort) Θ(n2) Θ(n2)
Inefficaces en pratique
Tri à bulles (Bubble sort) Θ(n2) Θ(n2)
Très efficace (meilleures
Tri par insertion (Insertion sort) Θ(n2) Θ(n2) prestations que Merge sort) sur
des entrée de petite taille

Tri fusion (Merge sort) Θ(n log n) Θ(n log n) Coût asymptotique optimal,
efficaces sur des
Tri par tas (Heapsort) Θ(n log n) Θ(n log n) entrées de grande taille

Très efficace en pratique


(meilleures prestations que
Heapsort)
Tri rapide (Quicksort) Θ(n2) Θ(n log n) En version randomisée: considéré
le meilleur algorithme de tri pour
des entrées de grande taille

Tous sur place sauf Merge sort


Tri par sélection (Selection sort)
Tri du tableau T[ 1..n ]:

for (i=1 to n-1) do

trouver l'élément minimal dans T[ i .. n ] (parcours linéaire de T[ i .. n ] )


échanger T[i] avec cet élément
endfor

• Coût en terme de nombre de comparaisons + échanges (cas meilleur, pire et moyen):


n + (n-1) +...+ 2 = Θ(n2)

• Le nombre de comparaisons (qui ne dépend pas des données) domine le coût

• Le nombre d'échanges dépend des données, voir Knuth “Sorting and Searching” pour une
analyse fine du nombre d'échanges en moyenne
Tri à bulles (Bubblesort)
Il s’agit d’une amélioration du tri par sélection :

• une autre façon de placer le minimum de T[ i.. n ] dans T[i] :


‣ parcourir T[ i..n ] de n à i
‣ à chaque étape échanger deux éléments contigus s’ils ne sont pas dans le bon ordre
• avantage: non seulement le minimum est placé dans T[i], mais tous les éléments de i à la
position du dernier échange sont à leur place
i←1 // les éléments de 1 à i -1 sont à leur place
while (i < n) do
// séquence d'échanges sur T[i..n]:
last_swap ← n
for (j =n downto i+1 ) do
if ( T[ j ] < T[ j-1 ] ) then échanger T[ j ] et T[ j-1]; last_swap ← j-1
endif
endfor
// i avance “plus vite” :
i ← last_swap +1
endwhile
Correction du tri à bulles
Invariants de boucle:

1) Au début de chaque itération de la boucle for:


- last_swap ≥ j et
- Pour tout k= j.. last_swap T[k] contient le minimum de T[k..n]
2) Au début de chaque itération de la boucle while:
Pour tout k, 1 ≤ k ≤ i-1 et k ≤ n T[k] contient le minimum de T[k..n]
(i.e. chaque élément est à sa place)
Terminaison
À chaque itération de la boucle while, i est strictement plus grand que dans l'itération
précédente
Exercice: démontrer les invariants de boucle et la propriété de terminaison

Quand l'algorithme termine i > n


2) prouve que pour tout 1 ≤ k ≤ n T[k] contient le minimum de T[k..n], i.e. T est trié
Temps d'exécution du tri à bulles

Cas meilleur Cas pire Cas moyen


Θ(n) Θ(n2) Θ(n2)

Cas meilleur: tableau déjà trié


‣ i ←1, une seule séquence de comparaisons (aucun échange), i ←n+1
Cas pire: tableau inversement trié
‣ chaque séquence d'échanges met seulement le minimum à sa place (comme le tri par
sélection)
Cas moyen: Assomptions: Chaque permutation de <1..n> en entrée est équiprobable
Variables aléatoires:
C: temps d'exécution (en nombre de comparaisons/ échanges)
B: nombre d'échanges
E[C] ≥ E[B], on montre que le nombre moyen d'échanges est

E[C] = Ω( n2) E[C] = Θ( n2)


Coût moyen du tri à bulles - Inversions
Calcul du nombre moyen d'échanges:
Définition. Inversion dans une permutation a1,.., an de {1, 2.., n} :
couple (ai, aj) tel que i < j et ai > aj
Définition. Table d’inversion d’une permutation a1,.., an de {1, 2.., n}:
séquence b1 ..bn où
baj = nombre d'éléments ai, i < j tels que ai > aj
Remarque: b1+ ..+ bn = nombre totale d’inversion dans a1,.., an

Lemme. La fonction qui associe à chaque permutation sa table d’inversion est une bijection des
permutations de {1, .., n} vers {0,..n-1} × {0,.., n-2} × ... ×{0, 1} × {0}
Preuve. Exercice

Corollaire. Si toute les permutations de {1, .., n} sont équiprobables, les éléments bi de la
table d’inversion sont tirés indépendamment et uniformément parmi {0, ..., n-i }, pour tout
i=1..n
Nombre moyen d'échanges du tri à bulles
Exercice. Démontrer B = nombre total d’inversions dans la séquence d’entrée
Tri par insertion (Insertion sort)
for (i=2 to n) do
clef ←T[i]
// insérer clef dans le tableau triée T[1..i-1]:
j ← i-1 Invariant de boucle:
while ( j ≥ 1 et clef < T[j] ) do Au début de chaque itération de la boucle
déplacer T[j] d’une position à droite for, T[1..i-1] contient les éléments
d’indices 1..i-1 de l’entrée, mais triés.
j ← j-1
endwhile
T[j+1]← clef
endfor

Insertion dans le tableau trié:


‣ coût dominé par le “shift” du contenu du tableau (la recherche pourrait être dichotomique)
‣ parfois combiné avec des techniques de “bas niveau” pour faire le “shift” efficacement
Tri par insertion (Insertion sort)
for (i=2 to n) do
clef ←T[i]
// insérer clef dans le tableau triée T[1..i-1]:
Temps d'exécution
j ← i-1
while ( j ≥ 1 et clef < T[j] ) do Cas meilleur Cas pire Cas moyen
déplacer T[j] d’une position à droite Θ(n) Θ(n2) Θ(n2)
j ← j-1
endwhile
T[j+1]← clef
endfor

Cas meilleur: tableau déjà trié


chaque élément T[i], i=1..n est comparé avec le précèdent et recopié en position i

Cas Pire: tableau inversement trié


chaque élément T[i] redescend les i-1 positions précédentes. Coût ∼ 1 +2 +...+ n-1
Tri par insertion - analyse du coût moyen
Assomption : toutes les permutations des rangs 1...n des clefs en entrée sont équiprobables

C: coût du tri

C = C2+ ...+Cn où Ci : coût de l’insertion de T[i] dans le tableau trié T[1..i-1]


Ci = rang de T[i] dans T[1..i]

Par l’invariant de boucle T[1..i] contient les clefs d'origine toutes le permutations des rangs
1..i sont équiprobables Ci est tiré uniformément parmi 1..i

E[Ci] = (i+1)/2
Tri fusion (Merge sort)
Approche “divide et impera”

Tri-Fusion (T, p, r)
Temps d'exécution
if (p < r) then
Cas meilleur Cas pire Cas moyen
q= (p+r) / 2
Θ(n log n) Θ(n log n) Θ(n log n)
Tri-Fusion(T, p, q)
Tri-Fusion(T, q+1, r) voir le cours de S. Haddad
Fusion (T, p, q, r)
endif

Fusion
‣ crée un tableau trié (en temps linéaire) par fusion des deux tableau triés T[p, q] et T[q+1..r]
‣ n'opère pas “sur place”
Tri par Tas (Heapsort)
Algorithme de tri “sur place” qui utilise un structure de données partiellement triée appelé Tas

Définition. Arbre Parfait


Un arbre parfait est un arbre binaire où tous les niveaux sont complets sauf possiblement le
dernier. De plus les feuilles du dernier niveau sont regroupées le plus à gauche possible

Remarque: Pour tout n, il existe exactement un arbre parfait à n noeuds


Tas
Définition. Arbre tournoi (min)
Un arbre tournoi est un arbre binaire dont les sommets sont munis d’une clef et la condition
suivante est satisfaite:
pour chaque sommet x de l’arbre: x.clef ≤ clefs des deux sous-arbres de x (*)

1 3

4 2 9 8

6 7 5 Trois conditions équivalente à (*):


1) pour chaque sommet x: x.clef ≥ clefs des ancêtres de x
2) pour chaque sommet x qui a un fils y : x.clef ≤ y.clef
3) pour chaque sommet x qui a un père y : x.clef ≥ y.clef

Définition. Tas Binaire (Heap) Un arbre parfait tournoi

Remarque: chaque sous-arbre d’un tas est un tas, la hauteur d’un tas à n noeuds est Θ(log n)
Type abstrait Tas Binaire
pour simplifier: absence de données satellites associées aux clefs

Données: les tas binaires d'éléments de type Tclef


Opérations:
CREER-TAS ( ) : Tas crée un tas vide
CREER-TAS (Tableau T) : Tas crée un tas contenant les clefs de T
INSERER-TAS (Tas H, Tclef c) insère la clef c dans le tas H
MIN (Tas H) : Tclef retourne la clef minimale de H (la clef de la racine)
EXTRAIRE-MIN (Tas H) : Tclef supprime et retourne la clef minimale du tas H
DIMINUER-CLEF (Tas H, Noeud x, Tclef c) affecte au noeud x du tas H la nouvelle clef c ≤ x.clef
SUPPRIMER (Tas H, Noeud x) supprime le noeud x du tas H

AUGMENTER-CLEF (Tas H, Noeud x, Tclef c) affecte au noeud x du tas H la clef c ≥ x.clef


(Certains auteurs définissent uniquement DIMINUER-CLEF pour le Tas-min et uniquement
AUGMENTER-CLEF pour les Tas-max)
Tas binaire
Implémentation du type Tas Binaire : par tableau
Implémentation des données :
Un tas: Un tableau T[1..N] (type Noeud: indices 1..N)
Un entier taille
Les clef du tas sont stockées par niveaux de gauche à droite dans le tableau
T
0 0 1 3 4 2 9 8 6 7 5

1 3 taille

4 2 9 8
Fonctions auxiliaires:
6 7 5
père (Noeud i): Noeud return i/2
gauche (Noeud i): Noeud return 2i
droit (Noeud i): Noeud return 2i+1
Tas binaire
Implémentation des opérations:
Dans la suite on appelle:
Tas augmenté en i : un arbre parfait (représenté par tableau) tel que tous les noeuds j de l’arbre
avec j≠ i satisfont la condition:
j.clef ≤ clefs des deux sous-arbres de j
Tas réduit en i : un arbre parfait (représenté par tableau) tel que tous les noeuds j de l’arbre avec
j≠ i satisfont la condition:
j.clef ≥ clefs des ancêtres de j
Remarque: les deux notions ne sont pas équivalentes 3

5 7

2
Procédures auxiliaires qui rétablissent la structure de Tas
RETABLIR-TAS-BAS (Tas H, Noeud i ) agit sur un tas H augmenté en i et rétablit la structure de Tas
RETABLIR-TAS-HAUT (Tas H, Noeud i) agit sur un tas H réduit en i et rétablit la structure de Tas
Tas binaire
RETABLIR-TAS-BAS ( Tas (T,taille) , Noeud i )
// agit sur un tas (T, taille) augmenté en i et rétablit la structure de Tas
c ← i //noeud courant
repeat
p←c
affecter à c l’index parmi {p, gauche(p), droit(p)} (s’ils existent) contenant la plus petite clef
if (c ≠ p) then échanger T[p] avec T[c]; endif
until (c = p) Temps d'exécution (cas pire): Θ(log n)

Correction
Invariant de boucle:
‣ Au début de chaque itération de la boucle repeat, (T, taille) est un tas augmenté en c
Remarque: Si H est un tas augmenté en c et c est une feuille ou T[c] ≤ clefs des fils de c,
alors H est un tas
Terminaison:
‣ L’indice p descend dans la profondeur de l’arbre à chaque itération de la boucle repeat,
‣ À la fin d’une itération de la boucle repeat, si p est une feuille alors c = p
Tas binaire
Invariant de boucle

c k k2
itération
k1 k2 k1 k c

≥ k1 ≥ k2 ≥ k1 ≥ k2

k2 = min{ k, k1, k2}

Tas augmenté en c Tas augmenté en c


Tas binaire
RETABLIR-TAS-HAUT ( Tas (T,taille) , Noeud i )
// agit sur un tas (T, taille) réduit en i et rétablit la structure de Tas
c ← i //noeud courant
while ( pere(c) ≥1 and T[pere(c)] > T[c] ) do
échanger T[pere(c)] avec T[c];
c ← pere(c)
endwhile Temps d'exécution: Θ(log n)

Correction
Invariant de boucle:
‣ Au début de chaque itération de la boucle while, (T, taille) est un tas réduit en c
Remarque: Si H est un tas réduit en c et c est la racine ou T[pere(c)] ≤ T[c], alors H est un tas
Terminaison:
‣ L’indice c monte dans la profondeur de l’arbre à chaque itération de la boucle while
Tas binaire
Implémentation des opérations:

INSERER-TAS (Tas H, Tclef c) Θ(log n) EXTRAIRE-MIN (Tas H) : Tclef Θ(log n)


//une nouvelle feuille de clef c est créée min ← H[1]
H.taille ← H.taille+1 // la clef de la racine est remplacé par la clef de
la dernière feuille:
H.T [H.taille] ← c
H.T[1] ← H.T[H.taille]
// ici H est un tas réduit en H.taille
taille← taille -1
Rétablir-Tas-Haut(H, H. taille)
// ici H est un tas augmenté en 1
Rétablir-Tas-Bas (H, 1)
MIN (Tas H) : Tclef Θ(1) return min
return H.T[1]
Tas binaire
Implémentation des opérations:
DIMINUER-CLEF (Tas H, Noeud i, Tclef c) Θ(log n)
H.T[i] ← c
//ici H est un Tas réduit en i
Rétablir-Tas-Haut( H, i);

AUGMENTER-CLEF (Tas H, Noeud i, Tclef c) Θ(log n)


H.T[i] ← c
//ici H est un Tas augmenté en i
Rétablir-Tas-Bas( H, i);

SUPPRIMER (Tas H, Noeud i) Θ(log n)


Diminuer-Clef (H, i, -∞)
Extraire-Min(H)
Tas binaire
Implémentation des opérations: Construction d’un tas
CREER-TAS (Tableau T) : Tas crée un tas contenant les clefs de T “sur place”
//T est interprété comme la représentation d’un arbre parfait de taille n:
taille ← T.n
H ← (T, taille)
// ensuite la structure de tas est rétablie du père de la dernière feuille jusqu'à la racine
for ( i← père(taille) to 1 ) do
Rétablir-Tas-Bas (H, i )
endfor
return H Temps d'exécution Θ(n) (Exercice)
Correction
Invariants de boucle:
Au début de chaque itération de la boucle, pour tout j = i+1...n le sous-arbre de H enraciné en j
est un tas. De plus le sous-arbre de H enraciné en i est un tas augmenté à la racine
À la fin de chaque itération de la boucle for, pour tout j = i...n le sous-arbre de H enraciné en j
est un tas.
Tri par Tas
Tri-par-Tas (Tableau T)
H ← Creer-Tas (T); // crée un tas “sur place” dans T
for ( i← T.n to 1) do
T[i] ← Extraire-Min (H)
endfor
Invariant de boucle:
À la fin de chaque itération de la boucle for, le tas H occupe T[1..i-1], alors que T[ i..n ] contient
les premières n-i+1 clefs triées par ordre décroissant
Tri-par-Tas renvoie le tableau T trié par ordre décroissant
(Utiliser un Tas-max, pour obtenir le tri par ordre croissant)

Temps d'exécution: O(n log n)

Cas pire Cas moyen


Θ(n log n) Θ(n log n)
Files de priorité
Structures de données pour la gestion d’ensembles dynamiques totalement ordonnés.
Le champ qui fourni l’ordre total est appelé priorité.
Type abstrait FileP (Tprio, Tval) où Tprio est un domaine totalement ordonné.
Données: les ensembles d'éléments (p, v) où p est de type Tprio et v de type Tval
Opérations:
Créer-FileP ( ) : FileP crée et renvoie l’ensemble vide de couples
Est-FileP-Vide( F: FileP ) : Booléen teste si F est l’ensemble vide
EnfilerP ( F: FileP, p: TPrio, v: Tval ) ajoute à F le couple (p, v)
DéfilerP ( F: FileP ): (Tprio, Tval) extrait de F le couple de priorité plus élevée (valeur de p plus petit)
TêteP ( F: FileP ): (Tprio, Tval) renvoie le couple de priorité plus élevée (valeur de p plus petit)
Réduire-Priorité ( F: FileP, e: Element, Tprio p ) réduit à p la priorité du couple pointé par e
Augmenter-Priorité ( F: FileP, e: Element, Tprio p ) augmente à p la priorité du couple pointé par e
Dans certain cas:
Fusionner ( F1: FileP, F2: FileP): FileP renvoie l’union F1 ∪ F2
Files de priorité
• Implémentation immédiate par Tas binaire ( si la Fusion n’est pas nécessaire )

(supposer, pour simplifier, absence de champ valeur)


Une file de priorité sur Tprio: Un Tas binaire d'éléments de type Tprio
EnfilerP ( F: FileP, p: TPrio ) Inserer-Tas (F, p)
DéfilerP ( F: FileP ): Tprio return Extraire-Min(F)
TêteP ( F: FileP ): Tprio return Min(F)
Réduire-Priorité ( F: FileP, i: Entier , Tprio p ) Augmenter-clef (F, i, p)
Augmenter-Priorité ( F: FileP, i: Entier, Tprio p ) Reduire-clef (F, i, p)

• La Fusion de tas binaires est inefficace: Θ(n)


• Pour supporter l'opération de Fusion: implémentation par Tas fusionnables
Tas fusionnables
Une famille de types abstraits de données qui supportent les opérations:

CREER-TAS ( ) : Tas crée un tas vide


INSERER-TAS (Tas H, Tclef c) insère la clef c dans le tas H
MIN (Tas H) : Tclef retourne la clef minimale de H
EXTRAIRE-MIN (Tas H) : Tclef supprime et retourne la clef minimale du tas H
DIMINUER-CLEF (Tas H, Noeud x, Tclef c) affecte à l'élément x du tas H la nouvelle clef c ≤ x.clef
SUPPRIMER (Tas H, Noeud x) supprime le noeud x du tas H
FUSIONNER (Tas H1, Tas H2) : Tas renvoie l’union des tas H1 et H2

Deux Types de données dans cette famille:

• Tas Binomiaux
• Tas de Fibonacci
Tas binomiaux
Définition. Arbre binomial
Un arbre binomial de degré k (dénoté Bk ) est un arbre ordonné défini récursivement comme
suit:
B0 est composé d’un seul noeud (racine) Bk

Bk , k > 0, est obtenu en reliant deux arbres Bk-1:


la racine de l’un est ajoutée comme premier fils
de la racine de l’autre
Bk-1
Bk-1

Propriétés. Pour tout k ≥ 0:


- Bk a comme hauteur k, 2k noeuds, et la racine est de degré k
- Noeud de degré maximal dans Bk : la racine
- Les sous-arbres fils de la racine de Bk sont dans l’ordre: Bk-1, Bk-2, ..., B0 (pour k > 0)
Preuve. Exercice
Arbres binomiaux et coefficients binomiaux
Le nom d’arbre binomial dérive de la propriété suivante: Pour tout k≥ 0

il existe exactement noeuds à la profondeur i dans Bk


Preuve. Exercice

Les noeuds de Bk à profondeur i sont en bijection avec les sous-ensembles de k-i éléments
parmi k
Remarque: Une façon d’associer un sous ensemble à chaque noeud: Numéroter les noeuds de
Bk en ordre post-fixe de gauche a droite. L’index de chaque noeud, en k bits, représente le sous-
ensemble associé
111

011 101 110

001 010 100

000
Tas binomiaux
Définition. Tas binomial
Un tas binomial est un ensemble d’arbres binomiaux qui satisfait les conditions suivantes:
- Chaque arbre de l’ensemble est un tournoi
- Pour tout k≥0, il existe au plus un arbre binomial de degré k dans le tas

Propriété des tas binomiaux:


Un tas binomial à n noeuds contient au plus arbres binomiaux

Preuve. Si Bk est présent dans le tas, 2k ≤ n k ≤ log n k ∈[ 0.. ] comme toutes


les valeurs de k dans le tas sont distinctes, il y en a au plus

Remarque. Soit bm ..b0 la représentation en binaire de n. Dans un tas binomial de n noeuds


l’arbre binomial Bk est présent ssi bk=1
Tas binomiaux
Implémentation des données : Dynamique.
Arbre binomial: chaque noeud stocke son degré, pointe à la tète de la liste des fils, et au noeud
père.
Tas binomial: liste chainée des racines des arbres binomiaux, triée par degré croissant

Implémentation des opérations de tas fusionnable


Opération auxiliaire sur deux racines x et z du même dégré de la liste des racines:
Relier (Tas H, Noeud x, Noeud z): Noeud
if( x.clef < z.clef ) then échanger x et z endif
détacher x de la liste des racines de H et recompacter la liste
ajouter x comme premier fils de z
retourner z

Remarque: Relier préserve


- la structure d’arbre binomial de z (le degré est augmenté de 1)
- la propriété d’arbre tournoi de z
Fusion de tas binomiaux
FUSIONNER (Tas H1, Tas H2) : Tas
H ←Créer-Tas() //crée un nouveau tas vide
H← fusion des listes triées des racines de H1 et H2
x← tête de H Consolidation du tas: H peut contenir
plusieurs arbres du même degré:
while (x ≠NIL) do
au plus 2 au début, au plus 3 au cours
m ← nombre de successeurs de x des itérations. Ces arbres sont contigus.
avec le même degré que x
switch(m)
case 0: x←x.next
case 1: z ← x.next; x← Relier (x, z);
case 2: y ← x.next; z←y.next; x ←Relier(y, z);
endswitch
endwhile
return H

Temps d'exécution Θ(log n)


Correction de la fusion
Invariants de boucle:
Au début de chaque itération de la boucle while,
1) H est une liste d’arbres binomiaux tournoi triée par degré croissant,
2) Le degré est strictement croissant jusqu’au noeud x (x inclus)
3) Pour chaque degré k, il y au plus 2 racines de degré k parmi les successeurs de x

Terminaison: le nombre de successeurs de x se réduit à chaque itération

La correction dérive de l’invariant de boucle quand x = NIL

Exercice: Démontrer les invariants de boucle

Remarque. Si on interprète un tas binomial de n noeuds comme la représentation en binaire


de n, FUSIONNER implémente l’addition en binaire:
- Deux arbres de degré k dans la liste fusionnée somme de deux bits 1 de poids k

- Relier ces deux arbres bit de poids k ← 0; “carry” 1 sur la somme des bits de poids k+1
Opérations sur les tas binomiaux
Exercice. En utilisant, si nécessaire, l'opération FUSIONNER, implémenter les autres opérations
de tas fusionnable sur les tas binomiaux, avec les temps d'exécutions suivants:

Temps d'exécution
(Cas pire)
CREER-TAS Θ(1)

INSERER-TAS Θ(log n)

MIN Θ(log n)

EXTRAIRE-MIN Θ(log n)

DIMINUER-CLEF Θ(log n)

SUPPRIMER Θ(log n)

FUSIONNER Θ(log n)
Tas de Fibonacci
Supportent toutes les opérations de tas fusionnables, sauf les opérations de suppression, en
temps amorti O(1)
‣ très efficaces si les suppressions sont peu fréquentes
‣ peu utilisés en pratique (nécessitent des techniques de manipulation complexes)

Tas binaires Tas binomiaux Tas de Fibonacci


(cas pire) (cas pire) (coût amorti)
Θ(1)
CREER-TAS Θ(1) Θ(1)
à partir d’un tableau: Θ(n)
INSERER-TAS Θ(log n) Θ(log n) Θ(1)

MIN Θ(1) Θ(log n) Θ(1)

EXTRAIRE-MIN Θ(log n) Θ(log n) O(log n)

DIMINUER-CLEF Θ(log n) Θ(log n) Θ(1)

SUPPRIMER Θ(log n) Θ(log n) O(log n)

FUSIONNER Θ(n) Θ(log n) Θ(1)


Tas de Fibonacci
Définition. Tas de Fibonacci
Un tas de Fibonacci est un ensemble d’arbres non-ordonnés tournoi dont les noeuds sont
marqués (vrai ou faux) avec les propriétés suivantes:

Pour tout noeud x du tas avec degré k, les fils de x peuvent être triés dans un ordre y0...yk-1
tel que:

1) degré(y0) ≥ 0 et
degré(yi) ≥ i-1 pour tout i = 1...k-1
2) si yi est marqué faux, degré(yi) > i-1 pour tout i = 1...k-1

Remarque: les degrés des racines des arbres dans un tas de Fibonacci ne sont pas
nécessairement distinctes:
‣ insertion et fusion efficaces (le tas n’est pas consolidé)
‣ tas consolidé uniquement après une suppression
Tas de Fibonacci

Exercice. Montrer que réduire de 1 le degré d’un noeud y d’un tas de Fibonacci, par suppression
d’un des ses sous-arbres fils, peut éventuellement invalider la propriété de tas de Fibonacci
uniquement sur le noeud père de y (s’il existe).

Exercice. Montrer qu’un tas binomial où l’ordre des frères est ignoré et tous les noeuds sont
marqués faux, est un tas de Fibonacci.

Exercice.
1) Montrer que si x est un noeud d’un tas de Fibonacci avec degré(x)=k, alors

où Ax est le sous-arbre enraciné en x, |Ax| est son nombre de noeuds, et Fk est le k-


eme nombre de Fibonacci.

2) En utilisant 1) montrer que le degré maximal d’un noeud d’un tas de Fibonacci de
taille n est au plus c log n, pour une certaine constante c .
Tas de Fibonacci
Implémentation des données: Dynamique.
Arbre tournoi:
- Chaque noeud stocke son degré et un booléen marqué, pointe à un de ses fils et au noeud
père;
- Les noeuds frères forment une liste doublement chainée.
Tas:
- Liste doublement chainée des racines;
- Pointeur min à la racine de clef minimale;
- Un entier pour la taille.

Analyse du coût des opérations: Coût amorti par potentiel


Fonction de potentiel. Pour un tas de Fibonacci H:
Φ(H) = c* ( a(H) + 2 m(H) )
a(H): nombre d’arbres dans H
m(H): nombre de noeuds marqués vrais dans H
c*: une constante
Opérations de tas de Fibonacci
Implémentation des opérations de tas fusionnable:
(pour simplifier le pseudo-code: on ne montre pas les instructions de mise à jour de la taille du tas et
des degrés des noeuds)
INSERER-TAS (Tas H, Tclef c) insère la clef c dans le tas H
créer un arbre A contenant un seul noeud de clef c marqué faux
concatener A à la liste des racines de H
if (c < H.min.clef) H.min ← A

MIN (Tas H) : Tclef retourne la clef minimale de H


return H.min.clef

FUSIONNER (Tas H1, Tas H2) : Tas renvoie l’union des Tas H1 et H2

H ←Creer-Tas() //crée un tas de Fibonacci vide


H ← concatenation des listes H1 et H2
if (H1.min.clef < H2.min.clef) then H.min ← H1.min else H.min ← H2.min endif
return H
Opérations de tas de Fibonacci

EXTRAIRE-MIN (Tas H) : Tclef


Supprime et retourne la clef minimale du tas H
Consolide le tas ensuite pour que les degrés des racines soit distincts
c ←H.min.clef
Concatener la liste des racine de H avec la liste des fils de H.min
Supprimer la racine H.min
Consolider(H); //consolide et recalcule le min
return c
Opérations de tas de Fibonacci
Opérations auxiliaires:

Consolider( Tas H ) Relier (Tas H, Noeud x, Noeud z): Noeud


Idée: if( x.clef < z.clef ) then échanger x et z endif
répéter
détacher x de la liste des racines de H et
- Trouver deux racines x et z dans H recompacter la liste
ayant degré(x) = degré(z) ajouter x aux fils de z; marquer x faux
- Relier (H, x, z) retourner z

jusqu’à ce que toutes les racines aient des


degrés distincts deux à deux

Remarque: On montrera plu tard que Relier préserve la structure de tas de Fibonacci de H
degré maximal de H après consolidation: c log n après consolidation a(H)≤ c log n +1

Exercice. Implémenter Consolider en temps linéaire (coût réel) dans la taille de la liste des racines
Opérations de tas de Fibonacci
DIMINUER-CLEF (Tas H, Noeud x, Tclef c) affecte à l'élément x du tas H la nouvelle clef c ≤ x.clef
x.clef ← c; y ← x.père
if ( y ≠NIL and y.clef > x.clef) then
courant ← x
repeat
Couper(H, courant)
courant ← courant.père
until ( courant.marqué = false or racine(courant) )
if ( not(racine(courant)) ) then courant.marqué ← true endif
if ( c < H.min.clef ) then H.min ← x endif
endif
Couper (Tas H, Noeud x)
détacher x de la liste des fils de x.père et recompacter la liste
ajouter x à la liste des racines de H
x.marqué ← false
Opérations de tas de Fibonacci
SUPPRIMER (Tas H, Noeud x)
Diminuer-Clef (H, x, -∞)
Extraire-Min(H)

Exercice.
Montrer que les opérations de tas fusionnable préservent la structure de tas de Fibonacci.

Exercice.
Analyser le temps d'exécution (coût réel et coût amorti par potentiel) des opérations de tas
fusionnable sur les tas de Fibonacci.

Exercice.
Montrer que quand les seules opération admises sur les clefs d’un tas fusionnable sont des
comparaisons (comme c’est le cas pour les implémentations proposés ici), il est impossible
d’obtenir coût amorti O(1) pour toutes les opérations de tas fusionnable.
Tri rapide (Quicksort)
Paradigme “divide et impera”, même principe que le tri-fusion, mais

‣ phase de partition plus complexe


‣ pas de fusion

Tri-rapide ( Tableau T, Entier inf, Entier sup)


if( inf < sup) then
q = Partition(T, inf, sup)
Tri-rapide(T, inf, q-1)
Tri-rapide(T, q+1, sup)
endif

Partition (T, inf, sup) réorganise T de telle sorte que T[inf, q-1] ≤ T[q] ≤ T[q+1, sup]
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x
....

x=4
3 0 1 7 5 6 9 2 8 4

inf i j sup
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x

if ( T[ j ] > x ) then j ← j+ 1
.... x=4
3 0 1 7 5 6 9 2 8 4

inf i j sup
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x

if ( T[ j ] > x ) then j ← j+ 1
.... x=4
3 0 1 7 5 6 9 2 8 4

inf i j sup
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x

if ( T[ j ] > x ) then j ← j+ 1
x=4
else échanger T[ i+ 1 ] et T[ j ]
3 0 1 7 5 6 9 2 8 4
i← i+ 1; j ← j+ 1
endif inf i j sup
endwhile
....
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x

if ( T[ j ] > x ) then j ← j+ 1
x=4
else échanger T[ i+ 1 ] et T[ j ]
3 0 1 2 5 6 9 7 8 4
i← i+ 1; j ← j+ 1
endif inf i j sup
endwhile
....
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x

if ( T[ j ] > x ) then j ← j+ 1
x=4
else échanger T[ i+ 1 ] et T[ j ]
3 0 1 2 5 6 9 7 8 4
i← i+ 1; j ← j+ 1
endif inf i j , sup
endwhile
....
Tri rapide - Partition
Partition (T, inf, sup)
fixe un pivot: x← T[sup]
i ← inf -1; j ← inf
while ( j ≤ sup-1) do
maintient l’invariant de boucle suivant:
le tableau T[ inf...j-1 ] est partitionné en deux sous-tableaux (potentiellement vides) :
T[ inf...i ] contient les clefs de T[ inf...j-1 ] qui sont ≤ x
T[ i+1...j-1 ] contient les clefs de T[ inf...j-1 ] qui sont > x

if ( T[ j ] > x ) then j ← j+ 1
x=4
else échanger T[ i+ 1 ] et T[ j ]
3 0 1 2 4 6 9 7 8 5
i← i+ 1; j ← j+ 1
endif inf i j , sup
endwhile
échanger A[i+ 1] et A[sup]
return i+ 1
Analyse du tri rapide
Soit T un tableau de taille n. Coût du tri rapide: C(T) = C(T1) + C(T2) + Θ(n)
où (T1, T2) est la partition de T générée par Partition et Θ(n) est le coût de la partition
Arbre des appels récursifs sur T: Θ(n) O(n)

Θ(n1) Θ(n2) O(n)

Θ(n3) Θ(n4) Θ(n5) Θ(n6) O(n)


...

O(n)

...
O(n)

‣ Contribution de chaque niveau à C(T): O(n)


‣ hauteur: O(n) (la taille ni décroît d’au moins 1 d’un noeud à son fils)

Pour tout tableau T de taille n, C(T) est O(n2)


Analyse du tri rapide
Cas pire: à chaque étape, partition en deux sous-tableaux de taille 0 et n-1:

Θ(n)

Θ(n-1)

....
Θ(1)

‣ Coût: Θ(n+n-1 + ...+ 1) = Θ(n2) (atteint la borne supérieure)

‣ Obtenu quand le pivot est toujours le min ou le max


(Exemple: tableau déjà trié)
‣ Prestations comparables au tri par insertion
Analyse du tri rapide
Si à chaque étape la partition génère deux sous-tableaux de taille n/2 et n/2 -1 :

Θ(n) Θ(n)

Θ(n/2) Θ(n/2) Θ(n)

Θ(n/4) Θ(n/4) Θ(n/4) Θ(n/4) Θ(n)


...

...
‣ Contribution de chaque niveau: Θ(n)
‣ hauteur : Θ(log n) Coût: Θ( n log n )

Exercice: démontrer que pour tout tableau T de taille n le coût du tri rapide est Ω(n log n)
Coût dans le meilleurs de cas: Θ( n log n )
Analyse du tri rapide
Cas meilleur: obtenu également quand chaque partition utilise la même proportion α (0 < α <1)

Θ(n) O(n)

Θ(α n) Θ((1-α) n) O(n)

Θ(α(1-α) n) Θ(α2 n)
... Θ((1-α)2 n) Θ(α(1-α) n) O(n)

O(n)

...
O(n)

‣ Contribution de chaque niveau: O(n)


‣ hauteur : O(log1/β n) où β=max(α, 1-α) Coût O(n log n) Θ (n log n)
Analyse du tri rapide
Cas moyen
Pour chaque tableau T, soit N(T) la somme des tailles des tableaux associés au noeuds de l’arbre
des appels récursifs du tri rapide
n

n1 n2

n3 ... n4 n5 n6

...
N(T) = n + ∑ ni
Coût du tri rapide sur T: proportionnel à N(T)
On montre que N(T) pour un tableau aléatoire T de taille n est en moyenne O(n log n)
coût moyen O(n log n) (par la borne inférieure) coût moyen Θ(n log n)
Analyse du tri rapide
Hypothèses:
‣ toutes les permutations des rangs des n clefs du tableau T en entrée sont équiprobables
‣ les clefs du tableau T en entrée sont toutes distinctes

Variables aléatoires sur une entrée de taille n:

Nn: valeur de N(T)


P: rang de la clef pivot ( i.e. T[n] )
T1, T2: tableaux générés par la partition de T
Analyse du tri rapide
Sachant P = i :
T1 est de taille i-1 et il est tiré uniformément parmi toutes le permutations de 1...i-1
T2 est de taille n-i et il est tiré uniformément parmi toutes le permutations de 1...n-i

Solution de la récurrence: E[Nn] ≤ a n log n pour tout n ≥ 2 et a ≥ 4


Analyse du tri rapide

Pour n=2: E[N2] = 3 ≤ a ·2 ·log 2 (parce que a ≥ 4)


Pour n>2:

On montrera plus tard que


Analyse du tri rapide
Analyse du tri rapide
Borne pour la sommation:
Tri rapide randomisé
Modification de Partition(T, inf, sup):
‣ tirer uniformément la position p du pivot parmi [inf..sup] et échanger T[p] et T[sup]
Coût moyen pour un input fixé T de taille n: Θ(n log n) - Analyse inchangée:
Variables aléatoires sur une entrée fixée T:
Nn: valeur de N(T)
P: rang de la clef pivot - Pr[ P=i ] =1/n pour tout i=1..n
T1, T2: tableaux générés par la partition de T

Les choix aléatoires sur T1 et T2 sont indépendants entre eux, et indépendants de P


Tri en temps linéaire
Algorithmes de tri qui utilisent:
‣ pas uniquement des comparaisons de clefs
‣ et/ou des hypothèses sur l’entrée
la borne inférieure Ω( n log n ) ne s’applique pas.

• Tri par dénombrement (Counting sort)


• Tri par base (Radix sort)
• Tri par paquets (Bucket sort)

Tous en temps Θ(n) (cas pire) sous certaines assomptions sur l’entrée
Tri par dénombrement (Counting sort)
Assomption: les clefs en entrée sont dans l'intervalle 0..k
Idée: Pour chaque clef c dans T, compter le nombre i de clefs < c insérer c en T0[ i+1]

Modifié pour tenir compte des doublons:

Tri-dénombrement (Tableau T, Tableau T0 , Entier k)


Créer un tableau P[0..k]
Pour toute clef c dans T calculer en P[c] le nombre d’occurrences de clefs c’ ≤ c dans T
// ∀ c en T P[c] est ici la position de la dernière occurrence de c dans T0
for ( j= T.taille to 1) do
// insérer toutes les clefs de T dans leur position finale dans T0 (stockée dans P)
clef ← T[j]; pos ← P[clef];
T0[pos] ← clef
P[clef] ←P[clef]-1 //position de la prochaine occurrence de clef à insérer dans T0
endfor

Stabilité: l’ordre d’origine des éléments avec la même clef est préservé
(obtenue grâce à la boucle de T.taille à 1)
Tri par dénombrement
Temps d'exécution: Θ(k+n) k: taille du domaine des clefs, n: taille du tableau

Calcul des positions P[0..k] en temps Θ(k+n):


Initialiser P[0..k] à 0
Incrémenter P[c] à chaque occurrence de la clef c dans T (une passe sur T)
// ici P[c] contient le nombre d’occurrence de la clef c dans T
Pour j=1..k remplacer P[j] par la somme de P[0..j] (une passe sur P)
// ici P[c] contient le nombre d’occurrences de clefs ≤ c dans T

Si k=O(n) : tri en temps Θ(n)

Ce n’est pas un tri par comparaisons. L’algorithme:


‣ s’autorise à compter (pas de comparaisons)
‣ utilise l'hypothèse que le domaine des clefs est 0..k
Tri par dénombrement et tri par base
‣ k ≤ n ou k=O(n) avec des “petites constantes” → Tri par dénombrement

‣ Plus fréquemment k >> n


‣ k=2b, où b est le nombre de bits d’une clef
‣ assomption raisonnable: b proportionnel à log n
‣ k=O(n) , mais les constantes cachées peuvent être grandes

‣ Dans ces cas le tri par dénombrement est inefficace → Tri par base

(utilise un tri stable comme sous-routine)


‣ Idée du tri par base (clefs numériques)
‣ domaine des clefs → “grand”
‣ domaine de chaque chiffre des clefs→ “petit”
‣ Utiliser le tri par dénombrement (ou équivalent) sur chaque chiffre des clefs
Tri par base (Radix sort)
Assomption:
Les éléments du tableau T sont des nombres à d chiffres, le chiffre 1 étant le plus significatif

Tri-par-base(Tableau T, Entier d)
for (i =d to 1) do
trier T sur le chiffre i avec un algorithme stable
endfor

Invariant de boucle: à la fin de chaque itération, T est trié sur les chiffres i..d
(grâce à la propriété de stabilité)

chiffre 1 2 3

5 2 0
3 6 9
7 6 4
2 5 0
3 2 4 T
Tri par base (Radix sort)
Assomption:
Les éléments du tableau T sont des nombres à d chiffres, le chiffre 1 étant le plus significatif

Tri-par-base(Tableau T, Entier d)
for (i =d to 1) do
trier T sur le chiffre i avec un algorithme stable
endfor

Invariant de boucle: à la fin de chaque itération, T est trié sur les chiffres i..d
(grâce à la propriété de stabilité)

chiffre 1 2 3

5 2 0
2 5 0
7 6 4
3 2 4
3 6 9 T
Tri par base (Radix sort)
Assomption:
Les éléments du tableau T sont des nombres à d chiffres, le chiffre 1 étant le plus significatif

Tri-par-base(Tableau T, Entier d)
for (i =d to 1) do
trier T sur le chiffre i avec un algorithme stable
endfor

Invariant de boucle: à la fin de chaque itération, T est trié sur les chiffres i..d
(grâce à la propriété de stabilité)

chiffre 1 2 3

5 2 0
3 2 4
2 5 0
7 6 4
3 6 9 T
Tri par base (Radix sort)
Assomption:
Les éléments du tableau T sont des nombres à d chiffres, le chiffre 1 étant le plus significatif

Tri-par-base(Tableau T, Entier d)
for (i =d to 1) do
trier T sur le chiffre i avec un algorithme stable
endfor

Invariant de boucle: à la fin de chaque itération, T est trié sur les chiffres i..d
(grâce à la propriété de stabilité)

chiffre 1 2 3

2 5 0
3 2 4
3 6 9
5 2 0
7 6 4 T
Tri par base
• Temps d'exécution: Θ( d (n+k) )
n: taille du tableau
d: nombre de chiffres des clefs
k: nombre de valeurs possibles de chaque chiffre

• Sous les hypothèses:


d constant,
k =O(n) , “petites” constantes cachées
le tri par base travaille en temps Θ(n) et il est efficace en pratique

• k=O(n), petites constantes cachées : hypothèse raisonnable (k est le domaine d’un chiffre)

• Mais d pourrait ne pas être constant :


clefs représentées en binaire: d = b (nombre de bits des clefs), k=2 - b=O(log n)
Tri par base
Tri par base sur des clefs en binaire (b bits)

• On suppose b > log n (sinon utiliser le tri par dénombrement : 2b < n)


partitionner les b bits en blocs de r bits (un bloc = un chiffre)

1 2 3

1 0 0 1 1 1 1 0 0 0 1 1 0 1 0 d = b/r k = 2r

• Temps d'exécution : Θ( (b/r) (n+2r) )

• En choisissant r≈log n : temps d'exécution Θ(n) (en considérant b=O(log n) )

• Très efficace quand la taille des clefs n’est pas trop grande. Sinon le tri rapide peut avoir des
meilleures performances (malgré la borne asymptotique)
Tri par paquets (Bucket sort)
Assomption: les clefs en entrée sont dans l'intervalle continu [0,1[

• l'intervalle [0,1[ est partitionné en n intervalles égaux (les “paquets”) d’indice 0,..,n-1

• chaque clef c ∈[0,1[ est dans l'intervalle d’indice nc

Tri-par-paquets (Tableau T)
créer un tableau B de n listes où n= T.taille
insérer chaque clef c de T dans la liste B[ n c ]
Pour tout i=0..n-1 trier la liste B[i] ( tri par insertion )
concaténer les listes B[0],...,B[n-1]

Complexité

Cas pire: toutes les clefs sont dans le même intervalle, Θ(n2) (insertion sort)
Cas moyen: O(n) en supposant chaque clef tirée indépendamment et uniformément dans
l'intervalle [0,1[
Preuve du cas moyen. Exercice
Tri externe
• Éléments à trier disponibles en mémoire secondaire (trop volumineux pour être tous en
mémoire centrale en même temps)
• Coût mesuré en nombre d'accès à la mémoire externe
• Différents paradigmes de tri selon le type d'accès (séquentiel/direct)

Supports à accès séquentiel


‣ La plupart des mémoires externes sont aujourd’hui à accès direct, mais l'accès séquentiel est
souvent plus efficace
‣ la conception d'algorithmes qui minimisent le nombre de “passes” sur la mémoire externe est
encore aujourd’hui un sujet de recherche
‣ 1 seule passe: algorithmes “streaming” pour le traitement de flux de données
Tri en mémoire externe à accès séquentiel
Tris en mémoire externe à accès séquentiel
‣ Assomptions
‣ Les éléments à trier sont stockés en mémoire externe et peuvent être lus seulement de
façon séquentielle
‣ La mémoire interne ne peut pas contenir tous les éléments à trier en même temps
‣ On fait abstraction du buffer d’entrée/sortie de la mémoire externe

‣ Variations du tri fusion (la fusion peut être réalisée efficacement en accès séquentiel)
Fusion en mémoire externe à accès séquentiel
Segments à fusionner stockés sur des supports distincts:

S0 4 6 7 8 9

S1 1 2 5 6 8 9

S2 3 7 7 8

un support de sortie S3 1 2 3 4 5

algorithme de fusion classique:


‣ recopier sur le support de sortie le minimum des éléments sous les têtes de lecture des
supports d’entrée
‣ avancer la tête de lecture sur le support contenant le minimum et sur le support de sortie
Tri en mémoire externe à accès séquentiel
Principe:
‣ Charger en mémoire interne des “segments” du fichier à trier
‣ Trier les segments en mémoire interne et les recopier en mémoire externe
‣ “Fusionner” les segments triés en mémoire externe deux à deux (ou k à k) en créant des
segments plus gros
‣ Réitérer les fusions jusqu’à obtenir un seul segment trié
Deux phases:
‣ Création des segments triés (monotonies)
‣ Répartition des segments triés sur des supports externes et fusion
Techniques (orthogonales)
‣ Création des segments triés: Segments de même taille / Sélection et remplacement
‣ Répartition et fusion des segments triés: Tri équilibré / Tri polyphasé
Création des segments triés
Soit m l’espace disponible en mémoire centrale (nombre d'éléments)
Soit S le support externe contenant les N éléments à trier
Segments de même taille

Tant qu’il reste des éléments à lire sur S


➡ lire en mémoire interne les m prochains éléments de S
(ou ceux qui restent si moins que m)
➡ trier les m éléments en mémoire centrale
➡ écrire le segment trié sur un support externe
(selon la stratégie de répartition choisie)

Tous les segment triés sont de taille m


Coût:
• 2N opérations d’entré-sortie ( N lectures, N écritures )
• N/m tris internes
Création des segments triés
Sélection et remplacement
Permet de créer des séquences triées plus longues que m;

➡ charger les premiers m éléments de S en mémoire centrale; aucun élément n’est marqué;
➡ tant que il reste des éléments à lire sur S
➡ extraire le minimum min des éléments non marqués en mémoire centrale
et l’ajouter au segment trié courant (sur un support externe)
➡ charger le prochain élément e de S (remplace le minimum extrait)
➡ si e < min (alors e ne peut pas faire partie du segment en phase de construction)
marquer e
➡ si les éléments en mémoire centrale sont tous marqués,
les démarquer et commencer un nouveau segment
Sélection et remplacement - exemple

création du premier segment

4 6 3 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0
Sélection et remplacement - exemple

4 6 3 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3
Sélection et remplacement - exemple

4 6 9 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3
Sélection et remplacement - exemple

4 6 9 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4
Sélection et remplacement - exemple

2* 6 9 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4
Sélection et remplacement - exemple

2* 6 9 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6
Sélection et remplacement - exemple

2* 5* 9 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6
Sélection et remplacement - exemple

2* 5* 9 7 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7
Sélection et remplacement - exemple

2* 5* 9 8 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7
Sélection et remplacement - exemple

2* 5* 9 8 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7 8
Sélection et remplacement - exemple

2* 5* 9 4* mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7 8
Sélection et remplacement - exemple

2* 5* 9 4* mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7 8 9
Sélection et remplacement - exemple

2* 5* 3* 4* mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7 8 9
Sélection et remplacement - exemple

création du prochain segment

2 5 3 4 mémoire centrale

S 4 6 3 7 9 2 5 8 4 3 2 6

S0 3 4 6 7 8 9

S1
Sélection et remplacement

Coût:
• 2N opérations d’entré-sortie ( N lectures, N écritures )
• O(N log m ) en mémoire interne
( utilise un tas pour les m éléments en mémoire centrale )
Répartition des segments triés et fusion
Tri équilibré

• Les segments triés sont repartis uniformément sur k supports distincts S0,.., Sk-1

S0

Sk-1

répartition uniforme: le i-eme segment trié, est placé sur le support S(i mod k)

• k autres supports sont initialement vides

T0

Tk-1

• Supports de lecture: Li ←Si, i=0, .,.k-1


• Supports d'écriture: Ei ← Ti, i=0,.., k-1
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

L0

Lk-1

E0

Ek-1
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

L0 s

Lk-1 s’

E0 fusion(s,s’)

Ek-1
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

L0 s

Lk-1 s’

E0

Ek-1 fusion(s,s’)
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

L0 s

Lk-1 s’

E0 fusion(s,s’)

Ek-1
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

L0

Lk-1 s

E0

Ek-1 s
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

E0

Ek-1

L0

Lk-1
Répartition des segments triés et fusion
Une phase du tri équilibré:
répéter
fusionner les segments courants de L0, ..Lk-1 et placer le segment fusionné en alternance sur E0..Ek-1
jusqu’à ce que tous les segments de L0, ..Lk-1 ont été lus
réinitialiser tous les supports; échanger L0, ..Lk-1 avec E0..Ek-1

Tri équilibré: réitérer les phases jusqu’à ce qu’il reste un seul segment trié

Coût: O(N log M) où M est le nombre initial de segments :


• O(log M) phases (chaque phase réduit le nombre de segments de i à i/k)
• Chaque phase: 2N opérations d’entrée/sortie ( N lectures, N écritures )
Répartition des segments triés et fusion
Tri polyphasé ( vs. tri équilibré )

• Nombre de phases logarithmique, mais chaque phase est optimisée: une phase ne fait pas
une passe complète sur les données

• Meilleure utilisation des supports: nécessite en général de moins du supports

• Moins de “rewind”: seulement deux supports par phase sont re-initialisés


Tri polyphasé - idée
• Le segments triés sont repartis sur p supports: un certain nombre Fi de Fibonacci de segments
sur chaque support

• Un support auxiliaire (initialement vide) est utilisé

Exemple p=2 Fk + Fk-1 = M ( nombre initial de segments )

nombre de segments (à lire)

S1 Fk S1 Fk-2 = Fk - Fk-1
une phase
S2 Fk-1 S2 0
S3 0 S3 Fk-1

Un phase (Fk-1 fusions):


tant qu’il reste des segments de S2 à lire
fusionner les segments courants de S1 et S2; écrire le segment fusionné sur S3;
réinitialiser S2 (pour écriture) et S3 (pour lecture)
Après chaque phase:
distribution de Fibonacci “parfaite” (d’indice décroissant) sur deux des trois supports
Tri polyphasé - idée

nombre de segments (à lire)

1 Fk une phase
1 Fk-1
2 Fk-1 2 Fk-2
3 0 3 0

On renomme les supports à chaque phase


1 : le support contenant le nombre maximale de segments
1..p : supports numérotés par ordre décroissant du nombre de segments
Tri polyphasé - idée
Plusieurs phases

1 Fk 1 Fk-1 1 Fk-2 1 1 F1 1 1 F0
2 Fk-1 2 Fk-2 2 Fk-3 ... 2 1 F0 2 0
3 0 3 0 3 0 3 0 3 0

Apres k phases il y aura un seul segment trié sur un des supports, et l'algorithme termine
Tri polyphasé
Le cas général ( p≥2 supports ) utilise la suite de Fibonacci d’ordre p

La suite de Fibonacci d’ordre 5: 0 0 0 0 1 1 2 4 8 16 31 61 120 .....


Distribution de Fibonacci parfaite d’indice i sur p+1 supports:

nombre de segments (à lire) Exemple

1 nombre de segments (à lire) 1 61


2 2 59
. . 55
. . 47
p p 31
p+1 0 p+1 0
Tri polyphasé
Une phase du tri polyphasé sur un distribution de Fibonacci parfaite:
nombre de segments (à lire)

1 nombre de segments (à
lire)
2
une phase

...
...

.
.
p 0

p+1 0

Un phase (Fi-1 fusions):


tant qu’il reste des segments du support p à lire
fusionner les segments courants des supports 1, .., p; écrire le segment fusionné sur le
support p+1
réinitialiser le support p (pour écriture) et p+1 (pour lecture)
Tri polyphasé
Une phase du tri polyphasé sur un distribution de Fibonacci parfaite:
nombre de segments (à lire)

1 nombre de segments (à 1
lire)
2 2
une phase
...

. 3

...
. .
p p
p+1 0 p+1 0

Après une phase: distribution de Fibonacci parfaite d’indice i-1


Tri polyphasé
L'étape avec distribution d’indice i est dénotée i - (p-1), i≥ p-1

Nombre de segments sur chaque support à chaque étape:

étape i-(p-1) i-p ... 1 0


support

2 1 0
...

...

...

...

...
p 0

p+1 0 0 0 0 0

À l'étape 0 il reste un seul segment trié, et le tri est terminé


Notation. fq(k) : nombre de segments sur le support q à l'étape k
Tri polyphasé
étape i-(p-1):
Nombre de segments sur le support 1:
1 nombre de segments (à
lire)
2
.
.
Nombre total de segments sur tous les supports
p
p+1 0

Lemme Pour tout j > 0 T( j )=T( j -1 ) + ...+ T( j-p ), si on pose par convention T( j )=1 pour
tout j < 0
Preuve. Exercice
Étape initiale: l'étape n telle que T(n) = M (nombre de segments initiaux)
Si T(n-1) < M <T(n) ajouter des segments “fantômes”
Tri polyphasé
Étapes et phases de l’algorithme phase = n - étape

Nombre de segments sur chaque support à chaque étape et phase:

étape n n-1 ... 1 0


support

1 f1(n) f1(n-1) ... 1 1


...
2 f2(n) f2(n-1) 1 0
...

...

...

...

...
...
p fp(n) fp(n-1) 1 0

p+1 0 0 0 0 0

phase 0 1 n
Analyse du tri polyphasé
Taille des segments:
unité = taille des segments initiaux (supposés tous de la même taille)
Lemme
Les tailles des segments sur les supports 1,.., p à la phase j ( étape n-j ) sont les suivantes:

étape i = n-j
1 T(j)
2 T(j-1)
...

p T(j-p+1)
phase j j = 0,.., n

Preuve par induction sur j (Exercice)


Analyse du tri polyphasé
Nombre d'opérations d’entrée/sortie en terme de nombre de transferts
1 transfert : m lecture et m écritures (m: taille des segments initiaux)
Nombre d'opérations d’entrée/sortie = 2m · nombre de transferts

Nombre de transferts à la phase j ( étape i = n-j ) = f1(n-j-1)· T(j+1) j=0,..n-1 :


➡ fp(i) = f1(i-1)= f1(n-j-1) fusions
➡ Chaque fusion transfère un segment de taille T(j) + ...+ T(j-p+1) =T(j+1)

Nombre de segments Taille des segments


étape i étape i -1 étape i
1 f1(i) f1(i-1) 1 T(j)
2 f2(i) f2(i-1) 2 T(j-1)

p fp(i) fp(i-1) p T(j-p+1)


phase j phase j+1 phase j
Analyse du tri polyphasé
Nombre total de transferts de l’algorithme (n phases):

Analyse du nombre de transferts et du nombre de phases:


‣ On étudie les suites
f1(i) i = 0, 1, ...
T(i) i = 1, 2 ...
à partir de leurs fonctions génératrices

‣ La fonction génératrice de la suite de Fibonacci:


Analyse du tri polyphasé
‣ On en dérive les fonctions génératrices de f1(i) et T(i) (Exercice):

ˆ a des zéros de module <1


‣ Le dénominateur de T(z)
T(i) = O(ai ), a >1 (inverse des zéros)

‣ T(n) = M (nombre initial de segments)

nombre de phases de l’algorithme n = O(log M)


Analyse du tri polyphasé

‣ Nombre total de transferts sur n phases

Θ(n) est le coefficient de zn dans

est la fonction génératrice de la suite Θ(n)

‣ Des propriétés de on peut dériver :

Pour plus de détails voir Knuth. “Sorting and Searching” 5.4.2 Ex. 5-7

‣ Nombre total de transferts du tri polyphasé sur M segments initiaux: O(M log M)
‣ Nombre total d’entrées/sorties pour le tri de N éléments: O(N log M) (log M passes)
Bibliographie
1) D. Knuth. The Art of Computer Programming,Volume 3: Sorting and Searching (2nd Edition)
Addison-Wesley

2) Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein.


Introduction to Algorithms. 3e édition, The MIT Press 2009

3) Danièle Beauquier, Jean Berstel, Philippe Chrétienne.


Éléments d’Algorithmique.
Masson, 1992. http://www-igm.univ-mlv.fr/~berstel/Elements/Elements.html

4) C. Froidevaux, M.Gaudel, M. Soria. Types de données et algorithmes. Ediscience, 1993.

Vous aimerez peut-être aussi