Vous êtes sur la page 1sur 65

Algorithmique et structures de données

SOMMAIRE

Notions d’algorithme et de complexité 2

Algorithmes de recherches et de tri 6

Piles, Files et Listes 13

Les arbres 31

Les graphes 52

Méthodes de hachage 62

M. E. RIFFI Page 1 2021/2022


Algorithmique et structures de données

I- NOTIONS D’ALGORITHME ET DE COMPLEXITE

1- Notion d’algorithme
1-1- Définition

Selon l’Encyclopedia Universalis un algorithme est la spécification d’un schéma de calcul,


sous forme d’une suite finie d’opérations élémentaires obéissant à un enchaînement déterminé.

Historiquement, la notion d’algorithme a précédé celle d’ordinateur : bien avant Euclide


(mathématicien grec du 3e siècle), on peut faire remonter aux babyloniens de l’époque de
Hammurabi (1800 av J.C.) les premières formulations de règles précises pour la résolution de
certains types d’équations.
Le mot algorithme lui-même est plus récent : il vient du nom du mathématicien arabe Abu Ja’Far
Mohammed Ibn Mûsâ al-Khowâ-rismî (820 après J.C.) dont l’ouvrage d’arithmétique utilisant les
règles de calcul sur la représentation décimale des nombres, montra l’inutilité des tables et
abaques, et eut une influence capitale pendant plusieurs siècles.

− Un algorithme est une suite finie de règles à appliquer dans un ordre déterminé à un nombre
fini de données pour arriver en un nombre fini d’étapes, à n certain résultat, et cela
indépendamment des données.

- Un algorithme est le processus de transformation de données (ou entrées) en résultats (ou


sorties).

De plus, les algorithmes que l'on considère sont déterministes : étant donné un algorithme,
toute exécution de cet algorithme sur les mêmes données donne lieu à la même suite d'opérations.
1-2- Expression des algorithmes
Un algorithme permet de résoudre un problème donné.
Un algorithme doit être exprimé dans un langage de programmation pour être compris et
exécuté par un ordinateur. Cependant, il faut bien comprendre qu'un algorithme est indépendant
du langage de programmation utilisé. L'algorithme d'Euclide programmé en Pascal, en C ou en
Lisp, reste toujours l’algorithme d'Euclide.
Algorithmes + structures de données = Programme [Wirth]
1-3- Notion de programme
Synonymes
- Programme, application, logiciel.
Objectifs des programmes
- Utiliser l’ordinateur pour traiter des données afin d’obtenir des résultats
- Abstraction par rapport au matériel.
Un programme est une suite logique d’instructions que l’ordinateur doit exécuter
- Chaque programme suit une logique pour réaliser un traitement qui offre des services
(obtention des résultats souhaités à partir de données).
- Le processeur se charge d’effectuer les opérations arithmétiques et logiques qui
transformeront les données en résultats.
Programmes et données sont sauvegardés dans des fichiers
- Instructions et données doivent résider en mémoire centrale pour être exécutées.

2- Notion de complexité
La notion de complexité des algorithmes permet de donner un sens précis à l’idée intuitive
selon laquelle un programme a un coût. Par coût, on entend en général le temps d’exécution (T.e)
et éventuellement la quantité de mémoire requise. Nous ne nous intéresserons qu’au premier
aspect et nous allons étudier comment évaluer le temps d’exécution des programmes.

M. E. RIFFI Page 2 2021/2022


Algorithmique et structures de données

2-1- Temps d’exécution (T.e) d’un programme


Le temps d’exécution (T.e) dépend de plusieurs facteurs:
1. Les données :
Il est clair que trier un tableau à 10 nombres est plus rapide que de trier un tableau à
1000 nombres.
Exemple :
Notons par T(n) le t.e. d’un algorithme sur des données de taille n tel que :
T(n)=C.n2 (C est une constante)
 On estime à C. n2 le nombre d’unités de temps nécessaires à un ordinateur pour
exécuter le programme.
2. La quantité de code généré par le compilateur :
La quantité de code (ou nombre d’instructions) et la vitesse des instructions de la
machine (fréquence).
En général on suppose que les instructions élémentaires (code) qui composent un
calcul (affectations, tests, ..) consomment un temps constant.
Ainsi évaluer le t.e. d’un algorithme, c’est compter le nombre d’instructions
élémentaires au cours d’une exécution.
T(n)=Cf(n), où f(n) représente une évaluation du nombre d’instructions élémentaires à
exécuter pour des données de taille n.
Exemple : f(n) = n2
t.e de l’algorithme est proportionnel à n2.
C dépend de la machine (fréquence).
3. La nature et la vitesse des instructions de la machine utilisée pour exécuter le
programme.
4. La complexité en temps de l’algorithme :
Elle permet de comparer plusieurs algorithmes qui résolvent le même problème.
Exemple :
1. Soit à écrire une fonction qui calcul le plus grand diviseur d’un entier
donnée.

Entier pgd1(Entier n)
Entier i
Debut
in–1
Tant que ((n mod i) != 0) faire
i i -1
ftant que
retourner i
Fin

2. Une autre méthode est fondé sur le fait que le résultat cherché est n/i, où i
est le plus petit diviseur (ppd) de n supérieur à 1.
 Si n est premier ce ppd est 1,
 Sinon il est compris entre 1 et Vn

Entier pgd2(Entier n)
Entier i, j
Debut
i2
Tant que ((i< int(sqrt(n))) et ((n mod i) !=0)) faire
ii+1

M. E. RIFFI Page 3 2021/2022


Algorithmique et structures de données

ftant que
si ((n mod i)=0) alors
jn div i
sinon j1
fsi
retourner j
Fin

pgd1 a une boucle de 1 à n, donc plus long que pgd2 qui a une boucle de 2 à Vn
Par exemple si n =1010, pgd1 est impraticable alors que pgd2 conduit à 105 instructions
élémentaires.

2-2- Différentes complexités


En pratique le t.e. d’un algorithme dépend de la taille de données et leur valeur
précise.
Exemple :
 Pour la recherche séquentielle d’un élément dans une liste de n éléments le t.e.
varie selon que l’élément se trouve en tête ou en fin de liste.
 Pour le tri d’une liste le t.e. varie selon que la liste est déjà triée ou non.

a) Complexités dans le pire des cas (Tmax(n)) : temps d’exécution maximale d’un
algorithme pour des données de taille n.
b) Complexités en moyenne des cas (Tmoy(n)) : temps d’exécution moyen d’un
algorithme sur tous les jeux de données de taille n.
c) Complexités dans le meilleur des cas (Tmax(n)) : temps d’exécution minimale
d’un algorithme pour des données de taille n.

2-3- Calcul de la complexité dans le pire des cas


2-3-1- Règle générale
1) Le t.e. d’une affectation ou d’un test peut en général être compté
comme une constante c.
2) Le t.e. d’une séquence d’instructions est la somme des t.e. des
instructions qui la composent.
3) Le coût d’un branchement conditionnel est égale à la somme du coût du
test (c pour un test simple) et du plus grand des coûts des deux
alternatives.
4) Le t.e. d’une boucle est la somme sur tous les tours de la boucle, de la
somme du t.e. du corps de la boucle et le t.e. de la condition de sortie de
boucle (en général c).

2-3-2- Complexité asymptotique


Il s’agit d’estimer le t.e. des algorithmes pour de grandes données, afin de
comparer la complexité de deux algorithmes effectuant des tâches similaires.
2-3-2-1- Définition du grand O
Soient T(n) et f(n) deux fonctions de IN  IN
On dit que T(n) est dominé asymptotiquement par f(n),
si (n0, c) / n≥ n0 T(n)≤c.f(n)
c.à.d. T(n) ≤c quand n  ∞
f(n)
On note T(n) = O(f(n))

M. E. RIFFI Page 4 2021/2022


Algorithmique et structures de données

On dit que T(n) est O(f(n)).


Remarque :
Si A un algorithme de t.e. T(n) et f(n) : IN  IN
Si T(n) est O(f(n)), on dira que A est de complexité O(f(n)).

Exemple :
T(n) = 5n3 +3n2 (avec n0 = 0 et c=5)
= O(n3)
Si T(n) est le t.e. de l’algorithme A, alors : A est donc O(n3).
2-3-2-2- Règles de calcul
On applique les règles usuelles sur le comportement à l’infini des fonctions
numériques.
1. Dans un polynôme, seul le terme de plus haut degré compte.
Exemple : T(n) = n3 + 7n2 +77n est O(n 3)
2. L’exponentielle l’emporte sur la puissance et la puissance sur le log.
Exemples :
T(n) = 2n + n22 + 5n2 + 2n est O(2n)
T(n) = 5 Log(n) + 5n est O(n).
3. Si T1(n) = O(f(n)) et T2(n) = O(g(n))
Alors T1(n) + T2(n) = O(Max(f(n), g(n))
Et T1(n) . T2(n) = O(f(n).g(n))

2-3-2-3- Exemple
Supposons que l’on dispose, pour résoudre un problème donnée de 7 algorithmes dont
les complexités dans le pire des cas sont 1(constante), log2(n), n, nlog2(n), n2, n3, 2n sur un
ordinateur pouvant exécuter 106 opérations/seconde.

complexité 1 log2(n) n nlog2(n) n2 n3 2n


n=102 ~1µs 6,6 µs 0,1 µs 0,6ms 10ms 1s 4,1016 a
n=103 ~1µs 9,9 µs 1ms 9,9ms 1s 16,6mn ∞
n=104 ~1µs 13,3 µs 10ms 0,1s 100s 11,5j ∞
n=105 ~1µs 16,6 µs 0,1s 1,6s 2,7h 31,7a ∞
n=106 ~1µs 19,9 µs 1s 19,9s 11,5j 31,7.103a ∞

Les écarts entre les différents t.e. se creusent lorsque la taille de données croit.

Réciproquement une estimation de la taille maximum des données traitables en un temps


donnée est la suivante :
complexité 1 log2(n) n nlog2(n) n2 n3 2n
1s ∞ ∞ 10 6
63.10 3
10 3
10 2
19
1mn ∞ ∞ 6.10 7
28.10 5
77.10 2
390 25
1h ∞ ∞ 36.108 13.107 60.103 15.102 31
1j ∞ ∞ 86.10 9
27.10 8
29.10 4
44.10 2
36

M. E. RIFFI Page 5 2021/2022


Algorithmique et structures de données

II- ALGORITHMES DE RECHERCHE ET DE TRI

Objectifs

Etudier et analyser certains algorithmes de recherche et de tri :


 Algorithmes élémentaires :
 Recherche des éléments satisfaisants certains critères,
 Ajouter ou supprimer des éléments
 Tri à bulle
 Tri par sélection
 Tri par insertion
 Algorithmes dichotomiques :
 Recherche dichotomique
 Tri rapide
 Tri par fusion

1- Méthode de recherche dans un tableau


On suppose qu’on dispose d’un type tableau noté Tab à n éléments de type Element
(Entier, Reel, ..)
1-1- Recherche séquentielle dans un tableau non trié
a- Fonction de recherche de l’élément x dans un tableau T de n éléments :
La fonction retourne l’indice de la première occurrence de x dans le tableau si x
s’y trouve, et -1 sinon.

Entier recherche(Tab T, Entier n, Element x)


Entier i, j
Debut
j-1
i1
Tant que ((i>=n) et (j=-1)) faire
Si (T[i]=x) alors ji
Sinon ii+1
Fsi
Ftant que
Retourner j
Fin

b- Ajout d’un élément en fin du tableau :

Procedure Ajoutfin(Tab T, Entier n, Element x)


Entier i
Debut // max taille maximale du tableau
Si (n=max) alors
Ecrire ‘ajout impossible tableau saturé ‘
Sinon
n n+1
T[n]  x
Fsi
Fin

M. E. RIFFI Page 6 2021/2022


Algorithmique et structures de données

c- Suppression d’un élément du tableau non trié :

Procedure supprime(Tab T, Entier n, Element x)


Entier i, j
Debut
i recherche1(T, n, x)
si (i=-1) alors Ecrire ‘Elément absent ‘
sinon
pour j1 à n-1 faire
T[j] T[j+1]
Fpour
n  n-1
fsi
Fin

Remarque :
Pour un tableau non trié :

Recherche O(n) : on doit parcourir tout le


tableau
Ajout en in de tableau O(1)
Suppression O(n) (décalage vers la gauche)

La recherche au sens large est donc O(n).

1-2- Recherche séquentielle dans un tableau trié


a. Fonction de recherche :
On cherche un élément x dans le tableau T trié, en ordre croissant (si x absent
alors la fonction retourne -1)

Entier recherche2(Tab T, Entier n, Element x)


Entier i, j
Debut
i 1
j -1
Tant que ((i<=n) et (j=-1) et (x >=T[i])) faire
Si (x=T[i]) alors j i
Sinon ii+1
Fsi
Ftant que
Retourner j
Fin

b. Ajout d’un élément dans un tableau trié :

Procedure ajout_trie(Tab T, Entier n, Element x)


Entier i, j
Debut
Si (n=max) alors
Ecrire ‘Insertion impossible’

M. E. RIFFI Page 7 2021/2022


Algorithmique et structures de données

Sinon
i1
Tant que ((i<n) et (x >= T[i])) faire
i i + 1
ftant que
pour j n+1 à (i+1) faire
T[j] T[j-1]
Fpour
T[i]  x
n  n+1
fsi
Fin

c. Suppression d’un élément du tableau :


Suppression de l’élément x, s’il existe du tableau trié.

Procedure suppression (Tab T, Entier n, Element x)


Entier i, j
Debut
i  recherche2(T, n, x)
si (i=-1) alors
Ecrire ‘Elément inexistant’
Sinon
Pour ji à (n-1) faire
T[j]=T[j+1]
Fpour
n n-1
fsi
Fin

Remarque :
Pour un tableau trié :

Recherche séquentielle O(n) (on doit parcourir le tableau)


Insertion O(n) (décalage vers le bas)
Suppression O(n) (décalage vers le haut)

2- Notion de tri
On considère un ensemble d’éléments, généralement des enregistrements, composés
d’un ou plusieurs champs.

Clé :
Un des champs d’un type, sur lequel il est possible de définir une relation d’ordre.

Données satellites :
Reste des champs, traités en même temps que la clé.
Exemple :
Fichier étudiant, où chaque étudiant est caractérisé par son numéro (clé), nom,
prénom, âge, (données satellites).

M. E. RIFFI Page 8 2021/2022


Algorithmique et structures de données

Le tri consiste à ordonner les données de l’ensemble selon un ordre croissant (ou
décroissant) des valeurs de leur champ clé.
Exemple :
{2, 25, 7, 15, 4] sera trié selon l’ordre croissant par {2, 4, 7, 15, 25} (ou selon l’ordre
décroissant par {25, 15, 7, 4, 2})

Le tri est un problème courant de l’informatique.

3- Généralité sur les tris


3-1- Définitions :
 Taille d’un problème de tri :
Nombre d’éléments à trier.
 Principales opérations élémentaires d’un algorithme de tri :
Comparaison et échange de données.
 Tri est stable :
S’il ne change pas l’ordre initial des éléments
 Tri est sur place :
S’il n’utilise que l’espace mémoire réservé aux éléments à trier.
 Tri interne :
Les données peuvent être chargées en mémoire centrale.
 Tri externe :
Les données sont placés dans des fichiers (car trop volumineuses pour tenir en
mémoire centrale).

3-2- Tris en O(n2):


Il existe trois méthodes de tri en O(n2) :
o Tri à bulle,
o Tri sélection,
o Tri insertion.

3-2-1- Tri à bulle :


Il s’agit de comparer deux à deux, les éléments successives du tableau, et les
échanger jusqu’à obtention d’un tableau trié.

Procedure tri_Bulle(Tab T, Entier n)


Entier i, j
Element Ech
Boolean Echange
Debut
Repeter
Echange = Faux
Pour i1 à (n-1) faire
Si (T[i] > T[i+1]) alors
Ech  T[i]
T[i] T[i+1]
T[i+1]  Ech
Echange Vrai
Fsi
Fpour
Jusqu à (Echange =Faux)

M. E. RIFFI Page 9 2021/2022


Algorithmique et structures de données

Fin

3-2-2- Tri sélection


On cherche le minimum d’un tableau, une fois trouvé on fait une permutation
jusqu’à ce que le tableau soit ordonné par ordre croissant.

Procedure tri_selection (Tab T, Entier n)


Entier i, j, min
Element Ech
Debut
Pour i1 à (n-1) faire
min i
pour j i+1 à n faire
si (T[j] < T[min]) alors
min j
fsi
fpour
si (i !=min) alors
ech T[min]
T[min] T[i]
T[i] ech
Fsi
Fpour
Fin

3-2-3- Tri insertion


On classe un élément parmi les éléments déjà triés, alors on l’insère dans sa bonne
place. On répète jusqu’à fin du tableau.
La case O joue le rôle de sentinelle.

Procedure tri_insertion(Tab T, Entier n)


Entier i, j
Debut
Pour i2 à n faire
T[0]  T[i]
ji-1
Tant que (T[0] < T[j]) faire
T[j+1] T[j]
j j-1
ftant que
T[j+1] T[0]
Fpour
Fin

Etude de la complexité :

On compte deux types d’opérations : comparaison et affectations.


On suppose équiprobable toutes les permutations possibles de n éléments.

M. E. RIFFI Page 10 2021/2022


Algorithmique et structures de données

Etape i Total
Meilleur cas Cas moyen Pire cas Meilleur cas Cas moyen Pire
cas
Comparaisons 1 (i-1)/2 i-2 n-1 n2/2-3n/2+1 n2-
4n+3
Affectations 3 i+1 2i-1 3n-3 n2-n 2n2-
5n+3

La complexité du tri par insertion dans le pire des cas et en moyenne est O(n2), par contre
dans le meilleur des cas est O(n).

3-3- Tri en O(nlog(n))


Le principe de ce tri appelé rapide (ou Quick Sort) est de partitionner les éléments du
tableau dont les indices sont dans l’intervalle [gauche, droit] de la manière suivante :
On choisit un élément nommé pivot (par exemple au milieu de l’intervalle), et par
échanges successifs, on place à gauche du pivot tous les éléments plus petits et à droite tous
les éléments plus grands. On rapplique (récursivement) ce qui précède sur [gauche, pivot-1] et
[pivot +1, droit].

Procedure tri_rapide(tab T, Entier gauche, Entier droit)


Entier i, j
Element pivot, x
Debut
i  gauche
j  droit
pivot T[(i+j)/2]
Tant que (i<=j) faire
Tant que (T[i] <pivot) faire i i+1
Ftant que
Tant que (T[j] >pivot) faire jj-1
Ftant que
Si (i<=j) alors
xT[i]
T[i]T[j]
T[j]x
ii+1
jj-1
fsi
Ftant que
Si (gauche < j) alors
Tri-rapide(T, gauche, j)
Fsi
Si (droit >i) alors
Tri_rapide(T, i, droit)
Fsi
Fin

Complexité :
Cas moyen et meilleur cas :
Complexité du tri rapide O(nlog(n))

M. E. RIFFI Page 11 2021/2022


Algorithmique et structures de données

Pire des cas :


Complexité du tri rapide O(n2).

M. E. RIFFI Page 12 2021/2022


Algorithmique et structures de données

III- PILE, FILE ET LISTE


Objectifs
◦ Introduire la notion de type abstrait de données (TAD)
◦ Spécification
◦ Implémentation
◦ Etudier les structures linéaires classique
◦ Pile
◦ File
◦ Liste

1- Type abstrait de données
La conception d’un algorithme est indépendante de toute implémentation
La représentation de données n’est pas fixé, elles sont considérées de manière abstraite
On s’intéresse à l’ensemble des opérations sur les données, et aux propriétés des
opérations, sans dire comment ces opérations sont réalisées
On parle de type abstrait de données : TAD

1-1 Définition d’un TDA :


Un TDA (Data Abstract Type) est un ensemble de valeurs munis d’opérations sur ces
valeurs, sans faire référence à une implémentation particulière.
Exemples :
Dans un algorithme qui manipule les entiers on ne s’intéresse pas à la représentation
des entiers, mais aux opérations définies sur les entiers : +, -, *, /
Type booléen : ensemble de deux valeurs (vrai, faux) muni des opérations : et, ou, non

Un TAD est caractérisé par :


– Sa signature: définit la syntaxe du type et des opérations.
– Sa sémantique : définit les propriétés des opérations.

1-2 Signature d’un TAD :


La signature d’un TAD comporte :
– Le nom d’un TAD
– Les noms des types des objets utilisés par le TAD
– Pour chaque opération l’énoncé des types des objets qu’elle reçoit et qu’elle
renvoie.
La signature d’un TAD décrite par les paragraphes :
– Type
– Utilise
– Opérations
Exemple : TAD Booléen

Type Booléen
Opérations
Vrai :  Booléen
Faux :  Booléen
Non : Booléen  Booléen
Et : Booléen x Booléen  Booléen
OU : Booléen x Booléen  Booléen

M. E. RIFFI Page 13 2021/2022


Algorithmique et structures de données

1-3 Sémantique d’un TAD :


La sémantique d’un TAD précise :
Les domaines de définition (ou d’applications) des opérations
Les propriétés des opérations.
La sémantique d’un TAD décrite par les paragraphes :
Préconditions
Axiomes
Remarque :
Lorsque la sémantique est défini par un système d’équations on parle d’un type abstrait
algébrique (TAA).

Exemples :
1) TAD Booléen
Type Booléen
Opérations :
Vrai :  Booléen
Faux :  Booléen
Non : Booléen  Booléen
Et : Booléen x Booléen  Booléen
OU : Booléen x Booléen  Booléen
Préconditions
Axiomes
Booléen a, b
Non (vrai) = faux
Non (non (a)) = a
Vrai et a = a
Faux et a = faux
a ou b = non (non(a) et non(b))

2) TAD Vecteur
Type Vecteur
Utilise Entier, Elément
Opérations :
Vect : Entier  Vecteur
Changer_ième : Vecteur x Entier x Elément  Vecteur
ième : Vecteur x Entier  Elément
Taille : Vecteur  Entier
Préconditions
Vect(i) est_défini_ssi i ≥0
ième(V, i) est_défini_ssi 0 ≤i ≤ Taille(V)
Changer_ième(V, i, elt) est_défini_ssi 0 ≤i ≤ Taille(V)
Axiomes
Soit i, j : Entier, elt : Elément, V : vecteur
Si 0 ≤i ≤ Taille(V) alors ième(Changer_ième(V, i, elt), i) =elt
Si 0 ≤i ≤ Taille(V) et 0 ≤j ≤ Taille(V) et i ≠ j alors
ième(Changer_ième(V, i, elt), j) = ième(V, j)
Taille(Vect(i)) = i
Taille(Changer_ième(V, i, elt), i) = Taille(V)

M. E. RIFFI Page 14 2021/2022


Algorithmique et structures de données

1-4 Opérateurs :
Trois catégorie d’opérateurs ou de primitives :
Constructeurs : type spécifié apparait uniquement comme résultat
Observateurs : type spécifié apparait uniquement comme argument
Transformateurs : type spécifié apparait à la fois comme argument et
comme résultat.
Constante : opérateurs sans argument
1-5 Opérations partielles :
Une opération peut ne pas être définie partout,
Cela dépend de son domaine de définition,
Ceci est traité dans le paragraphe préconditions
Exemple :
Opérateurs ième et changer_ième du TAD.
1-6 Réutilisations des TADs :
Quand on définit un type on peut réutiliser des types déjà définies.
La signature du type défini est l’union des signatures des types déjà utilisés, plus
de nouvelles opérations.
Le type hérite des propriétés des types qui le constitue.
Exemple :
Types Entier et Elément utilisé par le TAD Vecteur

1-7 Choix des axiomes :


Le système d’axiomes doit être :
Non contradictoire (consistance) : le résultat de chaque observateur doit être
unique.
Complet (complétude suffisante) : on doit déduire des axiomes
Le résultat de chaque observateur sur son domaine de définition.
1-8 Notion de structures de données :
On dit aussi structure de données concrètes.
Correspond à l’implémentation d’un TAD.
Composée d’un algorithme pour chaque opération, plus éventuellement des données
spécifiques à la structure pour sa gestion.
Un même TAD peut donner lieu à plusieurs structures de données, avec des
performances différentes.

1-9 Implémentation d’un TAD :


Pour implémenter un TAD :

déclarer la structure de données retenus pour représenter le


TAD : INTERFACE.
Définir les opérations primitives dans un langage particulier :
REALISATION.

On utilisera :
Les types élémentaires (Entier, réel, caractère, …),
Les tableaux et les enregistrements,
Les pointeurs,
Les types prédéfinis.
Plusieurs implémentations possibles pour un même TAD.

M. E. RIFFI Page 15 2021/2022


Algorithmique et structures de données

Implémentation d’un TAD en langage C:


Utiliser la programmation modulaire.
Pour chaque module créer deux fichiers :
Fichier module.h : l’interface (la partie publique) contient la spécification de
la structure.
Fichier module.c : la définition (la partie privée) contient la réalisation des
opérations fournies par la structure (il contient au début l’inclusion du fichier
module.h).

Tout module ou programme principale qui a besoin d’utiliser des fonctions de module
devra juste inclure le module.h

Un module C implémente un TAD :


L’encapsulation : détails d’implémentation cachée (l’interface est la partie visible à
l’utilisateur).
La réutilisation : placer les deux fichiers du module dans le répertoire ou on
développe le module.

Exemple TAD Booléen :

/* Fichier Booleen.h */

#Ifndef _Booleen
#define _Booleen
Typedef enum (vrai, faux) Booleen ;
Booleen et(Booleen x, Booleen y);
Booleen ou(Booleen x, Booleen y);
Booleen non(Booleen x);
#endif

/* Fichier Booleen.c */

#include Booleen.h
Booleen et(Booleen x, Booleen y)
{ if (x == faux )
return faux;
else return y;
}
Booleen ou(Booleen x, Booleen y)
{ if (x == vrai )
return vrai;
else return y;
}
Booleen non(Booleen x)
{ if (x == vrai )
return faux;
else return vrai;
}
2- Pile
Les piles sont très utilisées en informatique.

M. E. RIFFI Page 16 2021/2022


Algorithmique et structures de données

Notion intuitive :
Pile d’assiette
Pile de dossiers à traiter

Une pile est une structure linéaire permettant de stocker et de restaurer des
données selon un ordre LIFO (Last In First Out ou "dernier sorti premier entré")
Dans une pile les insertions (empilements) et les suppressions (dépilements) sont
restreintes à une extrémité appelée sommet.
Exemple :
Empiler 25
25
Empiler 36
36
25

Empiler 17
17
36
25
Dépiler
36
25

Type abstrait pile :


Type Pile
Utilise Elément, Booléen
Opérations
Pile_vide :  Pile
Est_vide : Pile  Booléen
Empiler : Pile x Elément  Pile
Dépiler : Pile  Pile
Sommet : Pile  Elément
Pré conditions
Dépiler(p) est_défini_ssi Est_vide(p) = faux
Sommet(p) est_défini_ssi Est_vide(p) = faux
Axiomes
Soit e : Elément , p: Pile
Est_vide(Pile_vide) = Vrai
Est_vide(Empiler(p, e)) = Faux
Dépiler(Empiler(p, e)) = p
Sommet(Empiler(p, e)) = e

Opérations sur une pile :


Pile_vide :  Pile
Opération d’initialisation, la pile crée est vide
Est_vide : Pile  Booléen

M. E. RIFFI Page 17 2021/2022


Algorithmique et structures de données

Teste si pile vide ou non


Empiler : Pile x Elément  Pile
Ajoute un élément à la pile
Dépiler : Pile  Pile
Supprime l’élément situé au sommet de la pile (n’a pas de sens si pile est vide)
Sommet : Pile  Elément
Permet de consulter l’élément situé au sommet (n’a pas de sens si pile est vide).

Représentation d’une pile :


Représentation contigüe (par tableaux)
– Les éléments de la pile sont rangés dans un tableau.
– Un entier représente la position du sommet de la pile.
Représentation chaînée (par pointeurs)
– Les éléments de la pile sont chaînés entre eux (liste chaînée).
– Un pointeur sur le premier élément désigne la pile et représente le sommet de
la pile.
– Une pile vide est représenté par le pointeur NULL (ou NIL en Pascal).

Représentation Contigüe:

/* Pile contigüe en C */
// Taille maximale pile
# Define max_pile 10
// Type des éléments
Typedef int Element;
// Type pile
Typedef Struct {
Element elements[max_pie];
int sommet;
} Pile;

Spécification d’une pile contigüe


/* fichier Tpile.h */
# ifndef _PILE_TABLEAU
# define _PILE_TABLEAU
#include "Booleen.h"
// définition du type file (implémentation par un tableau)
#define max_pile 10 /* taille maximale d’une pile */
Typedef int element ; /* les élements sont des entiers */
Typedef struct {
Element elements[max_pile]; /* les éléments de la pile */
Int sommet ; /* position du sommet */
} Pile ;
// Déclaration des fonctions gérant la pile
Pile pile_vide();
Pile pile_empiler(Pile P, Element e);
Pile pile_depiler(Pile P);
Element sommet(Pile P);
Booleen est_vide(Pile P);
#endif

M. E. RIFFI Page 18 2021/2022


Algorithmique et structures de données

Réalisation d’une pile contigüe


/* fichier "Tpile.c " */
#include « Tpile.h«
// Définition des fonctions gérant la pile
// Initialiser une nouvelle pile
Pile pile_vide(){
Pile p;
p.sommet =0;
return p; }
// Ajout d’un élément
Pile pile_empiler(Pile p, Element e) {
if (p.sommet >= max_pile ) {
printf (" Erreur pile pleine ! \n") ;
exit(-1); }
(p.sommet)++;
(p.Elements[p.sommet] = e;
return p;
}

// Supprimer un élément de la pile


Pile pile_depiler(Pile p) {
if est_vide(p) {
printf(" Erreur pile vide ! \n ");
exit(-1);
}
(p.sommet)--;
return p;
}
// valeur de l’élément au sommet de la pile
Element sommet(Pile p){
if est_vide(p) {
printf(" Erreur pile vide ! \n ");
exit(-1);
}
return (p.elements)[p.sommet];
}

// Tester si la pile est vide


Booleen est_vide(Pile p){
if (p.sommet == 0) return vrai;
return faux;
}

Pile chaînée
/* Pile chaînée en C */
// type des éléments
typedef int element;
// type cellule
typedef struct cellule{

M. E. RIFFI Page 19 2021/2022


Algorithmique et structures de données

element valeur;
struct cellule *suivant;
} cellule ;
// type pile
typedef cellule *pile;

Les opérations sur les piles sont toutes en O(1)

3- Les Files
Les files sont très utilisées en Informatique
Exemple :
File d’attente à un guichet,
File de documents à imprimer

Une file est une structure linéaire permettant de stocker et de restaurer des données selon
un ordre FIFO (First In First Out "Premier Entré Premier Sorti" ).
Dans une file :
Les insertions (enfilement) se font à une extrémité appelé queue de la file,
Les suppressions (défilement) se font à l’autre extrémité appelé tête de la file.

Exemple :
Enfiler 25
25

Enfiler 36
36
25

Enfiler 17
17
36
25
Défiler
17
36

Type abstrait File


Type File
Utilise Elément, Booléen
Opérations
file_vide :  File
est_vide : File  Booléen
enfiler : File x Elément  File
défiler : File  File
tête : File  Elément
Pré conditions
défiler(f) est_défini_ssi est_vide(f) = faux

M. E. RIFFI Page 20 2021/2022


Algorithmique et structures de données

tête(p) est_défini_ssi est_vide(f) = faux


Axiomes
Soit e : Elément , f: File
est_vide(file_vide) = Vrai
est_vide(enfiler(f, e)) = Faux
Si (est_vide(f) = vrai) alors tête(enfiler(f, e)) = e
Si (est_vide(f) = faux) alors tête(enfiler(f, e)) = tête(f)
Si (est_vide(f) = vrai) alors défiler(enfiler(f, e)) = file_vide
Si (est_vide(f) = faux) alors défiler(enfiler(f, e)) = enfiler(défiler(f), e))

Opérations sur une file :


– file_vide :  File
Opération d’initialisation, la file crée est vide
– est_vide : File  Booléen
Teste si la file est vide ou non
– enfiler : File x Elément  File
Ajoute un élément à la file
– défiler : File  File
Supprime l’élément situé en tête de la file (n’a pas de sens si file est vide)
– tête : File  Elément
Permet de consulter l’élément situé au sommet (n’a pas de sens si la file est
vide).
Représentation d’une file :
Représentation contigüe (par tableau)
– Les éléments de la file sont rangés dans un tableau
– Deux entiers représentent respectivement les positions de la tête et de la queue
de la file.
Représentation chaînée (par pointeurs)
– Les éléments de la file sont chaînées entre eux,
– Un pointeur sur le premier élément de la file représente la file et désigne la tête
de la file,
– Un pointeur sur le dernier élément désigne la queue de la file,
– Une file vide est représentée par le pointeur NULL (en C ou NIL en Pascal)

Représentation contigüe (par tableau)


– tête de file : position précédant le premier élément
– queue de file : position du dernier élément
– Initialisation :
tête  -1
queue  -1
– Inconvénient : on ne peut plus ajouter un élément dans la file alors qu’elle
n’est pas pleine

/* File contigüe en C */
// taille maximale file
#define MAX_FILE 10

//type des éléments


typedef int Element;
//type File

M. E. RIFFI Page 21 2021/2022


Algorithmique et structures de données

typedef struct {
Element tab[MAX_FILE];
int tête;
int queue;
} File ;

Spécification d’une file contigüe


/* fichier Tfile.h */
# ifndef _FILE_TABLEAU
# define _FILE_TABLEAU
#include "Booleen.h"
// définition du type file (implémentation par un tableau)
#define max_file 10 /* taille maximale d’une file */
Typedef int element ; /* les éléments sont des entiers */
Typedef struct {
Element tab[max_file]; /* les éléments de la file */
Int tête ; /* position précédant premier élément de la file*/
Int queue; /* position du dernier élément */
} file ;
// Déclaration des fonctions gérant la file
File file_vide();
File enfiler(File F, Element e);
File defiler(File F);
Element tete(File F);
Booleen est_vide(File F);
#endif

Réalisation d’une file contigüe


/* fichier "Tfile.c " */
#include « Tfile.h«
// Définition des fonctions gérant la file
// Initialiser une nouvelle file
File file_vide(){
File f;
F.tête =F.qeue= -1;
return f; }
// Ajout d’un élément
File enfiler(File f, Element e) {
if (f.queue >= max_file -1) {
printf (" Erreur file pleine ! \n") ;
exit(-1); }
(f.queue)++;
(f.tab[f.queue] = e;
return f;
}

// Supprimer un élément de la file


File defiler(File f) {
if est_vide(f) {
printf(" Erreur file vide ! \n ");

M. E. RIFFI Page 22 2021/2022


Algorithmique et structures de données

exit(-1);
}
(f.tete)++;
return f;
}
// valeur de l’élément en tête de la file
Element tete(File f){
if est_vide(f) {
printf(" Erreur file vide ! \n ");
exit(-1);
}
return (f.tab)[f.tete+1];
}

// Tester si la file est vide


Booleen est_vide(File f){
if (f.tete == f.queue) return vrai;
return faux;
}

File chaînée
/* File chaînée en C */
// type des éléments
typedef int element;
// type cellule
typedef struct cellule{
element valeur;
struct cellule *suivant;
} cellule ;
// type File
typedef struct {cellule *tete;
cellule *queue;
} File;

Exemple d’utilisation d’une file contigüe


/* Fichier "Utfile.c" */
#include <stdio.h>
#include "Tfile.h"
int main () {
File f = file_vide();
f= enfiler(f, 10);
f= enfiler(f, 20);
f= enfiler(f, 30);
f= enfiler(f, 40);
Printf("%d au sommet après enfilement de 10, 20, 30 et 40 \n", tete(f));
f=defiler(f);
f=defiler(f);
Printf("%d au sommet après défilement de 40 et 30 \n", tete(f));
return 0
}

M. E. RIFFI Page 23 2021/2022


Algorithmique et structures de données

4- Liste
Généralisation des piles et des files :
– Structure linéaire dans laquelle les éléments peuvent être traités les uns à la
suite des autres,
– Ajout ou retrait d’élément n’importe ou dans la liste,
– Accès à n’importe quel élément.
Une liste est une suite finie, d’éléments de même type repérés par leur rang dans la liste.
Chaque élément de la liste est rangé à une certaine place,
Les éléments de la liste sont ordonnés en fonction de leur place,
La longueur d’une liste est le nombre total de ses éléments.

Type abstrait Liste


Type Liste
Utilise Elément, Booléen, Place
Opérations
liste_vide :  Liste
longueur : Liste  Entier
inserer : Liste x Elément  Liste
supprimer : Liste  Liste
kème : Liste x Entier  Elément
accès : Liste x Entier  Place
contenu : Liste x Place  Elément
succ : Liste x Place  Place
Pré conditions
inserer(l,k,e) est_défini_ssi 1 ≤k ≤ longueur(l)+1
supprimer(l,k) est_défini_ssi 1 ≤k ≤ longueur(l)
kème(l,k) est_défini_ssi 1 ≤k ≤ longueur(l)
accès(l,k) est_défini_ssi 1 ≤k ≤ longueur(l)
succ(l,p) est_défini_ssi p≠ accès(l,longueur(l))

Axiomes
Soit e : Elément , l1,l2: Liste, j, k : Entier
Si l1 = NULL alors longueur(l1) =0
Sinon si l1=inserer(l2, k, e) alors longueur(l1) = longueur(l2) +1
sinon si l1= supprimer(l2,k) alors longueur(l1) = longueur(l2) – 1
Si 1 ≤j < k alors kème(inserer(l1,k,e), j) = kème(l1, j)
Sinon si j=k alors kème(inserer(l1,k,e), j) = e
sinon kème(inserer(l1,k,e), j) = kème(l1, j-1)
Si 1 ≤j < k alors kème(supprimer(l1,k), j) = kème(l1, j)
Sinon kème(supprimer(l1,k), j) = kème(l1, j+1)
succ (l1, accès(l1, k)) = accès(l1, k+1)
contenu (l1, accès(l1, k)) = kème(l1, k)
Si 1 ≤k < j < longueur(l1) alors
contenu (l1, accès(supprimer(l1,j), k)) = contenu (l1, accès(l1, k))
Si 1 ≤k ≤ j < longueur(l1) alors
contenu (l1, accès(supprimer(l1,j), k)) = contenu (l1, accès(l1, k+1))
Si 1 ≤k < j < longueur(l1)+1 alors
contenu (l1, accès(inserer(l1,k,e), j)) = contenu (l1, accès(l1, j))

M. E. RIFFI Page 24 2021/2022


Algorithmique et structures de données

Si 1 ≤k = j < longueur(l1) +1 alors


contenu (l1, accès(inserer(l1,k,e), j)) = e
Si 1 ≤k < j < longueur(l1)+1 alors
contenu (l1, accès(inserer(l1,k,e), j)) = contenu (l1, accès(l1, j-1))

Extension Type Abstrait Liste


Extension Type Liste
Opérations
– Concatener : Liste x Liste  Liste
– est_présent : Liste x Elément  Booléen
Préconditions
Axiomes
– Soit e : Element , l1, l2 : Liste , k, j : Entier
– longueur (concaténer(l1, l2)) = longueur(l1) + longueur(l2)
– Si k ≤ longueur(l1) alors
– kème (concaténer(l1, l2, k)) = kème(l1, k)
– Sinon kème (concaténer(l1, l2, k)) = kème (l2, k – longueur(l1))
– Si longueur(l1) = 0 alors est_présent(l1, e) = faux
– Sinon si e = kème(l1, k) alors est_présent(l1, e) = vrai
– sinon est_présent(supprimer(l1, k), e) = vrai
Opérations sur les Listes
– liste_vide :  Liste
Opération d’initialisation : la liste créée est vide,
– longueur : Liste  Entier
Retourne le nombre d’éléments dans une liste,
– inserer : Liste x Elément  Liste
Ajoute un élément à la liste dans une position donnée,
– supprimer : Liste  Liste
Supprimer un élément dans la liste,
– kème : Liste x Entier  Elément
Fournit l’élément de rang donné dans une liste,
– accès : Liste x Entier  Place
Connaître la place de rang donné,
– contenu : Liste x Place  Elément
Connaitre l’élément d’une place donnée,
– succ : Liste x Place  Place
Passer d’une place à la place suivante.

Opérations auxiliaires sur les Listes


– concatener: Liste x Liste  Liste
Accroche la deuxième liste en entrée à la fin de la première liste,
– est_présent : Liste x Elément  Booléen
Teste si un élément existe dans une liste,

Représentation Contiguë d’une Liste


Les éléments sont rangés les uns à côté des autres dans un tableau
– La kème case du tableau contient le kème élément de la liste
– Le rang est égale à la place : ce sont des entiers
La liste est représentée par une structure en langage C ou Pascal ou .. :

M. E. RIFFI Page 25 2021/2022


Algorithmique et structures de données

– Un tableau représente les éléments


– Un entier représente le nombre d’éléments dans la liste
– La longueur maximale : MAX_Liste de la liste doit être connue

/* Liste contigüe en C */

// taille maximale Liste


#define MAX_LISTE 10

//type des éléments


typedef int Element;
//type place
Typedef int Place;
//type File
typedef struct {
Element tab[MAX_FILE];
int taille;
} Liste ;

Réalisation d’une liste contigüe


/* fichier "Tliste.h " */

#ifndef _LISTE_TABLEAU
#define _LISTE_TABLEAU
// définition dy type liste complémentaire par tableau
#define MAX_LISTE 100 /* taille maximale de la liste */
Typedef int élement /* les éléments sont des entiers */
Typedef int Place /* place = rang (est un entier) */
Typedef struct {
élement tab[MAX_LISTE] ;/*éléments de la liste */
int taille ;
} Liste;
// Déclaration des fonctions gérant la liste
Liste liste_vide(Void)
int longueur(Liste l);
Liste inserer(Liste l,int i, Element e);
Liste supprimer(Liste l, int i);
élement kème (Liste l, int k);
Place acces(Liste l, int i);
élement contenu(Liste l, Place i);
Place succ (Liste l, Place i);
#endif

Représentation Chaînée d’une Liste


– Les éléments ne sont pas rangés les uns à côté des autres
La place d’un élément est l’adresse d’une structure qui contient l’élément,
ainsi que la place de l’élément suivant
Utilisation de pointeurs pour chaîner entre eux les éléments successifs
– En langage C (ou Pascal) la liste est représentée par un pointeur sur une
structure

M. E. RIFFI Page 26 2021/2022


Algorithmique et structures de données

La structure contient une élément de la liste et un pointeur sur l’élément


suivant,
La liste vide est représentée par une constante prédéfinie vide (NULL en C,
Nil en Pascal)

/* Liste Chaînée en C */
//type des éléments
typedef int Element;
//type place
Typedef struct cellule* Place;
//type cellule
typedef struct {
Element valeur;
Struct cellule *suivant;
} Cellule ;
//type Liste
Typedef Cellule *Liste;

/* fichier " Cliste.h" */


#ifndef _LISTE_CHAINEE
#define _LISTE_CHAINEE
typedef int Element;
Typedef struct cellule * Place;
typedef struct cellule{
Element valeur;
Struct cellule *suivant;
} Cellule ;
//type Liste
Typedef Cellule *Liste;
Liste liste_vide(Void)
int longueur(Liste l);
Liste inserer(Liste l,int i, Element e);
Liste supprimer(Liste l, int i);
élement kème (Liste l, int k);
Place acces(Liste l, int i);
élement contenu(Liste l, Place i);
Place succ (Liste l, Place i);
#endif

Réalisation d’une Liste Chaînée


Liste liste_vide(Void) { return NULL; }
int longueur(Liste l) { int taille=0; Liste p=l;
while(p) { taille++; p = psuivant;}
return taille; }
Liste inserer(Liste l, int i, element e) {
if (i<0 || i>longueur(l)){
printf(" Erreur : rang non valide !\n");
exit(-1); }
Liste pc =(Liste)malloc(sizeof(cellule));
pc valeur=e;

M. E. RIFFI Page 27 2021/2022


Algorithmique et structures de données

pc suivant =NULL;
if (i==0){pcsuivant =l;
l=pc; }
else {
int j; Liste p=l;
for (j=0; j<i-1; j++)
p=psuivant;
pcsuivant =psuivant;
p suivant =pc;}
return l; }

Place acces(Liste l, int k){


int i;
Place p;
If (k<0 || k>=longueur(l) {
printf("Erreur : rang invalide !\n");
exit(-1);}
If (k==0) return 1;
else { p=l; for (i=0; i<k; i++)
p= psuivant;
return p;
}
}
élement contenu(Liste l, Place i){
If (longueur(l) ==0) { printf(" Erreur : liste vide !\n ");
exit(-1); }
Return pvaleur; }
Place succ (Liste l, Place p){ if (psuivant == NULL) {
printf("Erreur : suivant dernière place !\n");
exit(-1); }
return psuivant; }

Liste inserer(Liste l,int i, Element e){


if (i<0 || i>longueur(l)) { printf("Erreur : rang non valide !\n"); exit(-1); }
Liste pc =(Liste)malloc(sizeof(Cellule)); pcvaleur=e; pcsuivant=NULL;
if (i==0) {pcsuivant =l;
l=pc; }
else { int j; Liste p=l;
for (j=0; j<i-1; j++) p=psuivant;
pcsuivant =psuivant;
psuivant =pc; }
return l; }
Liste supprimer(Liste l, int i){ int j; Liste p;
if (i>0 || i>= longueur(l)) { printf("Erreur : rang non valide !\n");
exit(-1); }
if (i == 0) { p=l; l=lsuivant; }
else {place q; q=acces(l, i-1); p= succ(l,q); qsuivant = psuivant; }
free(p); return l; }
élement kème (Liste l, int k){
If (k<0 || k>longueur(l)-1) { printf("Erreur : rang non valide !\n");

M. E. RIFFI Page 28 2021/2022


Algorithmique et structures de données

exit(-1); }
return contenu(l, acces(l, k)); }
#endif

Remarques :
Ajout au milieu d’une liste connaissant la place qui précède celle ou s’effectuera l’ajout
– ajouter : Liste x Place x Elément  Liste
– ajouter(L, p, e) : liste obtenue à partir de L en ajoutant une place contenant
l’élément e, juste après la place p
Enlever un élément d’une liste connaissant sa place
– enlever : Liste x Place  Liste
– enlever(L,p) : liste obtenue à partir de L en supprimant la place p et son
contenu

Liste ajouter(Liste l,Place p, Element e){


Liste pc;
pc=(Liste)malloc(sizeof(Cellule));
if (pc==NULL) { printf("Erreur : Problème de mémoire \n"); exit(-1); }
pcvaleur=e;
pcsuivant=psuivant;
psuivant =pc; }
return l;
}
Liste enlever(Liste l, Place p){
Place pred;
if (p==l) l=succ(l, p);
else {pred=l;
while (succ(l, pred) !=p)
pred =succ(l, pred);
predsuivant = psuivant;
}
free(p);
return l;
}

M. E. RIFFI Page 29 2021/2022


Algorithmique et structures de données

Variantes de Listes Chaînées


Liste avec tête fictive
– Mettre en tête de liste une zone qui ne contient pas de valeur et reste toujours
en tête
Liste chaînée circulaire
– Le suivant du dernier élément de la liste est le premier élément
Liste doublement chaînée
– Utiliser un double chaînage; chaque place repérant à la fois la place qui la
précède et celle qui la suit
Liste doublement chaînée circulaire
– Comme liste doublement chaînée et en plus le suivant du dernier élément de la
liste est le premier élément
Liste triée
– L’ordre des enregistrements dans la liste respecte l’ordre sur les clés

Complexités de Listes Chaînées


Soit n le nombre d’éléments d’une liste :
Opération Représentation contiguë Représentation chaînée
liste_vide O(1) O(1)
ajout/suppression en tête O(n) O(1)
ajout/suppression en générale O(n) O(n)
ajout/suppression en queue O(1) O(n)
accès O(1) O(n)
concaténation O(n) O(1)

Représentation par Curseurs d’une Liste


Curseurs (ou faux pointeurs) utilisés dans le cas d’un langage ne supportant pas les pointeurs
(par exemple Fortran)
La liste est représentée par une structure à trois champs en langage C :
– Un tableau de couples (élément, suivant)
– Un entier (tete) désignant début de liste des éléments
– Un entier (dispo) désignant début de liste des cases libres
– Liste vide : tete  -1 et dispo  0
/* Liste Chaînée par curseurs en C */
//taille maximale liste
#define MAX_LISTE 10
//type des éléments
typedef int Element;
//type cellule
typedef struct cellule{
Element valeur;
int suivant;
} Cellule ;
//type Liste
Typedef struct{
Cellule cellules[MAX_LISTE];
int tete;
int dispo;
} Liste;

M. E. RIFFI Page 30 2021/2022


Algorithmique et structures de données

IV- ARBRE
1- Objectifs :
Etudier des structures non linéaires :
 Arbre binaire
 Arbre binaire de recherche
 Arbre Maximier ou Tas
 Graphe
 …
2- Arbres
2-1- INTRODUCTION
2-1-1- Notion d’arbre
Les arbres sont les structures de données les plus importantes en Informatique.
Ce sont des structures non linéaires qui permettent d’obtenir des algorithmes plus
performants que lorsqu’on utilise des structures de données linéaires telles que les listes et
les tableaux.
Ils permettent une organisation naturelle des données.
2-1-2- Exemple d’utilisation d’arbre :
 Organisation des fichiers dans les systèmes d’exploitation;
 Organisation des informations dans un système de bases de données;
 Représentation de la structure syntaxique des programmes sources dans les
compilateurs;
 Représentation d’une table de matière;
 Représentation d’un arbre généalogique;

2-2- Terminologie
• Un arbre est un ensemble d’éléments appelés nœuds (ou sommet), liés par une
relation (dite de priorité) induisant une structure hiérarchique parmi ces nœuds.

• Un nœud, comme tout élément d’une liste, peut être de n’importe quel type.
• Une structure d’arbre de type de base T est :
• Soit la structure vide;
• Soit un nœud de type T, appelé racine, associé à un nombre fini de structures
d’arbre disjointes du type de base T appelées sous arbres.
• C’est une définition récursive : la récursivité est une propriété des arbres et des
algorithmes qui les manipulent.
• Une liste est un cas particulier des arbres (arbre dégénéré), où tout nœud a au plus un
sous arbre

M. E. RIFFI Page 31 2021/2022


Algorithmique et structures de données

Exemple :

La terminologie utilisée dans les structures d’arbres est empruntée:


– Aux arbres généalogiques :
• Père;
• Fils;
• Frère;
• Descendant;
• …
– À la botanique :
• Feuille;
• Branche;
• …

• Père :
– Tous les nœuds d’un arbre (sauf un), ont un père et un seul. Un nœud p est père
du nœud f si et seulement si f est fils de p;
– Dans l’exemple précédent : le père de 5 est 1, celui de 1 est 6;
• Fils :
– Chaque nœud d’un arbre pointe vers un ensemble éventuellement vide d’autres
nœuds; ce sont ses fils (ou enfants);
– Le nœud 6 a deux fils 3 et 1, le nœud 3 a un fils 2, le nœud 1 a trois fils 5, 7 et
8.
• Frère :
– Deux nœuds ayant le même père;
– Les nœuds 5, 7 et 8 sont des frères;
• Racine :
– Le seul nœud sans père;
– 6 est la racine de l’arbre précédent.
• Feuille :
– Nœud terminal (ou externe); ce sont des nœuds sans fils
– Exemple : 2, 5, 7 et 8
• Branche :
– Chemin qui commence à la racine et se termine à une feuille;
– Exemple : les chemins (6, 3, 2), (6, 1, 5), (6, 1, 7), (6, 1, 8);
• Nœud interne : 1, 3 et 6
– Un nœud qui n’est pas terminal

M. E. RIFFI Page 32 2021/2022


Algorithmique et structures de données

• Degré d’un nœud :


– Le nombre de fils de ce nœud
– Exemple : degré(6) = 2, degré(3) = 1, degré(1) =3, les feuilles (2, 5, 7, 8) sont
de degré nul.
• Degré d’un arbre (ou arité) :
– Plus grand degré des nœuds de l’arbre. Un arbre de degré n est n-aire.
– Exemple précédent : l’arbre est 3-aire
• Taille d’un arbre :
– Le nombre total des nœuds de l’arbre.
– Exemple précédent : l’arbre est de taille 7.
• Chemin :
– Une suite de nœud d’un arbre (n1, n2, .., nk) tel que ni = père(ni+1 ) pour 1 ≤ i
≤ k-1, est appelée chemin entre le nœud n1 et le nœud nk .
– La longueur d’un chemin est égale au nombre de nœuds qu’il contient moins 1.
– Exemple : le chemin qui mène du nœud 6 au nœud 5 est de longueur 2.
• Ancêtre :
– Un nœud A est un ancêtre d’un nœud B s’il existe un chemin de A vers B.
– Exemple : les ancêtres de 5 sont 1 et 6.
• Descendant :
– Un nœud A est un descendant d’un nœud B, s’il existe un chemin de B vers A.
– Exemple : 6 admet les 6 nœuds (3, 1, 2, 5, 7, 8) comme descendant.
• Sous arbre :
– Un sous arbre d’un arbre A est constitué de tous les descendants d’un nœud
quelconque de A.
– Exemple : {3, 2} et {1, 5, 7, 8} forment deux sous arbres.
• Hauteur (ou profondeur, ou niveau) d’un nœud :
– Longueur du chemin qui relie la racine à ce nœud.
– La racine est elle-même de hauteur 0, ses fils sont de hauteur 1, et les autres
nœuds de hauteur supérieure à 1.
• Hauteur d’un arbre :
– Plus grande profondeur des nœuds de l’arbre supposé non vide, c’est-à-dire
h(A) = Max{h(x); x nœud de A}
– L’arbre de l’exemple est de profondeur 2.
– Par convention, un arbre vide a une hauteur de -1.
• Arbre dégénéré ou filiforme :
– Un arbre dont chaque nœud a au plus un fils.
– C’est une liste.
• Arbre ordonné :
– Un arbre où la position respective des sous arbres reflète une relation d’ordre.
En d’autres termes, si un nœud a k fils, il existe un 1er fils, un 2ème fils, .., et un
kème fils.
– Exemple les deux arbres suivants sont différents comme arbres ordonnés, mais
identique comme arbres simples.

• Arbre binaire:
– Un arbre où chaque nœud a au plus deux fils.

M. E. RIFFI Page 33 2021/2022


Algorithmique et structures de données

– Quand un nœud de cet arbre a un seul fils, on précise s’i s’agit du fils gauche
ou du fils droit.
– Exemple : arbre binaire dont les nœuds contiennent des caractères ordonnés,
mais identiques comme arbres simples.

• Arbre binaire complet:


– Un arbre binaire dont chaque niveau est rempli.
– Exemple :

• Arbre binaire parfait (ou presque complet):


– Un arbre binaire dont chaque niveau est rempli, sauf éventuellement le dernier.
– Dans ce cas les nœuds terminaux (feuilles) sont groupés le plus à gauche
possible.
– Exemple :

M. E. RIFFI Page 34 2021/2022


Algorithmique et structures de données

• Facteur d’équilibre d’un nœud d’un arbre binaire :


– Hauteur du sous arbre partant du fils gauche du nœud moins la hauteur du sous
arbre partant de son fils droit.
• Arbre binaire équilibré (au sens des hauteurs):
– Un arbre binaire tel que pour chaque nœud, la valeur absolue du facteur
d’équilibre est inférieur ou égale à un.
– Exemple : arbre avec facteur d’équilibre à côté du nœud.

2-3- Arbres binaires :


2-3-1- Définition
Un arbre binaire A est :
– Soit vide (A =() ou A=  ),
– Soit A = <r, A1, A2), c-à-d composé d’un nœud r appelé racine contenant un
élément, et deux arbres binaires disjoints A1 et A2 appelés respectivement
sous arbre gauche (ou fils gauche) et sous arbre droit (ou fils droit).
2-3-2- Exemple

2-3-3- Représentation en arbre binaire d’un arbre général


• Un arbre binaire peut être utilisé pour représenter un arbre général où les nœuds
peuvent avoir un nombre quelconque de fils.
• Cette représentation appelée "fils gauche-frère droit" (ou "fils aîné-frère droit"), utilise
un arbre binaire dans lequel le fils gauche sera le fils aîné du nœud courant et le fils
droit le frère cadet du nœud courant.
• La racine de l’arbre binaire n’a pas de fils droit.

M. E. RIFFI Page 35 2021/2022


Algorithmique et structures de données

Exemple représentation en arbre binaire d’un arbre général :

2-3-4- Propriétés des arbres binaires


Pour tout arbre binaire de taille n et de hauteur h :
 Log2(n) ≤ h ≤ n-1

h-1 ≤ n ≤ 2h+1 - 1

2-3-5- Type Abstrait Arbre_Binaire


Type Arbre_Binaire
Utilise Nœud , Element, Booleen
Opérations
• arbre_vide :  Arbre_Binaire
• est_vide : Arbre_Binaire  Booleen
• cons : Nœud x Arbre_Binaire x Arbre_Binaire  Arbre_Binaire
• racine : Arbre_Binaire  Nœud
• gauche : Arbre_Binaire  Arbre_Binaire
• droit e : Arbre_Binaire  Arbre_Binaire
• contenu : Nœud  Element
Préconditions
• racine(A) est-défini-ssi est_vide(A) = faux
• gauche(A) est-défini-ssi est_vide(A) = faux
• droite(A) est-défini-ssi est_vide(A) = faux
Axiomes
• Soit r : Nœud , A1, A2 : Arbre_Binaire
• racine(<r, A1, A2>) = r
• gauche (<r, A1, A2>) = A1
• droite (<r, A1, A2>) = A2

2-3-6- Opérations sur un Arbre Binaire


• arbre_vide :  Arbre_Binaire
– Opération d’initialisation, crée un arbre binaire vide

M. E. RIFFI Page 36 2021/2022


Algorithmique et structures de données

• est_vide : Arbre_Binaire  Booleen


– Teste si un arbre binaire est vide ou non
• cons : Nœud x Arbre_Binaire x Arbre_Binaire  Arbre_Binaire
– cons(r, G, D) construit un arbre binaire dont le sous arbre gauche est G et le
sous arbre droit est D, et r est le nœud racine qui contient une donnée de type
Element.
• racine : Arbre_Binaire  Nœud
– Si A est un arbre binaire non vide alors racine(A) retourne le nœud racine de
A, sinon un message d’erreur.
• gauche : Arbre_Binaire  Arbre_Binaire
– Si A est un arbre binaire non vide alors gauche(A) retourne le sous arbre
gauche de A, sinon un message d’erreur.
• droite : Arbre_Binaire  Arbre_Binaire
– Si A est un arbre binaire non vide alors droite(A) retourne le sous arbre droit
de A, sinon un message d’erreur.
• contenu : Nœud  Element
– Permet d’associer à chaque nœud d’un arbre binaire une information de type
Element.
2-3-7- Extension Type Arbre_Binaire
• Utilise Entier, Booleen
Opérations
• taille : Arbre_Binaire  Entier
• hauteur : Arbre_Binaire  Entier
• feuille : Arbre_Binaire  Booleen
Préconditions
Axiomes
• Soit r : Nœud, A, A1, A2 : Arbre_Binaire
• taille(arbre_vide) = 0
• taille(<r, A1, A2>) = 1 + taille(A1) + taille(A2)
• hauteur (arbre_vide) = -1
• Si hauteur(A1) > hauteur(A2) alors hauteur(<r, A1, A2>) = 1 + hauteur(A1)
• Sinon hauteur(<r, A1, A2>) = 1 + hauteur(A2)
• Si est_vide(A) = faux et est_vide(gauche(A)) = vrai et est_vide(droite(A)) = vrai
• Alors feuille(A) = vrai
• Sinon feuille(A) = faux

2-3-8- Parcours d’arbre binaire


Un parcours d’arbre permet d’accéder à chaque nœud de l’arbre :
– Un traitement (test, affichage, comptage, etc.), dépendant de l’application
considérée, est effectué sur l’information portée par chaque nœud
– Chaque parcours de l’arbre définit un ordre sur les nœuds
On distingue :
– Les parcours de gauche à droite (le fils gauche d’un nœud précède le fils droit);
– Les parcours de droite à gauche (le fils droit d’un nœud précède le fils gauche).
On ne considèrera que les parcours de gauche à droite
On distingue aussi deux catégories de parcours d’arbres :
– Les parcours en profondeur;
– Les parcours en largeur.
2-3-8- 1- Parcours en profondeur
Soit un arbre binaire A= <r, A1, A2>, on définit trois parcours en profondeur de cet arbre :

M. E. RIFFI Page 37 2021/2022


Algorithmique et structures de données

– Le parcours préfixe;
– Le parcours infixe (ou symétrique);
– Le parcours post fixe (ou suffixe).
a) Parcours préfixe
En abrégé RGD (Racine, Gauche, Droit), consiste à effectuer dans l’ordre :
– Le traitement de la racine r;
– Le parcours préfixe du sous arbre gauche A1;
– Le parcours préfixe du sous arbre droit A2;

L’ordre correspondant s’appelle l’ordre préfixe.


b) Parcours infixe
En abrégé GRD (Gauche, Racine, Droit), consiste à effectuer dans l’ordre :
– Le parcours infixe du sous arbre gauche A1;
– Le traitement de la racine r;
– Le parcours infixe du sous arbre droit A2;

L’ordre correspondant s’appelle l’ordre infixe.


c) Parcours post fixe ou suffixe
En abrégé GDR (Gauche, Droit, Racine), consiste à effectuer dans l’ordre :
– Le parcours post fixe du sous arbre gauche A1;
– Le parcours post fixe du sous arbre droit A2;
– Le traitement de la racine r;

L’ordre correspondant s’appelle l’ordre suffixe.


Exemple de parcours en profondeur
– Le parcours préfixe affiche les nœuds dans l’ordre : 1, 2, 4, 5, 3, 6, 8, 9, 7, 10,
11
– Le parcours infixe affiche les nœuds dans l’ordre : 4, 2, 5, 1, 8, 6, 9, 3, 10, 7,
11
– Le parcours suffixe affiche les nœuds dans l’ordre : 4, 5, 2, 8, 9, 6, 10, 11, 7, 3,
1

2-3-8-2- Parcours en largeur


On explore les nœuds :
– Niveau par niveau,
– De gauche à droite,
– En commençant par la racine.
Exemple :
– Le parcours en largeur de l’arbre de la figure précédente affiche la séquence
d’entiers suivante : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

M. E. RIFFI Page 38 2021/2022


Algorithmique et structures de données

2-3-9- Représentations d’un arbre binaire


2-3-9-1- Représentation contiguë d’un arbre binaire
On caractérise un arbre binaire par :
– Sa taille (nombre de nœuds);
– Sa racine (indice de son emplacement dans le tableau de nœuds);
– Un tableau de nœuds.
Chaque nœud contient trois données :
– Une information de type Element;
– Deux entiers (indices dans le tableau désignant respectivement l’emplacement
des fils gauche et droit du nœud).

#define NB_MAX_NOEUDS 15
typedef int Element;
typedef struct nœud {
Element val;
int fg;
int fd;
} Nœud;
typedef Nœud TabN [NB_MAX_NOEUDS];
typedef struct arbre {
int nb_noeuds;
Int racine;
TabN les_noeuds;
} Arbre_Binaire;

M. E. RIFFI Page 39 2021/2022


Algorithmique et structures de données

Exemple de représentation contiguë d’un arbre binaire

Autre représentation contiguë d’un arbre binaire


Repose sur l’ordre hiérarchique (numérotation des nœuds niveau par niveau et de gauche à
droite)
On rappelle que pour stocker un arbre binaire de hauteur h, il faut un tableau de 2n+1 – 1
éléments.
On organise le tableau de la façon suivante :
– Le nœud racine a pour indice 1 (0 en langage C),
– Soit le nœud d’indice i dans le tableau, son fils droit a pour indice 2i (2i+1 en
C), et son fils gauche 2i+1 (2i+2 en C).
2-3-9-2- Représentation chaînée d’un arbre binaire
Chaque nœud a trois champs;
– val (élément stocké dans le nœud);
– fg (pointeur sur fils gauche);
– fd (pointeur sur fils droit).
Un arbre est désigné par un pointeur sur sa racine;
Un arbre vide est représenté par le pointeur vide (NULL en C ou NIL en Pascal).

Représentation chaînée en C d’un arbre binaire


typedef int Element;
typedef struct nœud *Pnoeud;
typedef struct nœud {
Element val;
Pnoeud fg;
Pnoeud fd;
} Nœud;
typedef Nœud *Arbre_Binaire;

Réalisation chaînée d’un arbre binaire


Arbre_Binaire arbre_vide() {
return NULL;
}
Booleen est_vide(Arbre_Binaire A) {

M. E. RIFFI Page 40 2021/2022


Algorithmique et structures de données

return A ==NULL;
}
Pnoeud nouveau _noeud(Element e) {
Pnoeud p=(Pnoeud) malloc (sizeof(Nœud));
if (p !=NULL) {
Pval = e;
Pfg = NULL;
Pfd = NULL;
}
Return (p);
}
Arbre_Binaire cons(Nœud *r, Arbre_Binaire G, Arbre_Binaire D) {
rfg = G;
rfd = D;
Return r;
}
Nœud racine (Arbre_Binaire A) {
if (est_vide(A) {
printf ("Erreur : Arbre vide ! \n");
exit(-1);
}
return (*A);
}
Arbre_Binaire gauche(Arbre_Binaire A) {
if (est_vide(A) {
printf ("Erreur : Arbre vide ! \n");
exit(-1);
}
return Afg; /* ou bien (*A).fg; */
}
Arbre_Binaire droite(Arbre_Binaire A) {
if (est_vide(A) {
printf ("Erreur : Arbre vide ! \n");
exit(-1);
}
return Afd; /* ou bien (*A).fd; */
}
Element contenu (Nœud n) {
return n.val;
}
2-4- Arbre binaire de recherche
2-4-1- Notion d’Arbre binaire de recherche
C’est un arbre binaire particulier :
– Il permet d’obtenir un algorithme de recherche proche dans l’esprit de la
recherche dichotomique;
– Pour lequel les opérations d’ajout et de suppression d’un élément sont aussi
efficaces.
Cet arbre utilise l’existence d’une relation d’ordre sur les éléments, représentée par une
fonction clé, à valeur entière.

M. E. RIFFI Page 41 2021/2022


Algorithmique et structures de données

2-4-2- Définition :
Un arbre binaire de recherche (Binary Search Tree), ou ABR est un arbre binaire tel que
pour tout nœud :
– Les clés de tous les nœuds du sous-arbre gauche sont inférieures ou égales à la
clé du nœud.
– Les clés de tous les nœuds du sous-arbre droit sont supérieures à la clé du
nœud.
Chaque nœud d’un ABR désigne un élément qui est caractérisé par une clé (prise dans un
ensemble totalement ordonné) et des informations associés à cette clé.
Dans toute illustration d’un arbre binaire de recherche, seules les clés sont représentées.
On supposera aussi que toute clé identifie de manière unique un élément.

2-4-3- Exemple :

L’arbre suivant est un ABR.


Cet arbre représente l’ensemble :
E={a, b, d, e, g, i , t}

2-4-4- Remarque :
Plusieurs représentations possibles d’un même ensemble par un ABR.
La structure précise de l’ABR est déterminée par :
– L’algorithme d’insertion utilisé,
– L’ordre d’arrivée des éléments.
Exemple :
L’ABR suivant représente aussi E = {a, b, d, e, g, i , t}

M. E. RIFFI Page 42 2021/2022


Algorithmique et structures de données

2-4-5- Opérations sur les ABR


Le type abstrait ABR, noté Arbre_Rech, est décrit de la même manière que le type
ARBRE_Binaire
On reprend les mêmes opérations de base des arbres binaires, excepté le fait que dans les
ABR, on suppose l’existence de l’opération clé sur le type abstrait Element.
On définit, en tenant compte du critère d’ordre, les opérations spécifiques de ce type
d’arbre concernant :
– La recherche d’un élément dans l’arbre;
– L’insertion d’un élément dans l’arbre;
– La suppression d’un élément de l’arbre.

2-4-5-1- Recherche d’un élément


a) Principe de l’algorithme :
– On compare la clé de l’élément cherché à la clé de la racine de l’arbre;
– Si la clé est supérieure à la clé de la racine, on effectue une recherche dans le
fils droit;
– Si la clé est inférieure à la clé de la racine, on effectue une recherche dans le
fils gauche;
– La recherche s’arrête quand on ne peut plus continuer (échec) ou quand la clé
de l’élément cherché est égale à la clé de la racine d’u sous arbre (succès).

b) Spécification
Extension Type Arbre_Rech
Utilise Elément, Booleen
Opérations
Rechercher : Element x Arbre_Rech  Booleen
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech

Rechercher(x, arbre_vide) = faux


Si clé(x) = clé(contenu(r)) alors Rechercher(x, <r, G, D>)= vrai
Si clé(x) < clé(contenu(r)) alors Rechercher(x, <r, G, D>)= Rechercher(x, G)
Si clé(x) > clé(contenu(r)) alors Rechercher(x, <r, G, D>)= Rechercher(x, D)

c) Réalisation en C
Booleen Rechercher(Arbre_Rech A, Element e) {
if (est_vide(A) == vrai)
return faux
else {
if (e == A->val)
return vrai;
else if (e < A->val)
return Rechercher(A->fg, e);
else
return Rechercher(A->fd, e);
}
}

M. E. RIFFI Page 43 2021/2022


Algorithmique et structures de données

d) Autre Spécification
Extension Type Arbre_Rech
Utilise Elément
Opérations
Rechercher : Element x Arbre_Rech  Booleen
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech

Rechercher(x, arbre_vide) = arbre_vide


Si clé(x) = clé(contenu(r)) alors Rechercher(x, <r, G, D>)= <r, G, D>
Si clé(x) < clé(contenu(r)) alors Rechercher(x, <r, G, D>)= Rechercher(x, G)
Si clé(x) > clé(contenu(r)) alors Rechercher(x, <r, G, D>)= Rechercher(x, D)

Ajout d’un élément dans un ABR


La technique d’ajout spécifiée ici est dite "ajout en feuille", car tout nouvel élément se
voit placé sur une feuille de l’arbre.
Le principe est simple :
– Si l’arbre initial est vide, le résultat est formé d’un ABR réduit à sa racine,
celle-ci contenant le nouvel élément;
– Sinon, l’ajout se fait (récursivement) dans le fils gauche ou le fils droit, suivant
que l’élément à ajouter est de clé inférieure ou supérieure à celle de la racine.
Remarque :
– Si l’élément à ajouter est déjà dans l’arbre, l’hypothèse d’unicité des éléments
pour certains applications fait qu’on ne réalise pas l’ajout.
2-4-5-2- Ajout "en feuille" d’un élément
a) Spécification
Extension Type Arbre_Rech
Utilise Elément
Opérations
Ajouter_f euille : Element x Arbre_Rech  Arbre_Rech
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech

Ajouter_feuille(x, arbre_vide) = <x, arbre_vide, arbre_vide>


Si clé(x) ≤ clé(contenu(r))
alors Ajouter_feuille(x, <r, G, D>) = <r, Ajouter_feuille(x, G), D>
Sinon
Ajouter_feuille(x, <r, G, D>) = <r, G, Ajouter_feuille(x, D)>

b) Réalisation
Fonction Ajouter_feuille(Elément x, Arbre_Rech A ) : Arbre_Rech
si (est_vide(A) ) alors
Pnoeud r = nouveau_noeud(x)
si est_vide(r) alors
ecrire ‘erreur’
fsi
Ajouter_feuille  cons(r, arbre_vide(), arbre_vide())
sinon
si (x > contenu(racine(A)) alors

M. E. RIFFI Page 44 2021/2022


Algorithmique et structures de données

Ajouter_feuille  cons(A, gauche(A), ajouter_feuille(x,droite(A)))


sinon
Ajouter_feuille  cons(A, ajouter_feuille(x, gauche(A)), droite(A))
fsi
fsi
Arbre_Rech Ajouter_feuille(Element x, Arbre_Rech A )
if (est_vide(A) ) {
Pnoeud r = nouveau_noeud(x)
if (r==NULL) {
printf("erreur : pas assez de mémoire !\n");
exit(-1);
}
return cons(r, arbre_vide(), arbre_vide())
}
else
if (x > contenu(racine(A)) alors
return cons(A, gauche(A), ajouter_feuille(x,droite(A)))
else
return cons(A, ajouter_feuille(x, gauche(A)), droite(A))
}
2-4-5-3- Suppression d’un élément
La suppression est complexe :
– Il faut réorganiser l’arbre pour qu’il vérifie la propriété d’un ABR.

La suppression commence par la recherche du nœud qui porte l’élément à supprimer.


Ensuite, il y a trois cas à considérer, selon le nombre de fils du nœud à supprimer :
– Si le nœud est sans fils (une feuille), la suppression est immédiate;
– Si le nœud a un seul fils, on le remplace par ce fils;
– Si le nœud a deux fils (cas général), on choisit de remplacer ce nœud, soit par
le plus grand élément de son sous arbre gauche (son prédécesseur), soit par le
plus petit élément de son sous arbre droit (son successeur).

On a besoin de deux opérations supplémentaires :


– Une opération Max qui retourne l’élément de clé maximale dans un ABR;
– Une opération SupprimerMax qui retourne l’arbre privé de son plus grand
élément.

a) Spécification
Extension Type Arbre_Rech
Utilise Element
Opérations
Max : Arbre_Rech  Element
SupprimerMax : Arbre_Rech  Arbre_Rech
Supprimer : Element x Arbre_Rech  Arbre_Rech
Pré-conditions
Max(A) est_défini_ssi est_vide(A) = faux
SupprimerMax(A) est_défini_ssi est_vide(A) = faux
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech
Si est_vide(D) = vrai alors Max(<r, G, D>) = r

M. E. RIFFI Page 45 2021/2022


Algorithmique et structures de données

Sinon Max(<r, G, D>) = Max(D)


Si est_vide(D) = vrai alors SupprimerMax(<r, G, D>) = G
Sinon SupprimerMax(<r, G, D>) = <r, G, SupprimerMax(D)>
Supprimer(x, arbre_vide) = arbre_vide
Si clé(x) = clé(contenu(r)) et est_vide(D) = vrai alors Supprimer(x, <r, G, D>) = G
Sinon si clé(x) = clé(contenu(r)) et est_vide(G) = vrai alors Supprimer(x, <r, arbre_vide,
D>) = D
sinon si clé(x) = clé(contenu(r)) alors Supprimer(x, <r, G, D>) =<Max(G),
SupprimerMax(G), D>
Si clé(x) < clé(contenu(r)) alors Supprimer(x, <r, G, D>) = <Max(G), SupprimerMax(G), D>
Si clé(x) < clé(contenu(r)) alors Supprimer(x, <r, G, D>) = <r, Supprimer(x, G), D>
Si clé(x) > clé(contenu(r)) alors Supprimer(x, <r, G, D>) = <r, G, Supprimer(x, D)>

b) Réalisation
Fonction Max(A : Arbre_Rech) : Pnoeud
Si est_vide(droite(A)) alors max  A
Sinon Max Max(droite(A))
Fsi

Fonction SupprimerMax(A : Arbre_Rech) : Arbre_Rech


Si est_vide(droite(A)) alors SupprimerMax  gauche(A)
Sinon SupprimerMax  cons(A, gauche(A),
SupprimerMax(droite(A)))
Fsi

Fonction Supprimer(Element x, Arbre_Rech A) : Arbre_Rech


Si est_vide(A) alors Supprimer  A
Sinon si x > contenu(racine(A)) alors
Supprimer  cons(A, gauche(A), Supprimer(x, droite(A)))
sinon si x < contenu(racine(A)) alors
Supprimer  cons(A, Supprimer(x, gauche(A)), droite(A))
sinon Si est_vide(droite(A)) alors Supprimer  gauche(A)
sinon Si est_vide(gauche(A)) alors Supprimer  droite(A)
sinon supprimer  cons(Max(gauche(A)),
SupprimerMax(gauche(A)), droite(A))
fsi
fsi
fsi
fsi
Fsi
2-4-6- Complexité des opérations dans un ABR
On montre que, les opérations de recherche, insertion et suppression dans un ABR contenant
n éléments sont :
– En moyenne en O(log2(n));
– Dans le pire des cas en O(h);
Où h désigne la hauteur de l’arbre
Si l’arbre est dégénéré, sa hauteur étant n-1, ces trois opérations sont en O(n)
Si l’arbre est équilibré, les opérations sont en O(log2(n))

2-5- Arbres Maximiers ou Tas

M. E. RIFFI Page 46 2021/2022


Algorithmique et structures de données

2-5-1- Notion de Tas


Appelé aussi Heap (en anglais)
C’est un arbre binaire parfait tel que la clé de chaque nœud est supérieure ou égale aux
clés de tous ses fils.
L’élément maximum de l’arbre se trouve donc à la racine.
Rappel :
– Pour un arbre binaire parfait, tous les niveaux sont entièrement remplis sauf
éventuellement le dernier et, dans ce cas, les feuilles du dernier niveau sont
regroupées le plus à gauche possible.
Un tas est un arbre binaire partiellement ordonné :
– Les nœuds sur chaque branche sont ordonnés sur celle-ci;
– Ceux d’un même niveau ne le sont pas nécessairement.
Un tas dans lequel chaque nœud enfant a une clé inférieur (resp. supérieure) ou égale à la
clé de son père est appelé arbre maximier (max heap) (resp. arbre minimier (min heap)).

2-5-2- Exemple :

2-5-3- Type Abstrait Tas


Type Tas
Utilise Booleen, Element
Opérations
Tas_vide :  Tas
Est_vide : Tas  Booleen
Max : Tas  Element
Ajouter : Tas x Element  Tas
SupprimerMax : Tas  Tas
Appartient : Tas x Element  Booleen

Préconditions
Max(T) est_défini_ssi est_vide(T) = faux
SupprimerMax(T) est_défini_ssi est_vide(T) = faux
Ajouter(T, e) est_défini_ssi Appartient(T, e) = faux

Axiomes
Soit T, T1 : Tas, e, e1 : Element
Si est_vide(T) = vrai alors Appartient(T, e) = faux
Sinon si T = ajouter(T1, e1) alors
Appartient(T, e) = (e=e1) ou Appartient(T1, e)
Sinon si T = SupprimerMax(T1) alors
appartient (T, e) = (e ≠ Max(T1)) et Appartient(T1, e)
Apparient(T, Max(T)) = vrai
Si Appartient(T, e) = vrai alors Max(T) ≥ e

M. E. RIFFI Page 47 2021/2022


Algorithmique et structures de données

2-5-4- Opérations sur un Tas


Tas_vide :  Tas
Opération d’initialisation; crée un tas vide
Est_vide : Tas  Booleen
Vérifie si un tas est vide ou non
Max : Tas  Element
Retourne le plus grand élément d’un tas
Ajouter : Tas x Element  Tas
Ajoute un élément dans un tas
SupprimerMax : Tas  Tas
Supprime le plus grand élément d’un tas
Appartient : Tas x Element  Booleen
Vérifie si un élément appartient ou non à un tas.

2-5-5-Représentation d’un Tas


Il existe une représentation compacte pour les arbres binaires parfaits, et donc pour les tas :
– La représentation par tableau, basée sur la numérotation des nœuds niveau par
niveau et de gauche à droite.
Les numéros d’un nœud sont donc les indices dans un tableau. En outre, ce tableau s’organise
de la façon suivante :
– Le nœud racine a pour indice 1 (0 en C);
– Soit le nœud d’indice i dans le tableau, son fils gauche a pour indice 2i et son
fils droit a pour indice 2i+1
– Si un nœud a un indice i (i≠1), alors son père a pour indice i/2
On déduit de cette organisation, où n désigne le nombre d’éléments du tas, que :
– Un nœud d’indice i est une feuille si 2i ≥n
– Un nœud d’indice i a un fils droit si 2i+1 <n
2-5-6- Représentation en C d’un Tas
#define Max_ELEMENTS 200
typedef int Element
typedef struct {
int taille;
Element tableau[MAX];
} Tas;
2-5-7- Opération ajout dans un tas
a) Principe :
– Créer un nouveau nœud contenant la clé du nouvel élément;
– Insérer cette clé le plus à gauche possible sur le dernier niveau du tas (ou si le
dernier niveau est plein, à l’extrême gauche d’un nouveau niveau). La nouvelle
clé est insérée dans la première case non utilisée du tableau;
– Faire "remonter cette nouvelle clé" à sa place en la permutant avec la clé de
son père, tant qu’elle est plus grande que celle de son père.
b) pseudo code :
Fonction ajouter(Tas t, Element e) : Tas
Debut
i  t.taille +1
t[i]  e
t.taille  i
tant que ((i >1) et (t[i] < t[(i) div 2])) faire

M. E. RIFFI Page 48 2021/2022


Algorithmique et structures de données

echanger(t[i], t[(i) div 2])


i  (i) div 2
ftantque
ajouter  t
fin
c) complexité :
La complexité de l’opération d’ajout est en O(h), où h est la hauteur du tas :
– On ne fait que remonter un chemin ascendant d’une feuille vers la racine (en
s’arrêtant éventuellement avant).
– La taille d’un tas de taille n est précisément égale à log2(n) et donc l’ajout
demande un temps O(log(n)).
2-5-8- Opération de suppression du maximum
a) Principe :
– Remplacer la clé du nœud racine par la clé du nœud situé le plus à droite du
dernier niveau du tas. Ce dernier nœud est alors supprimé;
– Réorganiser l’arbre, pour qu’il respecte la définition du tas, en faisant
descendre la clé de l’élément de la racine à sa bonne place en permutant avec
le plus grand des fils.
b) Pseudo code
Procédure Entasser(Tableau t[1..n], entier i)
Debut
si ((2i+1=n) ou (t[2i] ≥ t[2i+1])) alors
k  2i
sinon
k  2i +1
fsi
si t[i] < t[k] alors
echanger(t[i], t[k])
si (k ≤ (n div 2)) alors
Entasser(t, k)
fsi
fsi
Fin

Fonction SupprimerMax(Tas t) : tas


Entier i
Debut
t.Taille  t.Taille -1
t[1]  t[t.taille]
i1
Entasser(t, 1)
SupprimerMax  t
Fin

c) Complexité
La complexité de la suppression est la même que celle de l’insertion, c.-à-d. O(log(n)) :
En effet, on ne fait que suivre un chemin descendant depuis la racine

2-5-9- Opération de recherche du maximum


Fonction Max(tas t) : Element

M. E. RIFFI Page 49 2021/2022


Algorithmique et structures de données

Debut
Max  t[1]
Fin

La complexité de la recherche du maximum dans un tas est O(1) :

2-5-9- Algorithme de tri par tas


a)Principe
Supposons qu’on veut trier, en ordre croissant un tableau T de n éléments.
L’algorithme du tri par tas commence, en utilisant la fonction ConstruireTas, par
construire un tas dans le tableau à trier T;
Ensuite, il prend l’élément maximal du tas, qui se trouve en T[1], l’échange avec T[n], et
rétablit la propriété de tas, en utilisant l’appel de fonction Entasser(T, 1) pour le nouveau
tableau à n -1 éléments (la case T[n] n’est pas considérée);
L’algorithme de tri par tas répète ce processus pour le tas de taille n-1 jusqu’à la taille 2.

b) Pseudo code
Fonction ConstruireTas(Tableau T[1..n]) : Tas
Debut
Pour i  n à 2 par pas -1 faire
echanger(T[1], T[i])
Fpour
n  n-1
Entasser(T, i)
Tri_par_Tas  T
Fin

Fonction ConstruireTas(Tableau T[1..n]) : Tas


Debut
construireTas(T)
Pour i (n div 2) à 1 par pas -1 faire
Entasser(T, i)
Fpour
ConstruireTas  T
Fin

c) Complexité
On montre que l’appel à construire Tas prend un temps O(n)
Chacun des (n-1) appels à Entasser prend un temps O(log(n))
Par conséquent, l’algorithme du tri par tas s’exécute en O(n log(n))

M. E. RIFFI Page 50 2021/2022


Algorithmique et structures de données

2-6- Arbres binaires de recherche équilibrés


La définition des arbres équilibrés impose que la différence entre les hauteurs des fils
gauche et des fils droit de tout nœud ne peut excéder 1
Il faut donc maintenir l’équilibre de tous les nœuds au fur et à mesure des opérations
d’insertion ou de suppression d’un nœud
Quand il peut y avoir un déséquilibre trop important entre les deux fils d’un nœud, il faut
recréer un équilibre par :
Des rotations d’arbres ou par éclatement de nœuds (cas des arbres B)
Les algorithmes de rééquilibrage sont très compliqués :
On cite entre autres quelques exemples d’arbres équilibrés pour lesquels les
opérations de recherche, d’insertion et de suppression sont en O(log(n))
2-7- Arbres binaires de recherche équilibrés (Exemples)
a) Arbre AVL :
– Introduits par Adelson Velskii Landis (d’où le nom) dans les années 60;
– Un arbre AVL est un ABR stockant une information supplémentaire pour
chaque nœud : son facteur d’équilibre;
– Le facteur d’équilibre représente la différence des hauteurs entre son sous arbre
gauche et son sous arbre droit;
– Au fur et à mesure que des nœuds sont insérés ou supprimés, un arbre AVL
s’ajuste de lui-même pour que tous ses facteurs d’équilibres restent à 0, -1 ou
1.
b) Arbres rouges et noirs :
– Des ABR qui se maintiennent eux-mêmes approximativement équilibrés en
colorant chaque nœud en rouge ou noir;

– En contrôlant cette information de couleur dans chaque nœud, on garantit


qu’aucun chemin ne peut être deux fois plus long qu’un autre, de sorte que
l’arbre reste équilibré.
c) B Arbre :
– ABR équilibré qui est conçu pour être efficace sur d’énorme masse de données
stockées sur mémoires secondaires;
– Chaque nœud permet de stocker plusieurs clés;
– Généralement, la taille d’un nœud est optimisée pour coïncider avec la taille
d’un bloc (ou page) du périphérique, en vue d’économiser les coûteux accès
d’entrées sorties.

M. E. RIFFI Page 51 2021/2022


Algorithmique et structures de données

V- GRAPHE
1- Définitions et vocabulaire
1-1- Graphe orienté et non orienté
Un graphe orienté est un couple (V, E) où V est un ensemble fini et E une relation
binaire sur V.
V est l'ensemble des sommets (vertex en anglais, au pluriel vertices)
E est l’ensemble des arcs (edge en anglais)
Il y a alors possibilité d'un arc reliant un sommet à lui-même ce qu'on appelle une boucle.

Un graphe non orienté est un couple (V, E) où V est un ensemble fini et E une
relation binaire symétrique sur V et non réflexive. E est alors l'ensemble des arêtes
(edge également en anglais).

Etant donné un graphe non orienté G = (V, E), sa version orientée G' est le graphe G' =
(V, E') où (u, v)  E' si et seulement si (u, v)  E.
Chaque arête de G est donc recopiée en 2 arcs pour G'.

Etant donné un graphe orienté G = (V, E) sa version non orientée G' est le graphe G' =
(V, E') où (u, v)  E' si et seulement si [ u ≠ v ] et [ (u, v)  E ou (v, u)  E ]
On enlève les sens des orientations et les boucles.

1-2- Degré d’un graphe


Dans un graphe orienté, si (u, v) est un arc, on dit que v est un successeur de u, et u
un prédécesseur de v.
Le demi-degré extérieur de u est le nombre de successeurs de u; il est noté do+(u).
Le demi-degré intérieur de u est le nombre de prédécesseurs de u; il est noté do-(u).
Le degré total est alors la somme des demi-degrés intérieur et extérieur; il est noté do(u).
Dans un graphe non orienté, si (u, v) est une arête, on dit que u et v sont adjacents.
Le degré de u est alors le nombre de sommets adjacents à u.

Exemple :

1-3- Chemin dans un graphe


Un chemin de longueur k d'un sommet u à un sommet u' dans un graphe G = (V, E)
est une suite (v0, v1, v2, ..., vk) de sommets vérifiant :
u = v0 , u' = vk et (vi - 1, vi) ∈ E pour i = 1, 2,...k
Un chemin est simple si tous les sommets utilisés sont distincts.

M. E. RIFFI Page 52 2021/2022


Algorithmique et structures de données

Un chemin (v0, v1, ..., vk) est un cycle si k> 0 et si v0 = vk. C'est un cycle simple si de
plus v1, v2,...,vk sont distincts.
Dans un graphe orienté, il existe de cycles de longueur 1: ce sont les boucles.
Dans un graphe non orienté, il n'y a pas de cycles de longueur 1. Les cycles de longueur 2
sont les cycles (u, v, u) qu'on fabrique avec une arête (u, v). Dans un graphe non orienté,
on réserve alors la dénomination de cycle à des cycles simples de longueur ≥ 3.

1-4- Composantes connexes d'un graphe non orienté


On définit la relation :
v est atteignable à partir de u si et seulement si
il existe un chemin de longueur k ≥ 0 d'origine u et d'extrémité v.
C'est une relation d’équivalence : elle est réflexive car k = 0 est admis; elle est symétrique
car le graphe est non orienté; elle est transitive, car on "concatène" les chemins.
Par définition, les composantes connexes d'un graphe non orienté G sont les classes
d'équivalence pour la relation: " être atteignable à partir de ".

1-5- Composantes fortement connexes d'un graphe orienté


Pour un graphe orienté, la relation "être atteignable à partir de" est toujours réflexive et
transitive, mais elle n'est plus symétrique. On considère alors sa symétrisée.
v et u sont mutuellement atteignables si et seulement si
il existe un chemin (de longueur k ≥ 0) d'origine u et d'extrémité v et un chemin (de
longueur l ≥ 0) d'origine v et d'extrémité u.
Par définition, les composantes fortement connexes d'un graphe orienté sont les classes
d'équivalence de G pour la relation : " être mutuellement atteignables ".

1-6- Arbres et arborescences


a) non-orienté orienté :
Un arbre est un graphe non orienté connexe sans cycle
b) orienté :
Une arborescence est un graphe orienté comportant une racine et dont le graphe non
orienté sous-jacent est un arbre.
c) forêt :
Une forêt est un graphe dont chaque composante connexe est un arbre.
Une forêt est un graphe dont chaque composante fortement connexe est une
arborescence.

d) Définition d’une racine pour un graphe orienté :


Un sommet r d’un graphe orienté G est une racine si pour tout sommet x de G, il
existe un chemin de r vers x.

1-7- Notion de graphe valué :


Il est possible de définir une fonction de coût :
C: E →ℝ
sur un graphe G = (V, E) orienté ou non orienté. On attribue ainsi un poids à chaque arête
(ou à chaque arc) du graphe G. On dit alors que le graphe est valué.

La figure 3 donne un exemple de graphe orienté valué et la figure 4 un exemple de graphe


non orienté valué.

M. E. RIFFI Page 53 2021/2022


Algorithmique et structures de données

2- Types abstraits graphes


Type Graphe {cas orienté}
utilise Sommet, Entier, Booléen
opérations
graphe-vide : → Graphe
ajouter-le-sommet _ à _ : Sommet X Graphe → Graphe
ajouter-l'arc <_ , _> à _ : Sommet X Sommet X Graphe → Graphe
_ est-un-sommet-de _ : Sommet X Graphe → Booléen
<_ , _> est-un-arc-de _ : Sommet X Sommet X Graphe → Booléen
d°+ de _ dans _ : Sommet X Graphe → Entier
_ ème-succ-de _dans _ : Entier X Sommet X Graphe → Sommet
d°- de _ dans _ : Sommet X Graphe → Entier
_ ème-pred-de _ dans _ : Entier X Sommet X Graphe → Sommet
retirer-le-sommet _ de _ : Sommet X Graphe → Graphe
retirer-l'arc<_ , _> de _ : Sommet X Sommet → Graphe
Préconditions
soit s, s', s" Sommet, g Graphe, i, j Entier
ajouter-le-sommet s à g est-défini-ssi s est-un-sommet-de g = faux
ajouter-l'arc < s, s' > à g est-défini-ssi s ≠ s' & (< s, s'> est un-arc-de g) = faux
d°+ de s dans g est-défini-ssi s est-un-sommet -de g = vrai
ième-suc-de s dans g est-défini-ssi
(s est-un-sommet-de g) = vrai et (i ≤ d°+ de s dans g) = vrai
retirer-le-sommet s de g est-défini-ssi s est-un-sommet-de g = vrai
retirer-l'arc < s, s' > de g est-défini-ssi < s, s' > est-un-arc-de g = vrai

3- Compléments à la spécification des graphes orientés


A partir de ces opérations de base, on peut définir d'autres opérations qui sont très
utilisées dans les algorithmes travaillant sur un graphe orienté :
premsucc : Sommet X Graphe → Sommet
est définie pour tout sommet s et tout graphe g tels que d°+ de s dans g ≥ 1,
premsucc(s, g) = 1 ème-succ-de s dans g
succsuivant : Sommet X Sommet X Graphe → Sommet
est définie pour tout couple de sommets s, s' et tout graphe g tels que:
il existe i, 1≤ i < d°+ de s dans g, tel que s' = i ème-succ-de s dans g

M. E. RIFFI Page 54 2021/2022


Algorithmique et structures de données

et on a alors:
succsuivant(s, s', g) = (i+1)ème-succ-de s dans g
coût : Sommet X Sommet X Graphe → Réel
Cette opération est définie pour toute paire ordonnée de sommets (s, s') et tout graphe g
tels que < s, s' > est-un-arc-de g = vrai.
Il faut modifier dans ce cas le profil de l'opération d'ajout d'un arc, car il faut prévoir le
coût de cet arc en opérande :
ajouter-l'arc < _, _> de-coût _ à _ : Sommet X Sommet X Réel X Graphe → Graphe
arc: on lui fait rendre pour ces paires une valeur dont on sait qu'elle ne peut être un coût.

D'autres opérations peuvent être définies à partir de la spécification ci-dessus, par exemple
les opérations nb-sommets et nb-arcs.
Très souvent, on rencontre dans les algorithmes sur les graphes le schéma suivant de
traitement des successeurs d'un sommet i: « pour chaque sommet j, successeur de i,
effectuer un certain traitement sur j ». Ce schéma s'écrit en utilisant les opérations de
base:
Pour j = 1 à d°+ de i dans g faire
traiter(jème-succ-de i dans g);

3-1- Spécification des graphes non orientés


Type Graphe {cas non orienté}
utilise Sommet, Entier, Booléen
opérations
graphe-vide : → Graphe
ajouter-le-sommet _ à _ : Sommet X Graphe → Graphe
ajouter-l'arête <_ , _> à _ : Sommet X Sommet X Graphe → Graphe
_ est-un-sommet-de _ : Sommet X Graphe → Booléen
<_ , _> est-une-arête-de _ : Sommet X Sommet X Graphe → Booléen
d° de _ dans _ : Sommet X Graphe → Entier
_ ème-succ-de _dans _ : Entier X Sommet X Graphe → Sommet
retirer-le-sommet _ de _ : Sommet X Graphe → Graphe
retirer-l'arête <_ , _> de _ : Sommet X Sommet → Graphe
Axiomes
comme pour graphe orienté, en remplaçant arc par arête et d°+ par do. Il faut cependant
modifier et ajouter des axiomes, car on doit avoir pour tous sommets s, s' et tout graphe g:
< s, s' > est-une arête- de g = < s', s > est-une-arête-de g
Par exemple, quand on ajoute une arête {s, s'}, cela a pour conséquence que
< s, s' > est-une-arête-de g = vrai et < s', s > est-une-arête-de g = vrai;
de même, le demi-degré de s et le demi-degré de s' sont tous les deux augmentés de 1, ou
initialisés
à 1 s'il s'agit d'un nouveau sommet. On définit les opérations premsucc et succsuivant, et
le schéma de traitement de successeurs d'un sommet et la fonction de coût, comme dans le
cas des graphes orientés.

3-2- Représentation des graphes


3-2-1- Représentation par matrice d’adjacence
L'ensemble de sommets du graphe n'évolue pas; on représente l'ensemble des arcs par
un tableau de booléens; comme chaque arc est une paire ordonnée de sommets, le graphe
est représenté par une matrice carrée de booléens, dite matrice d'adjacence, de dimension
n (si le graphe a n sommets)

M. E. RIFFI Page 55 2021/2022


Algorithmique et structures de données

Déclaration en C :
int G[n][n];

L'élément d'indices i et j de la matrice est vrai si et seulement si il existe un arc entre


les sommets i et j.
Les successeurs du sommet i sont les indices j correspondant à des éléments non nuls de
la ligne i :
Succ(i) = {j tel que G[i][j]== 1}
De même les prédécesseurs du sommet i sont les indices j correspondant à des éléments
non nuls de la colonne i :
Préd(i) = {j tel que G[j][i]== 1}

a) Exemple graphe orienté non valué

Dans le cas où le graphe est non orienté, la matrice est symétrique. Dans le cas où le
graphe est valué, on utilise une matrice où l'éléments d'indices i et j a pour valeur le poids
et l'arc/arête du sommet i au sommet j, si cet arc/arête existe, et sinon une valeur dont on
sait qu'elle ne peut être un poids: par exemple, le plus grand entier utilisable si les poids
sont des entiers bornés supérieurement.

b) Exemple graphe orienté valué

3-2-2- Représentation par listes d'adjacence


Une autre représentation classique des graphes consiste à représenter l'ensemble de
sommets et à associer à chaque sommet la liste de ses successeurs rangés dans un certain

M. E. RIFFI Page 56 2021/2022


Algorithmique et structures de données

ordre. Ces listes sont appelées listes d'adjacence. Dans le cas où l'ensemble des sommets
n'évolue pas, ces listes sont accessibles à partir d'un tableau S, qui contient pour chaque
sommet, un pointeur vers le début de sa liste. On a donc une déclaration C du genre :

typedef struct Succ


{int no ; // numéro de sommet appartenant à l’intervalle [0..n-1]
struc Succ *suivant;
} succ, *psucc ;
succ *G[n] ; // le graphe représenté par ses n listes d’adjacence

Il est facile de prendre en compte l'existence de poids sur les arcs en ajoutant une
donnée à la struct succ. L'intérêt de cette représentation est que l'espace mémoire utilisé
est, pour un graphe orienté avec n sommets et p arcs, en O(n+p). Dans le cas d'un graphe
non orienté avec p arêtes, l'espace mémoire est en O(n+2p). De plus, quand on a besoin de
faire un traitement sur les successeurs d'un sommet s, le nombre de sommets parcourus est
exactement le nombre de successeurs de s, soit d°+(s).

Exemple

4- Représentation des « faworis »


Cette représentation ne peut s’appliquer qu’à des graphes non évolutifs, dont le
nombre de sommets et d’arcs est fixé une fois pour toute. FAWOri signifie « First Arc
With ORIgin ». Le graphe est stocké dans deux tableaux comme le montre l’exemple ci-
dessous :

M. E. RIFFI Page 57 2021/2022


Algorithmique et structures de données

5- Implémentation en C
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int extrem;
float valuation;
} arc;
typedef struct {
int nbsom;
int nbarc;
int *tab_som;
arc *tab_arc;
} graphe;
void alloue_graphe(int nbs, int nba, graphe *g)
{ g->nbsom = nbs; g->nbarc = nba;
g->tab_som = (int *)calloc(g->nbsom+2,sizeof(int));
g->tab_arc = (arc *)calloc(g->nbarc+0,sizeof(arc));
}
void affiche_arc(arc a)
{ printf("(%4d - %7.2f)",a.extrem, a.valuation); }
void affiche_graphe(graphe g)
{ int i;
printf("nombre de sommets = %d",g.nbsom);
printf(" nombre d'arcs = %d\n",g.nbarc);
printf("tableau des sommets = ");
for (i=1;i<=g.nbsom+1;i++) printf("%4d\t",g.tab_som[i]); printf("\n");
printf("tableau des arcs = ");
for (i=0;i<g.nbarc;i++) affiche_arc(g.tab_arc[i]); printf("\n");
}

void affiche_successeurs(int i,graphe g)


/* affichage des successeurs du sommet i */
{ int j;
printf("successeurs de %d = ",i);
for (j=g.tab_som[i];j<g.tab_som[i+1];j++) affiche_arc(g.tab_arc[j]);
printf("\n");
}
void charge_graphe(char *nom_fich, graphe *g)
{FILE *fich;
int nbs, nba, i, iarc, ori, ori_av;
fich=fopen(nom_fich,"rt");
fscanf(fich,"%d %d",&nbs,&nba);
/* les sommets du graphe sont numérotés à partir de 1 */
/* on fait donc démarrer tab_som a l'indice 1 */
/* en revanche tab_arc peut démarrer a l'indice 0 */
alloue_graphe(nbs,nba,g);
ori_av=0;
for (iarc = 0; iarc < g->nbarc ; iarc++)
{fscanf(fich,"%d %d %f",&ori,&(g->tab_arc[iarc].extrem),
&(g->tab_arc[iarc].valuation));

M. E. RIFFI Page 58 2021/2022


Algorithmique et structures de données

if (ori != ori_av) {for (i = ori_av+1; i<= ori; i++)


g->tab_som[i] = iarc;
ori_av = ori;
}
}
for (i= ori+1; i<= g->nbsom+1; i++) g->tab_som[i] = g->nbarc;
fclose(fich);
}

int main()
{ graphe g;
charge_graphe("pcc.txt",&g);
affiche_graphe(g);
affiche_successeurs(1,g);
affiche_successeurs(4,g);
affiche_successeurs(5,g);
affiche_successeurs(6,g);
}

6- Parcours dans les graphes


6-1- Parcours en “largeur d’abord”
Etant donnés un graphe G = (V, E) et un sommet s, qu'on appelle la source, on définit,
pour chaque sommet v qu'on peut atteindre à partir de s, la "distance" d[v] comme étant le
nombre minimal d'arêtes que contient un chemin reliant s à v. Cette notion a un sens pour
les graphes orientés comme pour les graphe non orientés.

Le parcours de G "en largeur d'abord" découvre tous les sommets qui sont à distance k
de avant de découvrir un quelconque sommet à distance k+1.
C'est la généralisation du parcours d'un arbre "en largeur d'abord" (parcours par niveaux).
Il utilise une file, et un repérage des sommets en 3 couleurs :
- Blanc, pour les sommets qui n'ont pas encore été visités,
- Gris, pour les sommets qui sont "en cours" de visite : ils sont dans la file, mais
leurs successeurs n'y sont pas encore,
- Noir, pour les sommets pour lesquels la visite est terminée : on les retire de la
file, en y mettant tous leurs successeurs.

Parcours_largeur (G: graphe);


debut
Pour chaque sommet u ∈ V faire
couleur [u] = blanc;
d[u]= ∞ ;
Préd[u] = RIEN;
Pour chaque sommet u ∈ V faire
si couleur [u] == blanc alors Visite_largeur(u);
fin
Visite_largeur (s : sommet);
debut
couleur[s] = gris;
d[s] = 0;
Préd[s] = RIEN;

M. E. RIFFI Page 59 2021/2022


Algorithmique et structures de données

F = file_vide;
Enfiler(s,F);
Tant que F != file_vide faire
u= Tête(F);
Pour chaque sommet v successeur de u faire
si couleur [v] == blanc alors
couleur[v] = gris;
d[v] = d[u]+1;
Préd[v] = u;
Enfiler (v, F);
Défiler (F);
couleur [u] = noir;
Tant que F != file_vide faire
u= Tête(F);
Pour chaque sommet v successeur de u faire
si couleur [v] == blanc alors
couleur[v] = gris;
d[v] = d[u]+1;
Préd[v] = u;
Enfiler (v, F);
Défiler (F);
couleur [u] = noir;
fin

6-2- Parcours “en profondeur d’abord”


on parcourt une "branche" jusqu'à son extrémité.
Quand un sommet v est découvert lors du parcours d'une liste d'adjacence d'un sommet u
visité précédemment, le parcourt en profondeur effectue l'affectation Préd[v] =u.
Le même principe du coloriage en 3 couleurs est effectué lors d'un parcours en
profondeur:
 Blanc: pour les sommets non encore traités,
 Gris: pour les sommets en cours de traitement,
 Noir: pour les sommets dont le traitement est terminé.
Le principe est le même que pour le parcours en profondeur d'un arbre binaire. Mais il y a
deux différences qui proviennent de la possible non-connexité ou de la présence des
cycles.
A cause de la possible non-connexité, le parcours en profondeur à partir d'un sommet peut
ne pas parcourir tout le graphe.
A cause de l'éventuelle présence des cycles, il faut faire attention de ne pas traiter
plusieurs fois le même sommet, d'où la nécessité d'un marquage en noir

Parcours_profondeur (G: graphe);


debut
Pour chaque sommet u de V faire
couleur [u] = blanc;
Préd[u] = RIEN;
temps = 0;
Pour chaque sommet u de V faire
si couleur [u] == blanc alors Visite_profondeur(u);
Fin

M. E. RIFFI Page 60 2021/2022


Algorithmique et structures de données

Visite_profondeur (u : sommet);
debut
couleur [u] = gris;
temps = temps+1;
début[u] = temps;
Pour chaque sommet v successeur de u faire
si couleur [v] == blanc alors
Préd[v] =u;
Visite_profondeur(v);
couleur[u] = noir;
temps = temps +1;
fin[u] = temps;
fin

6-3- Analyse de la complexité du parcours en profondeur d’abord


Pour un graphe ayant n sommets et p arcs/arêtes (p ≤ n2 ), chaque sommet est colorié
une et une seule fois, si bien qu’il y a n appels de la fonction Visite_profondeur.
L’examen des couleurs se fait
exactement n fois dans la fonction Parcours_profondeur; dans la fonction
Visite_profondeur, il se fait pour chaque successeur y d’un sommet x, et ceci pour tous les
sommets x.
Dans le cas de la représentation par matrice d’adjacence, la consultation de la matrice
nécessite un temps en O(n2), d’où une complexité totale en O(n2).
Dans le cas de la représentation par listes d’adjacence, cela revient à parcourir l’ensemble
des listes de successeurs : le temps requis est O(p). La complexité totale est alors en
O(max(n,p)).
On peut montrer que les résultats sont les mêmes dans le cas du parcours en largeur
d’abord.

M. E. RIFFI Page 61 2021/2022


Algorithmique et structures de données

VI- METHODES DE HACHAGE


1- Objectif
Réduire l’intervalle des indices.

h : fonction de hachage
T : table de hachage
m : taille tableau T
h:U {0, 1, …, m – 1}

2- Définition
Pour toute clé x, h(x) appelée valeur de hachage primaire, donne l'indice de la place
de x dans un tableau (ou une zone de mémoire) appelé table de hachage.
La fonction h est appelée fonction de hachage. Elle distribue les clés de façon aléatoire, mais
elle doit évidemment être déterministe: la valeur de hachage d'une clé donnée doit toujours
être la même pour pouvoir retrouver l'élément correspondant.
De plus, si h n’est pas injective on dit qu’il y’a collision, on attend de cette fonction qu'elle
soit uniforme afin de limiter les colisions c-à-d en termes de probabilité:
x et  i ε [0,m-1], Proba(h(x) = i) = 1/m
Enfin, la fonction de hachage doit être facilement et rapidement calculable.

3- Exemple de fonction de hachage:


On peut prendre tout simplement le reste dans la division par m (m est la taille de la table de
hachage) de la valeur de la clé:
h(x)= cle(x) mod m

Exemple
Soit E l'ensemble de mots suivant:
E={serge, odile, luc, anne, annie, jean, julie, basile, paula, marcel, élise}
On considère ici que la clé est l'élément lui-même; un exemple de hachage consiste à associer
à chaque élément e de E un nombre h(e), compris entre 0 et 12, en procédant comme suit.

M. E. RIFFI Page 62 2021/2022


Algorithmique et structures de données

Attribuer aux lettres a, b, c,...,z les valeurs 1, 2, 3,...,26.


Ajouter les valeurs des lettres de e.
Ajouter au nombre obtenu le nombre de lettres de e.
Calculer ce dernier nombre modulo 13 pour obtenir la valeur cherchée h(e).
En utilisant ce procédé de calcul, on obtient:
h(serge) =(54+5)mod13=7,
h(odile) =(45+5)mod13=11,
h(luc) =(36+3)mod13=0,
h(anne) =(34+4)mod13=12,
h(annie) =(43+5)mod13=9,
et de même:
h(jean)=8, h(julie)=10, h(basile)=2,
h(paula)=4, h(marcel)=6, h(élise)=3.

Remarque:
 Dans cet exemple, la restriction de h à E est injective et l'on peut alors ranger chaque
élément e à l'adresse h(e).
 On initialise le tableau au début par une valeur spéciale, afin de reconnaître les places
vides,
 La valeur de m doit être premier, sinon la clé aura la même parité que m et donc les
indices de la table.

4- Méthodes de hachage
4-1- Méthodes de résolution des collisions par chaînage (ou hachage indirect):

Dans cette méthode, tous les éléments en collision sont chaînés entre eux à l'extérieur
de la table de hachage: la table contient seulement les têtes de m listes chaînées représentant
chacune un ensemble d'éléments qui rendent une même valeur par la fonction de hachage.

Exemple:
hachage par adjonctions successives des éléments:

M. E. RIFFI Page 63 2021/2022


Algorithmique et structures de données

e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13
ayant respectivement pour valeur de hachage
2, 0, 3, 0, 3, 0, 4, 8, 1, 5, 4, 2, 4

Remarque:
L’ajout est précédé d’un test d’appartenance, on ajoute en fin (ou au début de liste),
Pour rechercher, supprimer ou ajouter l’élément x, on travaille avec la liste Lh(x). (avec Li, la
liste dont la tête est T[i]).

4-2- Méthodes de résolution des collisions par calcul (chaînage direct)


Lorsqu’il y a collision on calcul une nouvelle place dans la table à partir de la clé de l’élément
considéré (pour le rechercher, l’ajouter ou le supprimer).
Exemple :
E={e1, e2, e3, e4, e5, e6, e7, e8, e9}
Avec respectivement :
h(x)=5, 3, 6, 3, 7, 1, 4, 8, 7

Remarque :
Après ajout de e5, tout élément dont la valeur de hachage primaire est entre 3 et 8 ira se
placer à l’adresse 8

M. E. RIFFI Page 64 2021/2022


Algorithmique et structures de données

Cette méthode est appelée hachage linéaire

5- Double hachage
Pour éviter la formation de groupements d’éléments contigus, il faut essayer de les
disperser davantage lorsqu’il y a collision, on utilise une deuxième fonction de hachage.

M. E. RIFFI Page 65 2021/2022

Vous aimerez peut-être aussi