Structures de données : Piles et Arbres
Structures de données : Piles et Arbres
Sommaire ................................................................................................................................................ 1
Chapitre 1 : Les piles et les files.............................................................................................................. 2
I - Définition : ................................................................................................................................ 33
II – Implémentation : ..................................................................................................................... 34
I- Représentations :............................................................................................................................ 46
1
Chapitre 1 : Les piles et les files
Les piles et les files sont des ensembles dynamiques d’objets auxquels on accède en respectant des
stratégies bien définies.
La stratégie d’accès à une pile est la stratégie F.I.L.O. (First In Last Out).
Soit E un ensemble d’éléments. On note PE l’ensemble des piles sur E.
I.1.a Actions :
créer : réserver de l’espace libre pour une pile.
I.1.b Axiomes :
Soient p une pile et x un élément (objet).
vide(ajouter(x,p)) = FAUX
valeur(ajouter(x,p)) = x
supprimer(ajouter(x,p)) = p
I.1.c Exemple :
valeur(supprimer(supprimer(ajouter(3,ajouter(2,supprimer(Ajouter(1,ajouter(8,créer))))))))=8
2
créer (p) :
déclarer ou allouer de l’espace pour un tableau p indicé de 1 à N et de même type que
les éléments de E.
ipile ← 0
valeur(p) :
si ipile=0 alors
afficher(’valeur non définie’)
sinon
retourner p[ipile]
ajouter(x,p) :
ipile ← ipile+1
si (ipile > N) alors
afficher(’implémentation insuffisante’)
sinon p[ipile] ← x
supprimer(p) :
si ipile=0 alors
afficher(’opération non définie’)
sinon
ipile ← ipile-1
L’écriture postfixée est une forme d'écriture d'expressions algébriques qui se distingue par la position
relative qu'y prennent les opérateurs et leurs opérandes. Un opérateur est écrit après ses opérandes en
notation postfixée.
8+2 → 8 2 +
8+2*3 → 8 2 3 * +
8 6 + 3 5 – 12 * + → 8+6+(3-5)*12
Définition formelle :
exemple :
8 5 + * ou 432– sont incorrectes
3
par convention, on pose :
priorité(^) = 3 ; priorité(*, / )=2 ; priorité( + , - ) =1 ; priorité(‘(‘) =0
Algorithme :
données : expression arithmétique usuelle représentée par une constante de type chaine de caractères.
on définit comme opérandes les noms de variables écrits avec une seule lettre.
4
I.4 Implémentation en langage c d’un module de gestion de piles
#define N 50
typedef struct { int tab_p[N];//le tableau sera indicé de 0 à N-1
int ip;
} pile;
void creerp(pile *);
int videp (pile );
int valeurp(pile );
int ajouterp(int,pile *);
int supprimerp(pile *);
void aff_pile(pile ); // affichage du contenu d’une pile
void remplir_pile(pile *);
5
void remplir_pile(pile *p){
int i;
printf("remplir_pile_int\n");
for (;;) {
printf("donner un entier: (-1 pour arreter) :");
scanf("%d",&i);
if (i>=0) ajouterp(i,p);
else break;
}
}
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "pile.h"
#define TBLOC 8
6
int prio(char x) {
switch (x) {
7
II – Les files :
La stratégie d’accès à une file est la stratégie F.I.F.O. (First In First Out).
Soit E un ensemble d’éléments. On note FE l’ensemble des files sur E.
II.1.a Actions
créer : réserver de l’espace libre pour une file f.
II.1.b Axiomes :
Soient f une file et x un élément (objet).
vide(ajouter(x,f)) = FAUX
II.1.c Exemple :
valeur(ajouter(3,supprimer(ajouter(4,supprimer(Ajouter(1,ajouter(2,créer)))))))=4
Nous utiliserons toujours une implémentation approchée car la file aura toujours une capacité limitée
(limite d’ajout =N)
la file sera notée f et nous avons besoin d’utiliser deux informations :
la place du dernier élément entré dans la file : indice de queue de file = iq
la place du plus ancien élément résidant dans la file : indice de tête de file = it
remarque : la différence entre iq et it nous donne le nombre d’éléments présents dans la file.
créer (f) : déclarer ou allouer de l’espace pour un tableau f indicé de 1 à N et de même type que
les éléments de E.
it ← 0
iq ← 0
8
vide(f) : si it=iq alors
retourner VRAI
sinon
retourner FAUX
ajouter(x,f) : iq ← iq+1
si (iq > N) alors
afficher(‘implémentation insuffisante’)
sinon f[iq] ← x
supprimer(f) : it ← it+1
si it > iq alors
afficher(‘opération non définie’)
#define N 50
typedef struct { int tab_f[N];// //le tableau sera indicé de 0 à N-1
int it,iq;
} file;
void creerf(file *);
int videf(file );
int valeurf(file );
int ajouterf(int,file *);
int supprimerf(file *);
int aff_file(file );
void remplir_file(file *);
9
int ajouterf(int x,file *f){
(*f).iq++;
if ((*f).iq < N) {
(*f).tab_f[(*f).iq]=x;
return 1;
}
printf("implementation insuffisante\n");
exit(-1);
}
int aff_file(file f){ //cette fonction viole les contraintes d’accès mais peut
//servir à vérifier le contenu de la file sans la modifier
int i;
if (videf(f)) {
printf("file vide\n");
return 0;
}
printf("tete de file = %d\n",valeurf(f));
for (i=[Link]+1;(i<= [Link]);i++)
printf("%d ",f.tab_f[i]);
printf("\n");
}
10
II.4 Files circulaires
it <iq
it >iq
it = iq file vide
///////////////////////////////vide/////////////////////////////////////////////// /////////////////////////////////////////////////////////
0 it N
iq
it = iq file pleine
créer (f) : déclarer ou allouer de l’espace pour un tableau de taille N et de même type que les
éléments de E.
it ← 0
iq ← 0
11
Illustration :
it
iq
Illustration
Implémentation :
Une cellule est une structure auto-référencée qui peut être déclarée sous forme d’un enregistrement
contenant une information (val) et un pointeur (lien) référençant la cellule suivante ou la fin de la liste.
Comme toute structure de donnée complexe, nous avons besoin de construire des outils d’accès et de
modification de la structure en respectant des règles bien définies.
12
Allocation mémoire pour une cellule
cellule *creer_cellule(){
cellule *c;
c=(cellule *) malloc(1*sizeof(cellule));
c->lien=NULL;
return c;
}
Pour accéder aux éléments d’une liste chainée, on doit débuter la lecture à partir de la tête de liste et
ensuite effectuer un parcours jusqu’à la fin de la liste. Pour cela, on est obligé d’utiliser un pointeur
auxiliaire de parcours. En effet, si on modifie la valeur du pointeur de tête de liste, on perd
définitivement l’accès à la liste.
Calcul de la longueur d’une liste chaînée (nombre de cellules existantes dans la liste)
13
Insertion d’une cellule en début de liste
Lors de la construction d’une liste chainée, on doit adopter une stratégie d’ajout de cellules dans la
liste. En effet, l’ordre final des éléments de liste dépend de l’endroit ou on insère les nouvelles
cellules.
Cette fonction retourne l’adresse de la cellule contenant l’information recherchée ou le pointeur NULL
si non trouvé. On suppose que la liste n’est pas triée.
Pour supprimer (déchainer) une cellule, il nous faut d’abord identifier le pointeur de l’élément
précédent qui pointe vers la cellule à supprimer. Pour cela, on effectue d’abord un parcours de
recherche qui nous donne ce pointeur nommé pp dans la fonction : recherche_elt_s().
pp p
liste
14
Lors de la suppression nous devons faire attention à la position de la cellule à supprimer. Cette
opération ne se déroulera pas de la même façon selon que la cellule soit en début ou au milieu de la
liste.
L’insertion en milieu de liste n’a de sens que si celle ci est triée. Comme la suppression, cette
opération nécessite la recherche de l’endroit où on doit effectuer l’insertion. Nous choisissons
arbitrairement l’ordre croissant pour la liste triée.
cellule *pp=liste;
cellule *p=liste->lien;
while ((p!=NULL)&&((*p).val <=x)) {pp=p;p=p->lien;}
return pp;
}
15
Voici un exemple de programme principal permettant de tester ces fonctions.
#include "listes_cours.h"
main(){
int i,x; cellule *liste = (cellule *) NULL; cellule *p;
for (;;){
printf("1:inserer_debut 2:inserer_fin 3: afficher\n");
printf("4:longueur 5: rechercher 6: inserer \n") ;
printf("7:supprimer;\n");
scanf("%d",&i);
switch (i){
case 1: p=creer_cellule();remplir(p);
liste=inserer_debut(liste,p);break;
case 2: p=creer_cellule();
remplir(p);
liste=inserer_fin(liste,p);break;
case 3: afficher_liste(liste);
afficher_liste_p(liste);break;
case 4: printf("longueur= %d\n",longueur(liste));break;
case 5: printf("donner l'info a rechercher:");
scanf("%d",&x);
printf("%d %p\n",x,recherche_elt(x,liste));break;
case 6: p=creer_cellule();
remplir(p);
liste=inserer(liste,p);break;
case 7: printf("donner l'info à supprimer:");
scanf("%d",&x);
liste=supprimer(liste,x);break;
default: exit(0);
}
}
}
16
Chapitre 3 : Les Arbres
I – Préarbre (arborescence)
Un préarbre est un ensemble dynamique fini S qu’on définit sous la forme d’une application de S dans
l’ensemble des parties de S :
s S, il existe une suite unique s1, s2, ... , sp / s1 = r et sp =s ; et si+1 (si ) pour i=1,2, ... , p-1
Exemple :
S={ s0, s1, s2, s3, s4, s5, s6, s7, s8} ; r = s0
| s0 s1 s2 s3 s4 s5 s6 s7 s8
______|______________________________________________________
| s1 s4 s3 s6 s8
| s2 s5
| s7
Pour montrer que (S,r,) est un préarbre, il faut montrer qu’il existe une suite unique reliant r et tous
les éléments de S.
On dénombre les suites qui vérifient si+1 (si ) pour i=1,2, ... , p-1
- s0 s1 s4
- s0 s1 s5 s8
- s0 s1 s7
- s0 s2 s3 s6
I.1 Représentation :
s1 s2
s4 s5 s7 s3
s8 s6
17
b) Ensembles imbriqués:
s5 s8
s6
s4 s7
s3
s1
s2
s0
c) Parenthèses imbriquées :
(s0(s1(s4) (s5(s8))(s7))(s2(s3(s6))))
d) Identation :
s0
s1 s2
s4 s5 s7 s3
s8 s6
I.2 Définitions :
On appelle :
Chemin : une suite s1, s2, ... , sp telle que si+1 (si ).
Nœud : s S tel que (s) .
Feuille : s S tel que (s)= .
Sommet : un nœud ou une feuille.
Branche : le chemin s1=r, s2, ... , sp et (sp)=.
18
I.3 Propriétés :
b) s S ; r (s)
Démonstration :
Soit n le nombre de sommets de S et il existe un chemin de longueur n+1 :
I.4 Restriction :
(t) = (t)
Exemple 1 Exemple 2
T1={ s0, s2, s5, s8} T2={ s0, s1, s4, s5}
(s0) = { s2} (s0) = { s1}
(s5) = { s8} (s1) = { s4, s5}
(s2) = (s4) =
(s8) = (s5) =
I.5 Propriétés :
par récurrence :
soit un élément s/ s { s1,s2 ... , si}, on dit que sk (s) est fils de s.
II.1 Définition :
s0
s1 s2
s4 s5 s7 s3
s8 s6
(s0)= s1 s2
(s0)= s2 s1
(s1)= s4 s5 s7
s4 s7 s5
s5 s4 s7
s5 s7 s4
s7 s4 s5
s7 s5 s4
Ce qui nous donne douze arbres possibles.
20
II.2 Ensembles de mots associés à un arbre :
soit s, s r, il existe une chemin r=s1, s2, ... , s=sp / w(s)=w(sp-1).ai si s intervient au ième rang dans la
suite (sp-1).
(s0)= s2 s1
(s1)= s5 s7 s4
s0
s2 s1
s3 s5 s7 s4
s6 s8
w(s0)={}
w(s1)= w(s0).ai=a2
w(s2)= w(s0).ai=a1
w(s3)= w(s2).ai= a1 a1
w(s4)= w(s1).ai= a2 a3
w(s5)= w(s1).ai= a2 a1
w(s6)= w(s3).ai=a1 a1 a1
w(s7)= w(s1).ai= a2 a2
w(s8)= w(s7).ai= a2 a2 a1
Propriété :
Réciproque :
Si vérifie (1) et (2) alors il existe un arbre dont l’ensemble des mots associés est
W.
exemple : {a1 a4 a2, a1a3 a1, a1 a2, a1 a3, a1 a4 a1 ,a2 ,a1, a1 a1, a1 a4 }
21
a1 a2
Remarque : Si on ne considère que l’ensemble des mots associés aux feuilles d’un arbre qui vérifie (1)
et (2) alors cet ensemble est un code.
a2 a1
a2a2a1 a1a1a1
le premier tableau contiendra les étiquettes (informations ou valeurs) associées aux sommets de
l’arbre.
le deuxième tableau contiendra les indices des cases où se trouve le premier fils (fils ainé) pour chaque
sommet et -1 si pas de fils.
le troisième tableau contiendra les indices des cases où se trouve le fils suivant du père (frère) pour
chaque sommet et -1 si pas de frère.
Exemple:
1 2 3 4 5 6 7 8 9 10 11 12 13
Val S0 S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12
Fils 2 3 -1 6 8 -1 10 -1 12 -1 -1 -1 -1
frere -1 4 5 -1 -1 7 -1 9 -1 11 -1 13 -1
pere -1 1 2 1 2 4 4 5 5 7 7 9 9
Remarque: le tableau père n’est pas nécessaire à l’implémentation et peut être calculé à partir des autres tableaux
comme nous le verrons plus loin.
22
s
s1 s3
s2 s4 s5 s6
s7 s8 s9 s10
s11 s12
#include <stdio.h>
#include <stdlib.h>
#define N 20 /* N=Nombre maximal de sommets possibles */
main(){
int racine;
valeur val={"","s0","s1","s2","s3","s4","s5","s6","s7","s8","s9",
"s10","s11","s12"};
sommet fils={-1,2,3,-1,6,8,-1,10,-1,12,-1,-1,-1,-1};
sommet frere={-1,-1,4,5,-1,-1,7,-1,9,-1,11,-1,13,-1};
sommet pere={-1,-1,1,2,1,2,4,4,5,5,7,7,9,9};
//Appels aux fonctions utilisant cet arbre
}
23
c)trouver l'indice du père de t :
On suppose qu’on ne dispose pas du tableau père, que t n’est pas forcement le premier fils et que
l’arbre n’est pas binaire (i.e. cas général)
int i,t;
for (i=1;i<N;i++)
if (!strcmp(s,val[i])) {t=i; break;}
int pere=-1;
if (t == 1) { printf("pas de pere\n");getchar();exit(0);}
while (pere == -1){
for (i=1;i<N;i++)
if (t==fils[i]) pere=i;
for (i=1;i<N;i++)
if (t==frere[i]) t=i;
}
return val[pere];
}
Un parcours est une suite ordonnée de l’ensemble des sommets qui contient chaque sommet une fois et
une seule. Il existe trois façons de parcourir un arbre :
Parcours en préordre :
visiter la racine
parcourir les sous arbres de gauche à droite
Parcours en postordre :
parcourir les sous arbres de gauche à droite
visiter la racine
Exemple :
s
s1 s3
s2 s4 s5 s6
s7 s8 s9 s10
s11 s12
24
préordre(s0) = s0, préordre(s1), préordre(s3)
= s0, s1, préordre(s2), préordre(s4), s3, préordre(s5), préordre(s6)
= s0, s1, s2, s4, préordre(s7), préordre(s8), s3, s5, s6, préordre(s9), préordre(s10)
= s0, s1, s2, s4, s7, s8, préordre(s11), préordre(s12), s3, s5, s6, s9, s10
= s0, s1, s2, s4, s7, s8, s11, s12, s3, s5, s6, s9, s10
l’arbre associé à la forme: expression1 opérateur expression2 peut être représenté par:
opérateur
expression1 expression2
25
exemple: (a+b)*(a-b)-(a**2+b)/(c+a)
* /
+ - + +
a b a b ** b c a
a 2
nom_user1 nom_user2
26
fonction
corps
déclarations instructions
i = = < =
nbf 0 i 1 i N i +
i 1
if
condition corps
== = printf
nbf 1
27
VI- Arbres Binaires:
Chaque sommet a :
ou bien deux fils
ou bien pas de fils
ou bien un fils gauche
ou bien un fils droit
Pour tout sommet, il existe une façon et une seule de l’obtenir par fg et fd à partir de la racine.
Exemple :
s
g s1 s2 d
dg s3
s4 dgd
dgdg s5 s6 dgdd
s7 dgdgd
Cet arbre peut être représenté (codé) par l’ensemble de mots suivant :
M={, g, d, dg, dgd, dgdg, dgdd, dgdgd}
On peut représenter un arbre binaire par deux tableaux fg et fd contenant les indices des fils gauche et
droit de chaque sommet respectivement ainsi qu’un tableau valeur contenant l’information associées à
chaque sommet.
28
1
2 3
4 5 6
7 12
8 9 13 14
10 11 15
s 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
fg 2 4 5 0 0 0 8 0 10 0 0 13 0 15 0
fd 3 0 6 7 12 0 9 0 11 0 0 14 0 0 0
val
#define N 100
typedef struct sommet{int val; int fg; int fd;}sommet;
typedef sommet tab[N];
typedef struct arbre {int racine; tab table;} arbre;
On utilise une structure auto-référencée avec deux pointeurs pour désigner les « suivants » fils gauche
et/ou droit.
arbre
b c
d
k
l
e
m
f g
29
typedef struct sommet{ int val ;
struct sommet *fg;
struct sommet *fd;
}sommet;
sommet *arbre ;
taille(arbre_vide)=0 ;
taille(<r,B1,B2>)=1+taille(B1)+taille(B2)
avec :
<r,B1,B2>
r
B1 B2
On a la relation : LC(B)=LCE(B)+LCI(B).
30
VII – 5 Profondeur moyenne externe :
Exemple :
n1 h(B)=4
n2 n4 LC(B)=22
n3 n5 n7 LCI(B)=9
n6 n9 LCE(B)=13
n8 n10 PE(B)=13/4=3.25
taille(B)=h(B)+1
n1
n2
n3
n6
n8
Il contient un nœud au niveau 0, 2 nœuds au niveau 1, 4 nœuds au niveau 2 ..... 2h nœuds au niveau h.
(chaque niveau est complètement rempli)
31
s
s1 s3
s2 s4 s5 s6
C’est un arbre binaire dont tous les niveaux sont complètement remplis sauf éventuellement le dernier,
et dans ce cas les feuilles sont groupées le plus à gauche possible.
Parfait
Non Parfait
Non Parfait
32
VIII – 4 Arbre binaire localement complet :
C’est un arbre binaire non vide dans lequel tous les nœuds qui ne sont pas des feuilles ont deux fils.
C’est un arbre binaire localement complet dans lequel tous fils droit (respectivement gauche) est une
feuille.
Peigne gauche
I - Définition :
Un ABR est un arbre binaire dans lequel tout sommet t satisfait les conditions suivantes :
33
II – Implémentation :
#define N 20
typedef struct sommet { int val;
struct sommet *fg;
struct sommet *fd;} sommet;
Un ABR croît par les feuilles : On doit effectuer un parcours en comparant les valeurs des sommets de
l’arbre à celle du sommet à insérer jusqu’à atteindre une feuille.
34
5) parcours récursif en préordre
35
9)Retirer un sommet de l’arbre
Pour supprimer un sommet, nous devons d’abord chercher son pointeur ainsi que celui de son père.
Nous devons aussi traiter différemment les cas où ce sommet possède un fils gauche, un fils droit, ou
deux fils.
sommet *supprimer(sommet *r,int a){
sommet *u,*v,*x,*y;
rechercher(r,&u,&v,a);
if (u==NULL) {printf("non trouve\n"); return r;}
if ((u->fg==NULL) || (u->fd==NULL)) oter1(&r,&u,&v);
else {
oter2(u,&x,&y);
(*u).val=(*x).val;
oter1(&r,&x,&y);
}
return r;
}
Recherche du pointeur du sommet à supprimer ainsi que celui de son père
int rechercher(sommet *r,sommet **u,sommet **v,int a){
*u=r; *v=NULL;
while ((*u!=NULL) && ((**u).val!=a))
if ((**u).val > a) {*v=*u ; *u=(*u)->fg;}
else {*v=*u ; *u=(*u)->fd;}
}
36
Chapitre 5:Arbres binaires complets
I – Définitions et propriétés
I-1 Définition :
: S → S X S U {}
(s) = ==> s est une feuille.
(s) = (s1, s2) ==> s est un nœud, s1 est fils gauche et s2 est fils droit.
vérifie les conditions sur les arbres.
I-2 Exemple :
s
s1 s3
s2 s4 s5 s6
s7 s8 s9 s10
s11 s12
I-3 Propriété :
Si N est la nombre de sommets d’un arbre binaire complet, alors le nombre de ses feuilles est (N+1)/2.
37
Preuve :
On peut associer à un arbre binaire des mots d’un alphabet à deux lettres {g, d}.
a1 a2
a1a2a2a1 a1a2a2a2
Propriété : L’ensemble des mots associés aux feuilles d’un arbre binaire complet constitue un code C
tel que : C U m n’est pas un code m C.
Codage de Huffman :
Pour chaque symbole, créer un arbre avec un nœud et ordonner ces arbres suivants la
fréquence d’apparition.
Tant qu’il reste plus d’un arbre faire :
Prendre les deux arbres a1 et a2 ayant les plus petites fréquences f1 et f2 / f1 <= f2 et
créer un arbre avec a1 et a2 comme fils gauche et droit.
La fréquence associée au nouvel arbre sera f1 + f2.
Exemple :
Z K F C U D L E
2 7 24 32 37 42 42 120
38
étape 1 :
Z K F C U D L E
2 7 24 32 37 42 42 120
étape 2 :
9 F C U D L E
24 32 37 42 42 120
Z K
2 7
étape 3 :
C 33 U D L E
32 37 42 42 120
9 F
24
Z K
2 7
étape 4 :
U D L 65 E
37 42 42 120
C
33
32
F
9 24
Z K
2 7
39
étape 5 :
L 65 E
42 79
120
C
33 U D
32
37 42
F
24
9
K
Z
7
2
étape 6 :
E
79 107 120
U D L
65
37 42 42
C
33
32
F
24
9
K
Z
7
2
40
étape 7 : E 186
120
79 107
U D L
37 42 65
42
C
33
32
9 F
24
Z K
2 7
étape 8 :
306
0 1
E 186
120
0 1
107
79
0 1 0 1
U D L 65
37 42 42
0 1
C 33
32
0 1
9 F
24
0 1
Z K
2 7
41
b) Le code de Huffman :
II – Propriétés fondamentales :
Lemme 1 :
Preuve :
42
Lemme 2 :
Preuve :
1 h=0
2 3 h=1
4 5 6 7 h=2
8 9 10 11 12 h=3
- B a un sommet : LB(B)=0=
- supposons la propriété vraie pour les nœuds à la hauteur k : (numérotés de 2k à 2k+1-1)
- considérons le niveau k+1 :
rappel : pour
n=0 b0=1
43
n=1 o b1=1
o o
n=2 o o b2=2
o o o o o
n=3 o o o o o o b3=5
o o o o
o o
o o o o
o o o o
o o o o
o o
o o o o
o o o o o o
o o
o o
o o
o o
44
Théorème : L’image par de l’ordre symétrique sur un arbre binaire représentant une permutation
détermine cet arbre de manière unique.
Etapes :
3) Utiliser l’algorithme.
o3 o3 o3 o3 o3
o2 o2 o1 o2 o2 o2
o1 o1 o1 o1
123 213 132 312 321
o3
o2 o1
231
45
Chapitre 6 : Les Graphes
I- Représentations :
Exemple :
Adj
1 2 5
1 2
2 1 5 3 4
3
3 2 4
5 4 4 2 5 3
5 4 1 2
G est un graphe constitué d’un ensemble S de sommets et un ensemble A d’arêtes et noté G=(S,A).
Sur l’exemple le nombre de sommets de S est noté |S|=5, et le nombre d’arcs de A est noté |A|=7.
Une liste de successeurs est un ensemble de |S| listes chaînées. C’est une représentation efficace pour
les graphes peu denses :
Pour chaque sommet , la liste d’adjacences est une liste de sommets v tels qu’il existe un
arc .
Les sommets de chaque liste d’adjacences sont en général chaînés selon un ordre arbitraire.
Graphe orienté
Exemple :
Adj
1 2 3 1 2 4
2 5
3 6 5
4 5 6
4 2
5 4
6 6
Si G est un graphe orienté, la somme des longueurs de toutes les listes d’adjacences vaut |A|.
Si G est non orienté, cette somme vaut 2|A|.
46
L’avantage principal de la représentation par listes d’adjacences est que l’espace mémoire nécessaire
pour stocker le graphe ne dépend que du nombre de sommets et du nombre d’arcs. Par contre, toute
recherche dans le graphe nécessite un parcours des listes d’adjacences.
les listes d’adjacences peuvent être aisément adaptées aux graphes pondérés. Le poids d’un arc (u,v)
peut être stocké dans la cellule contenant le sommet v dans la liste d’adjacences de u.
1 2 M
1 2 3 4 5
3
1 0 1 0 0 1
2 1 0 1 1 1
5 4 3 0 1 0 1 0
4 0 1 1 0 1
5 1 1 0 1 0
L’espace mémoire nécessaire pour stocker le graphe est O(|S|2) quelque soit le nombre d’arcs.
1 2 3 1 2 3 4 5 6
1 0 1 0 1 0 0
2 0 0 0 0 1 0
3 0 0 0 0 1 1
4 5
4 0 1 0 0 0 0
6
5 0 0 0 1 0 0
6 0 0 0 0 0 1
Transposée :
1 2 3 4 5 6
1 2 3 1 0 0 0 0 0 0
2 1 0 0 1 0 0
3 0 0 0 0 0 0
4 1 0 0 0 1 0
4 5 6 5 0 1 1 0 0 0
6 0 0 0 0 0 1
La transposée d’un matrice d’adjacences d’un graphe non orienté est égale à elle même. On ne
conserve dans ce cas que la diagonale et la partie supérieure de la matrice. Ce qui réduit de presque la
moitié la quantité de mémoire requise pour stocker le graphe.
47
Graphes pondérés :
Exemple :
1 2 3 4
1 0 1 1 0
2 0 1 1 1
3 0 1 0 2
4 1 0 0 0
Propriété :
Soit M2 la matrice obtenue en multipliant M par M : M2ij représente le nombre de chemins de longueur
2 d’origine xi et d’extrémité xj.
En effet :
xi xj
Mik Mkj
xk
Généralisation :
Propriété :
S’il existe un chemin entre xi et xj , alors il y en a un de longueur inférieur à |S|-1.
a=1
N=M
tant que (( < n-1) et Nij=0) faire
N←M*N
←+1
si (Nij = 0) alors
afficher("pas de chemin")
sinon
afficher("Il existe un chemin de longueur:",,"entre",i, "et",j)
48
Remarque :
II- Définitions :
Soit un graphe G=(S,A). On définit deux applications : org : A → S et ext : A → S.
Chemin :
c’est une suite a1, a2, ..., ak d’arcs telle que : ext(ai)=org(ai+1) pour i=1, ..., k-1.
Chemin propre:
ai=aj ==> i=j
chemin qui ne repasse pas deux fois par le même arc.
Chemin élémentaire:
Si ext(ai)=ext(aj) alors i=j
Il n’existe pas deux arcs ayant la même extrémité.
Circuit :
c’est un chemin propre tel que ext(ak)=org(a1).
Exemple :
a1 a2 a3 a4 a5 a6 a7 a8 a9
org x1 x2 x2 x1 x3 x2 x3 x4 x3
ext x2 x3 x2 x3 x2 x4 x4 x1 x4
a3
x2
a1 a6 a1 a6 a8 a1 a2 a9 = chemin.
a5 a2 a1 a6 a8 a4 a5 = chemin propre non
x4 élémentaire.
x1 a4 a9 a6 a8 a4 = chemin élémentaire.
x3 a7 a8
49
Graphes symétriques :
Un graphe est symétrique si pour tout arc a, il existe un arc a’ tel que :
ext(a)=org(a’) et ext(a’)=org(a)
Notations
Exemple :
x1 x3
X={ x1, x2 , x3 , x4}
E={ x1, x3},{ x2, x4},{ x2, x3}
x2
x4
Représentation :
x2
x2
===> x1
x1
x3
x3
x1 a9 x2
a6
a7 a8 x3 a5
a4
a3
x5 x4
a1 a2
x6
Soit , on définit a-i comme l’opposé de ai et les arcs seront définis ainsi :
a1 ... am et a-1 ... a-m
50
On définit l’ensemble des arcs ayant xi pour origine :
Illustration :
-9 -8 -7 -6 -5 -4 -3 -2 -1 1 2 3 4 5 6 7 8 9
SIGMA 6 -3 1 -5 -4 5 -2 4 2 3 -1 -7 -8 -6 8 9 -9 7
ORIG x2 x4 x5 x3 x3 x3 x4 x4 x6 x5 x6 x5 x4 x3 x2 x1 x2 x1
1 2 3 4 5 6
PREM 7 -9 -6 -3 -7 -1
51
Chapitre 7 : Cheminement dans un Graphe
I- Parcours en largeur :
Données : un graphe G(S,A) orienté simple et un sommet x début de parcours.
Résultats : la liste de tous les sommets y, extrémités d’un chemin d’origine x.
Le parcours en largeur emprunte systématiquement les arcs de G pour « découvrir » tous les sommets
accessibles depuis x. Il peut aussi calculer la distance (plus petit nombre d’arcs) entre x et tous les
sommets accessibles.
Il construit également une arborescence de parcours en largeur de racine x. Cette arborescence
correspond aux plus courts chemins de x à tous les sommets y qu’on peut atteindre dans G. La
méthode fonctionne aussi bien sur les graphes orientés que sur les graphes non orientés.
L’utilisation d’un algorithme empirique pour réaliser un parcours peut conduire à des problèmes
comme la non terminaison (boucle infinie) ou l’oubli d’un sommet.
Pour éviter la non terminaison, on utilise un tableau de booléens Marque[] permettant de marquer les
sommets visités.
Pour enregistrer les distances, on utilise un tableau d’entier d[].
Pour enregistrer l’arbre recouvrant, on utilise un tableau P[] représentant le père de chaque sommet
dans l’arbre.
ParcoursLargeur(G,x)
//initialisation
creerf(f) // une file de sommets
pour i← 1 à N faire
Marque[i] ← 0
d[i] ←
P[i] ← NULL
d[x] ← 0
P[x] ← NULL
Marque[x] ← 1
ajouterf(x,f)
//phase centrale
tant que non vide(f) faire
y ← valeurf(f)
supprimerf(f)
pour tout successeur z de y faire
si (marque[z]=0) alors
Marque[z] ← 1
d[z] ← d[y]+1
P[z] ← y
ajouterf(z,f)
//phase de sortie des résultats
pour i← 1 à N faire
si (Marque[i]=1) alors
afficher(xi)
52
Exemple :
Adj
1 2 5
1 2
2 1 5 3 4
3
3 2 4
5 4 4 2 5 3
5 4 1 2
arbre recouvrant :
1 2
5 4
L’arbre obtenu par cet algorithme dépend de l’ordre des successeurs dans les listes. Reprenons le
même graphe implémenté avec les mêmes listes mais avec un ordre différent pour les successeurs.
Adj
1 2 1 5 2
2 3 4 5 1
3
3 4 2
5 4
4 5 3 2
5 4 2 1
1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
Marque 1 1 1 1 1 d 0 1 2 2 1 p NULL 1 2 5 1
arbre recouvrant :
1 2
5 4
Exercice : Construire les arbres recouvrant du parcours en largeur en utilisant un autre sommet
comme départ du parcours
53
II- Parcours en profondeur :
ParcoursProfondeur(G,x)
pour i← 1 à N faire
Marque[i] ← 0
P[i] ← NULL
Marque[x] ← 1
Tremaux(x)
Tremaux(x)
pour tout successeur z de y faire
si (marque[z]=0) alors
Marque[z] ← 1
P[z] ← y
Tremaux(z)
Exemple :
Adj
1 2 5
1 2 2 1 5 4 3
3 3 2 4
4 2 3 5
5 4
5 1 2 4
1 2 3 4 5 1 2 3 4 5
Marque 1 1 1 1 1 p NULL 1 4 5 2
arbre recouvrant :
1 2
5 4
L’arbre obtenu par cet algorithme dépend de l’ordre des successeurs dans les listes. Reprenons le
même graphe implémenté avec les mêmes listes mais avec un ordre différent pour les successeurs.
54
Adj
1 5 2
1 2
2 3 4 5 1
3
3 4 2
5 4 4 5 3 2
5 4 2 1
1 2 3 4 5 1 2 3 4 5
Marque 1 1 1 1 1 p NULL 3 4 5 1
arbre recouvrant :
1 2
5 4
Exercice : Construire les arbres recouvrant du parcours en profondeur en utilisant un autre sommet
comme départ du parcours
1 6
3 5 6
4 5 4 5
5 6
1 2 3 4 5 6 1 2 3 4 5 6
Marque 1 1 1 1 1 1 p NULL 1 2 2 3 5
55
arbre recouvrant :
2 3
1 6
4 5
Définition :
Un graphe est dit transitif si RG est transitive. i.e. pour tout chemin f, il existe un arc ayant la même
origine et la même extrémité que f.
Exemples :
1) graphe transitif :
2
3
1
4 5
2 3
1 6
4 5
56
Fermeture transitive :
(i) C’est le plus petit graphe (S,A’) tel que et (S,A’) est transitif.
(ii) C’est le graphe obtenu à partir de (S,A) en ajoutant des arcs entre les sommets reliés par un chemin.
2 3
1 6
4 5
Algorithme :
Pour tout sommet x faire
Tremaux(x)
ajouter un arc entre x et tous les sommets marqués
Exemple :
Propriété : 4 5
2) Algorithme :
Choisir quelconque.
Tremaux( dans G.
S’il existe un sommet non marqué alors
G est non fortement connexe et arrêt.
Construire G’ à partir de G en inversant le sens des arcs.
Tremaux( dans G’.
Si tous les sommets sont marqués alors
G est fortement connexe
sinon
G est non fortement connexe.
57
Exemple :
2 3 2 3
G fortement
connexe 6 6
4 5 4 5
G non fortement
connexe
2 3 2 3
4 5 4 5
1 1
6 6
x est un sommet d’articulation dans un graphe symétrique s’il existe y et z deux sommets tels que tous
les chemins reliant y et z passent par x.
2 3 2 3
x x
4 5 4 5
1 1
6 6
58
#include "entete_graphe.h"
#include "file.h"
#include "pile.h"
#include "listes.h"
#define N 6
#define M 6
z=z->lien;
}
}
}
59
void profondeur_iteratif(cellule **g,int s, int n,int pere[],int marque[]){
int i,art=0;
for (i=0;i<=n;i++) {marque[i]=0;pere[i]=-1;}
marque[s]=1;
pile p; creerp(&p);
ajouterp(-1,&p);
cellule *z;
int y=s;
z=g[y];
printf("parcours en profondeur iteratif:\n");
while (!videp(p)){
if (z!=NULL){
if (marque[(*z).val] == 0){
printf("%d --> %d\n",y,(*z).val);
if ((valeurp(p)==-1) && (art==1))
printf("%d = point d'articulation\n",s);
ajouterp(y,&p);art=1;
marque[(*z).val]=1;
pere[(*z).val]=y ;
y=(*z).val;
z=g[y];
}
else z=z->lien;
}
else{
y=valeurp(p);
supprimerp(&p);
z=g[y];
}
}
}
main(){
int i,j;
int m_adj[N+1][M+1]={{0,0,0,0,0,0,0},{0,0,1,0,1,0,0}, {0,0,0,0,0,1,0},
{0,0,0,0,0,1,1},{0,0,1,0,0,0,0},{0,0,0,0,1,0,0},{0,0,0,0,0,0,1}};
cellule **g=(cellule **)malloc((N+1)*sizeof(cellule *));
for (i=1;i<=N;i++) g[i]=(cellule *)NULL;
for (i=1;i<=N;i++)
for (j=1; j<=M;j++)
if (m_adj[i][j]!=0)
g[i]=inserer_fin(g[i],creer(j));
for (i=1;i<=N;i++){
printf("sommet %d -->",i);
aff_liste(g[i]);}
largeur(g, 3,N);
60