Title: Les adresses et les pointeurs
1Les adresses et les pointeurs
2Les adresses
Les cases mémoires ont toutes un numéro qui les
distingue les une des autres ce numéro est
appelé adresse. Cest par cette adresse que le
processeur peut communiquer avec la mémoire.
0
1
2
3
4
. . .
. . .
max
3Les adresses et les variables
Le nom que lon donne au cases mémoires est
traduit en une adresse juste avant lexécution
dun programme. Cela est nécessaire afin que le
processeur sache à quelle case mémoire est
associée chaque variable. En général il est
impossible de prévoir à quelle adresse sera
placée une variable. Le nom des variables est
donc nécessaire.
0
c1 1
char
2
c1 c2 Lire le contenu de la case 3. Mettre ce
qui a été lu dans la case 1.
c2 3
char
1324
4
. . .
. . .
max
4Le partage de la mémoire
Sur les systèmes modernes, il peut y avoir
plusieurs usagers se partageant la mémoire et
chaque usager peut exécuter plusieurs programmes
simultanément. Cela signifie que lon nest pas
libre dutiliser toutes les cases mémoires comme
on le veut. Une case peut être occupée par un
programme à un certain moment et libre à un
autre. Cette situation est aléatoire. Pour cette
raison, on ne mentionne jamais explicitement une
adresse dans un programme même si cela est
théoriquement possible.
5Adresses valides et non valides
Exemple. Dans le pseudo-code suivant Lire le
contenu de la case 3. Mettre ce qui a été lu
dans la case 1. Que se passe t-il si au moment
de lexécution la case mémoire 1 est déja
utilisée par un autre programme. La case est
alors non valide et il y aura erreur à
lexécution. Cest pour cette raison que lon
utilise des variables. Avant lexécution, une
adresse valide est associée à chaque variable.
Seul notre programme pourra utiliser ces cases
mémoire.
6Position des variables dans la mémoire
a
int
. . .
. . .
Sauf pour les tableaux, il ny a aucune garantie
que les variables occupent des cases adjacentes
en mémoire. Exemple. int a,b4,c,d3
int
b0
int
b1
int
b2
int
b3
int
c
. . .
. . .
d0
int
int
d1
int
d2
. . .
. . .
7Les adresses et les types
Une des principales fonctions des types est
dindiquer le nombre doctets utilisés par une
variable. Par exemple nous avons vu que un
caractère prend 1 octet (8 bits) un entier prend
4 octets (32 bits). Cela signifie que si on
divise la mémoire en case dun octet alors un
char utilise 1 case un int utilise 4 cases
adjacentes
n 0
00000000
int
1
00000000
2
00000000
3
00001101
c1 4
char
00011011
. . .
c2 5
00011100
char
. . .
. . .
max
8Remarque
On peut aussi voir la mémoire comme une suite de
cases de taille variable.
n 0
int
00000000000000000000000000001101
int
c1 4
char
00011011
. . .
c2 5
00011100
char
. . .
. . .
max
9Les adresses et les tableaux
Le nom dun tableau correspond à ladresse du
début du tableau. Exemple char
tab5 printf(p\n, tab) 4027630992
printf(p\n, tab1) 4027630993 printf(
p\n, tab2) 4027630994 Note p sert
à afficher les pointeurs.
10Les tableaux dentiers
Exemple int tab5 printf(p\n, tab)
4027630976 printf(p\n, tab1)
4027630980 printf(p\n, tab2)
4027630984 Question Pourquoi?
4
4
11Lincrémentation dune adresse
a 16216
Ladresse a1 nest pas valide
int
. . .
. . .
int
b0 b24600
b1 b124604
int
int
b2 b224608
Incrémenter une adresse ne veux pas dire ajouter
1, cela veut dire aller à ladresse suivant la
variable courante. En général cela na du sens
que si on est dans un tableau.
int
b3 b324612
. . .
. . .
d0 d54316
char
char
d1 d154317
char
d2 d254318
. . .
. . .
12Remarque
Si Tab est un tableau alors Ladresse de Tab0
est Tab, ladresse de Tab1 est Tab
1, ladresse de Tab2 est Tab 2, etc. Cela
est vrai quelque soit le type des éléments de Tab.
13Lopérateur
Il est possible de connaître, pendant lexécution
dun programme, ladresse associée à une
variable. En C, cela est possible à laide de
lopérateur unaire
Exemple char c int n, tab1000 Ladresse
de c est c Ladresse de n est n Ladresse de
tab3 est tab3
14Lopérateur
Il est aussi possible de connaître, pendant
lexécution dun programme, le contenu de la case
mémoire située à une adresse donnée. En C, cela
est possible à laide de lopérateur unaire
- Exemple
- char c
- int tab1000
- Le contenu de ladresse tab 25 est (tab 25)
- (tab 25) est donc identique à tab25
- (c) est identique à c
- c na aucun sens
15Exemple
- Les expressions logiques suivantes sont vraies
- n 12556
- (12560) 60
- (12560) lt c2
- (r) 12.345
. . .
. . .
int
5000000
n 12556
c1 12560
char
60
char
c2 12561
61
double
r 12562
12.345
. . .
. . .
16Résumé des opérations sur les adresses
- On peut
- Additionner une adresse et un entier
- Déterminer ladresse dune variable
- Déterminer le contenu dune adresse
- Mais, on ne peut pas
- Additionner deux adresses
- (mais on peut soustraire deux adresses dun
même tableau)
17Les pointeurs
Un pointeur est une variable pouvant contenir une
adresse.
Exemple int pn pointeur sur une valeur
entière char pc pointeur sur un
caractère double pr pointeur sur un réel
18Les pointeurs exemple
pn 12556
int
Exemple int pn, m
. . .
. . .
m 65710
int
. . .
. . .
19Les pointeurs exemple
65710
pn 12556
int
Exemple int pn, m pn m
. . .
. . .
m 65710
int
. . .
. . .
20Les pointeurs exemple
65710
pn 12556
int
Exemple int pn, m pn m m 6
. . .
. . .
6
m 65710
int
. . .
. . .
21Les pointeurs exemple
65710
pn 12556
int
Exemple int pn, m pn m m 6 pn
38
. . .
. . .
38
m 65710
int
. . .
. . .
22Les pointeurs et les tableaux
En C les pointeurs sont intimement liés aux
tableaux. Exemple int tab10,
p ptab tab3 70 (tab 3)
70 p3 70 (p 3) 70
tous équivalents
23Remarque
Le nom dun tableau est une adresse constante et
non pas un pointeur qui est une
variable. Exemple int tab10, p ptab p
tab / Valide / tab p / Non valide /
0
1
2
3
4
5
6
7
8
9
10
tab
p
24Quelques utilités des pointeurs
- Pour implanter le passage de paramètres par
référence - Pour implanter le passage de tableaux en
paramètre - Pour utiliser des indices négatifs au tableaux
- Fondamental en structure de données
25Les pointeurs comme paramètres
En C echanger(a, b)
void echanger( int x, int y) int tmp tmp
x x y y tmp
26Les pointeurs comme paramètres
En C echanger(a, b)
void echanger( int x, int y) int tmp tmp
x x y y tmp
27Les tableaux passés en paramètre
Une des plus importantes utilisation des
pointeurs réside dans le passage des tableaux en
paramètre. Lorsque lon passe le nom dun
tableau en paramètre, on passe une adresse. La
fonction appelée reçoit donc une adresse quelle
met dans une variable cette variable doit donc
être un pointeur.
28Les tableaux passés en paramètre
void liretab(int tab , int max) int c
int i0 while ((cgetchar()) ! EOF)
tabi c i i 1
29Les tableaux passés en paramètre
void liretab(int tab, int max) int c int
i0 while ((cgetchar()) ! EOF)
(tabi) c i i 1
30Les indices de tableaux
Exemple 1 Tableau dentiers dont les indices
vont de -5 à 5 int tab11 int ptab ptab
tab 5 ptab0 est identique à tab5 ptab-5
est identique à tab0 ptab5 est identique à
tab10
0
1
2
3
4
5
6
7
8
9
10
tab
ptab
31Les indices de tableaux
Exemple 2 Tableau dentiers dont les indices
vont de A à Z int tab26 int
ptab ptab tab - A / A vaut 65 en ASCII
/ ptabA est identique à tab0 ptabZ
est identique à tab25
0
1
2
3
4
21
22
23
24
25
-65
tab
?
ptab
32Allocation statique et dynamique
Jusquà maintenant, toutes la mémoire que nous
avons utilisée dans nos programmes devait avoir
été allouée avant l'exécution à laide des
déclarations de variables. Il est parfois utile
dallouer une partie de lespace mémoire en cours
dexécution.
33Exemple 1
Par exemple, si on a besoin de mémoriser un
certain nombre dobjets mais que ce nombre nest
pas connu avant lexécution du programme. Il
faut alors allouer suffisamment despace au cas
où le nombre dobjets est grand. Si le nombre
dobjets est petit, on gaspille inutilement de
lespace mémoire.
34Le fichier dentête stdlib.h
Le fichier dentête stdlib.h contient des
déclarations de fonctions traitant, entre autres,
de lallocation de la mémoire - malloc -
free - calloc - realloc
35(void )malloc(size_t size)
size_t est un type dentiers positifs. malloc
retourne un pointeur sur un espace mémoire
réservé à un objet de taille size, ou bien NULL
si cette demande ne peut être satisfaite. La
mémoire allouée nest pas initialisée. Bien
entendu, quand on travaille avec des pointeurs en
C, ces derniers possédent chacun un type. Par
conséquent, le type de données (void ) retourné
par malloc doit toujours être mis dans le type de
donnée avec lequel nous voulons travailler..
36Exemple 2
int p p 10 / INVALIDE puisque p ne pointe
sur / / aucune case mémoire valide
/ p (int) malloc(sizeof(int)) p
10 / VALIDE /
p
Avant malloc
p
Après malloc
37Exemple 2
int p p 10 / INVALIDE puisque p ne pointe
sur / / aucune case mémoire valide
/ p (int) malloc(sizeof(int)) p
10 / VALIDE /
Pourquoi?
p
Avant malloc
p
Après malloc
38Pointeurs sur void
La fonction malloc ne sait pas à quoi servira
lespace mémoire qui lui est demandé. Elle ne
sait pas quel type dobjet utilisera cet
espace. Alors, elle retourne un pointeur
générique qui peut être converti en nimporte
quel type de pointeur un pointeur sur void
39Conversion implicite
En C, certaines conversions de type sont
implicites double x int n char c n
c / un char est converti en un int / c n
/ un int est converti en un char / x n
/ un int est converti en un double / n x
/ un double est converti en un int /
40Conversion explicite
Dans toute expression, on peut forcer
explicitement des conversions de types grâce à un
opérateur unaire appelé cast. Dans la
construction (nom de type) expression lexpressio
n est convertie dans le type précisé (selon
certaines règles).
41Exemple 3
printf(d, pow(2,3)) / mauvaise façon
/ printf(d, (int) pow(2,3)) / bonne façon
/ int p struct complexe cplx p (int )
malloc(sizeof(int)) cplx (struct complexe )
malloc(sizeof(struct complexe))
42void free(void p)
free libère lespace mémoire pointé par p elle
ne fait rien si p vaut NULL. p doit être un
pointeur sur un espace mémoire alloué par malloc,
calloc ou realloc.
43void calloc(size_t nobj, size_t size)
calloc retourne un pointeur sur un espace mémoire
réservé à un tableau de nobj objets, tous de
taille size, ou bien NULL si cette demande ne
peut pas être satisfaite. La mémoire allouée
est initialisée par des zéros.
44void realloc(void p, size_t size)
realloc change en size la taille de lobjet
pointé par p. Si la nouvelle taille est plus
petite que lancienne, seul le début du contenu
de lobjet est conservé. Si la nouvelle taille
est plus grande, le contenu de lobjet est
conservé, et lespace mémoire supplémentaire
nest pas initialisé. realloc retourne un
pointeur sur un nouvel espace mémoire, ou bien
NULL si cette demande ne peut pas être
satisfaite, auquel cas p nest pas modifié.
45Exemple 4
On veut lire des entiers et les mettre en
mémoire. Plutôt que de créer un tableau avant
lexécution, on utilise calloc pendant
lexécution. int p, n scanf(d, n) p
(int ) calloc(n, sizeof(int)) si plus tard cet
espace nest plus suffisant, alors on utilise p
(int ) realloc(p, 2n)
p
46Exemple 5
Liste chaînée struct noeud int valeur
noeud suivant struct noeud chaine, p
chaine
p
47Exemple 5
chaine (struct noeud) malloc(sizeof(struct
noeud))
valeur
suivant
chaine
p
48Exemple 5
chaine (struct noeud) malloc(sizeof(struct
noeud)) chaine -gt valeur 8
8
chaine
p
49Exemple 5
chaine -gt suivant (struct noeud)
malloc(sizeof(struct noeud))
8
chaine
p
50Exemple 5
p chaine -gt suivant
8
chaine
p
51Exemple 5
p -gt valeur 5
8
5
chaine
p
52Exemple 5
p -gt suivant (struct noeud) malloc(sizeof(struct
noeud))
8
5
chaine
p
53Exemple 5
p p -gt suivant
8
5
chaine
p
54Exemple 5
p -gt valeur 13 p -gt suivant NULL
8
5
13
0
chaine
p
55- Nous venons ainsi de créer une liste de valeurs
qui sont liées entre elles par des pointeurs.
Autrement dit, ces valeurs en mémoire peuvent
être dans des endroits non contigues. Pou accéder
ces valeurs, il suffit davoir ladresse de la
première valeur qui est dans le pointeur chaine.
56Impression des valeurs dun chaine
- Soit une liste chainée en mémoire centrale, dont
le premier élément est à ladresse chaine. - Question Imprimer toutes les valeurs constiuant
cette liste.
57Exemple 6
p chaine while (p ! NULL) printf(d\n,
p-gtvaleur) p p-gtsuivant
8
5
13
0
chaine
p
58Exemple 6
- La solution précédente consiste à mettre
ladresse du premier élément dans le pointeur p.
La valeur p-gtvaleur est alors affichée pour
aller à lélément suivant, il suffit davoir son
adresse qui est dans - p-gtsuivant. Ce processus est répété jusquà
atteindre l fin de la chaine qui est donnée par
le pointeur NULL.
59Suppression dun élément dans une liste chainée
- Soit une liste chainée en mémoire centrale, dont
le premier élément est à ladresse chaine. - Question Supprimer lélément de valeur X sil
existe.
60- Solution Dans un premier temps, nous devons
rehercher cette valeur dans la liste. Si elle
nexiste pas, alors il ny a rien à faire. - Maintenant, si elle existe. Nous devons avoir
ladresse de son emplacement en mémoire
(supposons que cette adresse soit donnée par p),
et ensuite distinguer les deux cas suivants
61Lélément à supprimer nest pas le premier de la
liste
Q-gtSuivant P-gtsuivant free(P)
8
5
13
0
chaine
P
Q
8
5
13
0
chaine
Lélément à supprimer est le le premier de la
liste
Chaine chaine-gtsuivant Free(P)
P
62- Si cette valeur est en première position de la
liste. Dans ce cas, en la supprimant, le début de
cette liste va changer. Autrement dit, cest le
deuxième élément qui va devenir le premier
élément. Ceci sexprime par linstruction
suivante - chaine p-gtsuivant ou alors par
- chaine chaine-gtsuivant
63- Maintenant, si cette valeur nest pas le premier
élément, alors on doit mettre dans lélément
précédent cette valeur ladresse de lélément
suivant cette valeur. Si Q est ladresse de
lélément qui précéde cette valeur, alors cette
idée est exprimée comme suit - Q-gtsuivant p-gtsuivant
- Ensuite, on supprime physiquement de la liste
lemplacement de cette valeur pour la rendre
disponible pour deventuels appels dallocation
mémoire ultérieurs. Ceci est fait par - free(p)
64Lalgorithme est alors comme suit
- p chaine trouve 0
- while ((p ! NULL) (!trouve))
- if (p-gtvaleur x)
- trouve 1
- else
- q p / sauvegarde ladresse
actuelle - avant daller à lélément
suivant - p p-gtsuivant
-
- If (trouve) / lélément existe/
- if (p chaine) / cet élément est le
premier de la liste - chaine p-gtsuivant
- else q-gtsuivant p-gtsuivant
- free(p)
-
- else printf(élément inexistant)
-
65Ajout un élément X dans une liste chaînée
- Linsertion dun élément peut se faire de deux
manières - 1. Après un élément donné de valeur V
- 2. Avant un élément donné de valeur V
66- 1. Après un élément de valeur V
- La solution consiste dans un premier temps à
chercher si cet élément de valeur V existe dans
la liste. Si cet élément nexiste pas, il ny a
rien à faire. Sinon, on procède comme suit - - soit P ladresse de cet élément
- - allouer un espace mémoire pour ce nouvel
élément à laide de la fonction malloc soit Q
ladresse de cet espace mémoire - - après insertion, lélément X sera entre
lélément de valeur V et le suivant de V.
Autrment dit, le suivant de V sera maintenant le
nouvel élément X et le suivant de X sera le
suivant de V de tantôt. En termes algoritmiques,
on exprime cette idée comme suit -
67- Q.suivant P-gtsuivant (mettre comme suivant de X
le suivant de V - P.suivant Q (mettre comme suivant de V
lélément X
8
V
13
0
chaine
X
P
Q
68Lalgorithme est alors comme suit
- p chaine trouve 0
- while ((p ! NULL) (!trouve))
- if (p-gtvaleur V)
- trouve 1
- else p p-gtsuivant
- If (trouve) / lélément existe /
-
- Q (struct noeud) malloc(sizeof (struct
noeud))/ alloue une addresse mémoire - Q-gtvaleur X / y mettre la valeur X /
- Q-gt suivant p-gtsuivant
- P-gtsuivant Q
-
- else printf(élément inexistant)
-
69- 2. Insertion avant un élément de valeur V
- La solution consiste dans un premier temps à
chercher si cet élément de valeur V existe dans
la liste. Si cet élément nexiste pas, il ny a
rien à faire. Sinon, on procède comme suit - - soit P ladresse de cet élément
- - allouer un espace mémoire pour ce nouvel
élément à laide de la fonction malloc soit Q
ladresse de cet espace mémoire. - - après insertion, lélément X sera entre
lélément de valeur V et le lélément précédent
de V. Autrment dit, le précédent de V aura pour
élément suivant lélément X et le précédent de V
sera maintenant le nouvel élément X. Pour
effectuer ces opérations, on aura besoin de
ladresse de lélément précédent de V. Soit
preced cette adresse. Notons que si V est le
premier élément, alors après insertion de X, ce
sera X qui sera le nouveau premier élément de la
liste. - En termes algoritmiques, on exprime cette idée
comme suit
70- Si p chaine (signifie que V est le premier
élément de la liste) - linsertion va se faire comme suit
- Q-gtsuivant chaine (le suivant de
X est V) - chaine Q (X devient le premier
élément de la liste chaînée) - Sinon (V nest pas le premier élément de la
liste) - precedent-gtsuivant Q (le suivant
du précédent de V est X) - Q-gtsuivant p (le suivant de X
est V)
71Lalgorithme est alors comme suit
- p chaine / initialiser p au début de la liste
/ - trouve 0
- while ((p ! NULL) (!trouve))
- if (p-gtvaleur x)
- trouve 1 / élément trouvé /
- else preced p
- p p-gtsuivant
-
- If (trouve) / lélément existe
-
- Q (struct noeud) malloc(sizeof (struct
noeud))/ alloue une addresse mémoire - Q-gtvaleur X
- if (chaine p) / élément V est en
première position de la liste/ - Q-gtsuivant chaine / (le suivant de
X est V) / - chaine Q / (X devient le
premier élément de la liste chaînée)/ -
- else
- Q-gtvaleur X / mettre la valeur
X dans la case mémoire référencée par Q / - preced-gt suivant Q / mettre le
suivant du précédent de V lélément X /
72Lélément V nest pas le le premier de la
liste Precedent-gtsuivant Q Q-gtsuivant P
8
V
13
0
chaine
X
P
preced
Q
5
13
0
chaine
V
X
Lélément V est le le premier de la liste
Q-gtsuivant chaine Chaine Q
P
Q
73Avantages des pointeurs sur les tableaux
- La suppression et lajout dun élément se font
sans déplacer les autres éléments comme dans les
tableaux - Le nombre déléments na pas besoin dêtre connu
à lavance. - La longeur de la liste nest pas figée (peut
augmenter ou diminuer) - Mais, demande plus despace mémoire que le
tableaux.
74Listes doublement chainées
- Une liste est dite doublement chainée si chaque
élément contient ladresse de lélément suivant
et celle de lélément précédent.
chaine
Null
75- La déclaration de cette structure de données est
comme suit
Liste doublement chaînée typedef struct
listelement int valeur struct listelement
suivant struct listelement preced
noeud noeud chaine, p
76Création de la liste
- typedef fin -1
- noeud creation(void)
- / construit la liste dentiers jusquà
lecture de fin - int donnee
- noeud nouveaupoint, debut, derriere
- / construit le premier élément, sil y en a
un / - scanf(d,donnee)
- if (datafin)
- debut NULL
- else debut (noeud ) malloc(sizeof
(noeud)) - debut-gt valeur donnee
- debut-gtsuivant NULL
- debut-gtpreced NULL
- derriere debut
- / continuer la création /
- for (scanf (d, donnee) donnee
! fin scanf (d, donnee)) -
- nouveaupoint (noeud )
malloc(sizeof (noeud)) / alloue de la mémoire
/ - nouveaupoint-gtvaleur
donnee / remlissage de la mémoire/
77Impression de la liste
- void impression (noeud debut)
-
- noeud pointeur
- pointeur debut
- while (pointeur ! NULL)
-
- printf(d\n, pointeur-gtvaleur)
- pointeur pointeur-gtsuivant
-
-
78Ajout dune valeur avant une autre
- void ajouteravant(noeud debut, int valeur, int
data) - / ajouter dun élément valeur avant
lélément data sil existe / - noeud pointeur, debut, nouveau, avant
- pointeur debut trouve 0
- while ((nouveaupoint !NULL) (!trouve))
- if pointeur-gtvaleur data
- trouve 1
- else pointeur pointeur-gtsuivant / aller à
lélément suivant/ -
- if (trouve) / élément existe /
-
- nouveaupoint (noeud ) malloc(sizeof
(noeud)) / alloue de la mémoire pour le nouvel
élément/ - nouveaupoint-gtvaleur valeur
- / tester maintenant si lélément est le
premier de la liste - if (pointeur debut)
-
- nouveaupoint-gtsuivant debut
- nouveaupoint-gtpreced NULL
- debut nouveaupoint
79Ajout dune valeur après une autre
- void ajouterapres(noeud debut, int valeur, int
data) - / ajouter lélément valeur avant lélément
data sil existe / - noeud pointeur, debut, nouveaupoint,
apres - pointeur debut trouve 0
- while ((pointeur !NULL) (!trouve))
- if pointeur-gtvaleur data
- trouve 1
- else pointeur pointeur-gtsuivant / aller
à lélément suivant/ -
- if (trouve) / élément existe /
-
- nouveaupoint (noeud ) malloc(sizeof
(noeud)) / alloue de la mémoire pour le nouvel
élément/ - nouveaupoint-gtvaleur valeur
- apres pointeur-gtsuivant
- nouveaupoint-gtsuivant apres
- nouveaupoint-gtpreced pointeur
- apres-gtpreced nouveaupoint
- pointeur-gtsuivant nouveaupoint
80Suppression dune valeur
- void suppression (noeud debut, int data)
- / supprimer lélément data sil existe /
- noeud pointeur, debut, avant, apres
- pointeur debut trouve 0
- while ((nouveaupoint !NULL) (!trouve))
- if pointeur-gtvaleur data
- trouve 1
- else pointeur pointeur-gtsuivant / aller
à lélément suivant/ -
- if (trouve) / élément existe /
- / tester si cet élément est premier
dans la liste / - avant pointeur-gtpreced
- apres pointeur-gtsuivant
- if (debut pointeur)
- apres-gtpreced NULL
- debut apres
-
81Récursivité dans les listes chaînées
- Reprenons les liste chainées et essayons de
donner des versions récursives.
82Création dune liste
- include ltstdlib.hgt / donne accès à malloc /
- typedef fin -1
- noeud creation(void)
- / construit la liste dentiers jusquà
lecture de fin - int donnee
- noeud ansp
- / construit le premier élément, sil y en a
un / - scanf(d,donnee)
- if (datafin)
- debut NULL
- else debut (noeud ) malloc(sizeof
(noeud)) - debut-gt valeur donnee
- debut -gtsuivant creation () /
faire la même chose que précédemement/ -
- return (debut) / retourne ladresse du
premier élément / -
83Rechercher un élément
- / cette fonction recherche un élément dans une
liste / - noeud recherche (noeud debut, int data)
- noeud pointeur
- pointeur debut
- if pointeur-gtvaleur data
- return (pointeur)
- else pointeur recherche(pointeur-gtsuivant,
data) - return(NULL)
-
-
84Les arbres
- Définition Un arbre binaire est un ensemble de
noeuds qui est, soit vide, soit composé dune
racine et de deux arbres binaires disjoints,
appelés sous-arbre droit et sous-arbre gauche.
1
2
5
85- Définition Un arbre binaire est dit de recherche
(binary search tree) si, pour chaque noeud V,
tous les éléments qui aont dans sous arbre gauche
de V ont des valeurs inférieures à celle de V, et
tous les éléments du sous-arbres droit de V sont
supérieurs à celle de V. - Exemple
8
6
10
4
7
15
13
86- Remarqez que pour une suite de nombres, il existe
différentes arbres binaires de recherche
correspondant à cette liste. Tout dépend de la
manièe dont sont présentés ces nombres.
87- La déclaration de cette structure de données est
comme suit
Arbre binaire typedef struct listelement int
valeur struct listelement droit strcut
listelement gauche noeud noeud racine, p
88Création de larbre
- La fonction de création consiste à lire les
données destinées à êre introduites dans les
noeuds de larbre. - La création de larbre revient à insérer, à tour
de rôle, des éléments dans larbre existant, en
partant duin arbre vide.
89EXEMPLE
- SUPPOSONS QUE LA SUITE DE NOMBRES SUIVANTS EST
INTRODUITE DANS CET ORDRE - 4 5 0 28
45 7 - LARBRE DE RECHERCHE OBTENU EST COMME SUIT
90Version non récursive
- noeud inserer(noeud racine, int data)
- noeud p, q
- if (racine NULL)/ arbre vide/
- racine (noeud ) malloc(sizeof
(noeud)) - racine-gtvaleur data
- racine-gtdroit NULLl
- racine-gtgauche NULL
- return(racine)
-
- p racine
- while (p ! NULL) / trouver lemplacement
pour linsertion / - q p / sauvegarder le parent avant
daller vers les enfants / - if (data lt p-gtvaleur)
- p p-gtgauche / aller vers les
enfants de gauche / - else p p -gtdroit / aller les enfants
de droite / -
- p (noeud ) malloc(sizeof (noeud))
- p -gtvaleur data
- if (data lt q-gtvaleur)
91Version récursive
- noeud inserer(noeud racine, int data)
-
- noeud p, q
- if (racine NULL)
- racine debut (noeud ) malloc(sizeof
(noeud))/ allouer de lespace et mettre
ladresse dans racine - racine-gtvaleur new
- racine-gtgauche NULL
- racine-gtdroit NULL
-
- else
- if data lt racine-gtvaleur
- inserer(racine-gtgauche, data)
- else inserer(racine-gtdroit, data)
- return(racine)
-
-
-
92- typede fin -999
- noeud creation(noeud racine)
- racine NULL
- for (scanf (d, donnee) donnee ! fin
scanf (d, donnee)) - inserer(racine, donnee)
-
- return (racine) / retourne ladresse du
premier élément / -
93Recherche dun élément dans un arbre binaire de
recherche
- Parce que larbre possède la propriété dun arbre
de recherche, la manière dy rechercher un
élément est comem suit - 1. commencer à la racine
- 2. si la valeur de ce noeud est lélément
recherché, stop - 3. si la valeur du noeud courant est plus
grande que la valeur recherchée, alors continuer
la recherche dans la partie gauche de larbre. - 4. si la valeur du noeud courant est plus
petite que la valeur recherchée, alors continuer
la recherche dans la partie droite de larbre. - Cela nous consuit à lalgorithme suivant
-
94- noeud rechercher (noeud racine, int data)
- noeud p int trouve
- p racine trouve 0
- while (( p ! NULL) (!trouve))
- if (p-gtvaleur data)
- trouve 1
- else if (p-gtgauche gt data)
- p p-gtgauche
- else p p-gtdroit
- if (trouve)
- printf(élément trouvé)
- else printf(élément inexistant)
- return(p)
-
-
-
95La version récursive est comme suit
- noeud rechercher (noeud racine, int data)
- if (racine NULL)
- p NULL
- else if (racine-gtvaleur data)
- p racine
- else if (racine-gtvaleur gt data)
- p recherche(racine-gtgauc
he,data) - else p recherche(racine-gtdr
oit,data) - if (p NULL)
- printf(élément inexistant )
- else printf(élément existe)
- return(p)
-
-
-
96Suppression dun élément dans un arbre binaire de
recherche
- Cette opération doit être telle que larbre qui
va en résulter doit toujours rester un arbre
binaire de recherche. - Cette opération est un peu plus complexe et plus
longue que celle de linsertion bien quelle ne
consiste quen quelques miouvements de pointeurs - On peut penser que la suppression dun élément
consiste à le trouver, puis à relier la structure
sous-jacente à celle qui subsiste. - La structure de la fonction supprimer semble
similaire à celle de la fonction rechercher. - Elle lest effectivement, mais dans le cas où
lélément à supprimer est trouvé, il convient de
dsitinguer plusiers cas de figures - CAS 1 on enlve un élément situé à la racine
illustré par la figure suivante
Élément à supprimer
B
C
A
97- Daprès la fonction de création, tous les
éléments de C sont plus grands que ceux de A. - Premier cas particulier celui où C est vide.
Dans ce cas, A devient la racine. - Dans la cas contraire, on al choix entre A et C,
pour la racine. - Choisit-on A? Lélément le plus grand de A est
alors inférieur à lélément le plus petit de B. - Il faut donc rechercher lélément le plus grand
de A, en parcourant ses branches de droite
jusquà ce quon atteigne une feuille de larbre,
puis remplacer le pointeur de droite de cet
éléemnt par lancien pointeur droit, qui
pointait aupravent vers B. -
98- racine ancien pointeur gauche
de B
A
Ancien pointeur droit de B
C
99Avec C comme racine, nous aurions
- racine ancien pointeur droit
de B
C
Ancien pointeur gacuhe de B
A
100- CAS 2 Cest le cas où lélément enlevé nest pas
la racine de larbre.dans ce cas, il faut alors
anticiper la recherche de cet élément pour
pouvoir raccrocher les pointeurs de la structure
sous-jacente à ceux de la structure sus-jacente à
lélément concerné. - Soit par exemple, la suppression de B dans la
structure suivante
101D
B
Élément à supprimer
A
C
On a A lt B lt C lt D
Si on supprime B, on a donc A
lt C lt D
102- Ceci implique, soit une structure du type
suivant, qui réalise un chagement de racine et
une rotation de toute la structure à droite
C
A
D
103- Soit une structure de cet autre type, où la
structure sus-jacente nest pas modifiée
D
C
A
104- Dans le cas où C, on a simplement
D
A
105- Nous retiendrons, et programmerons, la deuxième
solution. - Lancien pointeur droit de B devient le pointeur
gauche de D. Tandis que le pointeur de lélément
le plus à gauche de C est maintenant égal à
lancien pointeur gauche de B. - Le cas où C est vide est traité par le simple
remplacement de lancien pointeur gauche de D
par lancien pointeur gauche de B.
106- CAS 3.
- Ce cas est symétrique au précédent. Soit une
structure du type
D
Élément à supprimer
F
E
G
107- Dans ce cas, nous avons
- D lt E lt F lt G
- Si on supprime F, on a
- D lt E lt
G - Ce qui donne deux possibilités
108E
G
D
Ou bien
D
E
G
109- Le raisonnement est tout à fait similaire, et
symétrique, à celui qui a été vu à gauche. - Nous obtenons alors la fonction récursive
suivante
110- noeud supprimer(noeud racine, int data)
- noeud PG, PT, POINTD
- if (racine NULL)
- printf(mot inexistant)
- else
- if (racine-gtvaleur data)/ supprimer la
racine/ - PT racine-gtdroite
- if (PT ! NULL)
- while(PT ! NULL)
- PG PT
- PT PT-gtgauche / aller
chercher le plus petitélément de la partie droite
/ -
- PG-gtgauche racine-gtgauche
- racine racine-gtdroite
- / fin du if
- else
- racine racine -gtgauche
- else /de racine-gt ! data /
-
111- if (data racine-gtgauche-gtvaleur)
- printf(élément trouvé à gauche)
- PT racine-gtgauche-gtdroite
- if (PT ! NULL)
- while (PT! NULL)
- PG PT
- PT PT-gtgauche
-
- PG-gtgauche racine-gtgauche-gtgauche
- racine-gtgauche racine-gtgauche-gtdroite
- /fin du if /
- else racine -gtgauche racine
-gtgauche-gtgauche -
- else / data ! racine-gtgauche-gtvaleur /
112- If (data racine -gtdroite-gtvaleur)
- printf(élément trouvé à droite)
- PT racine-gtdroite-gtgauche
- If (PT ! NULL)
- while (PT ! NULL)
- POINTD PT
- PT PT-gtdroite
-
- POINTD-gtdroite racine-gtdroite-gtdroite
- racine-gtdroite racine -gtdroite-gtgauche
-
- else racine -gtdroite racine -gtdroite-gtdroite
-
- else / data ! racine-gtdroite-gtvaleur /
113- if (data lt racine-gtvaleur)
- supprimer(racine-gtgauche,data)
- else
- supprimer(racine-gtdroite,data)
-
-
114Parcours dans un arbre
- Il existe différentes manière de parcourir un
arbre - Parcours postfix
- Parcours infix
- Parcours prefix
115Exemple
- Dans la parcours postfix, pour un noeud donné, il
est visité aprés les noeuds de sa partie gauche
et de sa partie droite. - Dans le parcours infix la partie gauche dun
noeud est visité, ensuite le noeud lui-même, et
enfin sa partie droite.
116- Dans le parcours, le noeud en question est
visité, ensuite sa partie gauche et enfin sa
partie droite.
117Exemple
21
12
23
8
15
27
20
6
9
14
118- Le parcours en postfixe
- 6 9 8 14 15 12 20 27 23 21
- Le parcours en infixe
- 6 8 9 12 14 15 20 21 23 27
- Le parcours en prefixe
- 21 12 8 6 9 15 14 23 20 27
119- void postfixe(noeud racine)
- if (racine ! NULL)
- postfixe(racine-gtgauche)
- postfixe(racine -gtdroite)
- printf(d,racine-gtvaleur)
-
-
- void infixe(noeud racine)
- if (racine ! NULL)
- infixe(racine-gtgauche)
- printf(d,racine-gtvaleur)
- postfixe(racine -gtdroite)
-
-
-
-
120- void prefixe(noeud racine)
- if (racine ! NULL)
- printf(d,racine-gtvaleur)
- prefixe(racine-gtgauche)
- prefixe(racine -gtdroite)
-
-
121Les arbres (suite)
N
........
Tk
T2
T1
T3
122- Le parcours en prefixe des noeuds de cet arbre
est la racine n suivie des neouds de T1 en
prefixe, puis ceux de T2, T3, ... Et Tk en
prefixe - Le parcours en infixe des noeuds de cet arbre est
lesnoeuds de T1 en infixe, suivi de la racine n,
suivies des noeuds de T2, T3, ... et Tk en
infixe - Le parcours en postfixe des noeuds de cet arbre
est les noeuds de T1, puis de T2, T3, ... et Tk
en postfixe, ensuite la racine n
123- Le parcours en postfixe se fait comme suit
- Prefixe(V)
- Pour chaque enfant C de V, de gauche à droite,
faire prefixe(C) - Visiter C
124- Le parcours en infixe se fait comme suit
- Infixe(V)
- if V est une feuille
- visiter V
- else
- infixe(enfent gauche de V)
- visiter V
- pour chaque enfant C de V, de gauche à
- droire, excepté le plus à gauche,
- faire infixe(C)
-
125- Le parcours en postfixe est comme suit
- Pour chaque enfant C de V, de gauche à droite,
faire postfixe(C) - Visiter C
126Exemple
1
2
4
3
5
6
7
8
9
12
10
11
13
14
127- En prefixe, le parcours de cet arbre est comme
suit - En postfixe, le parcours de cet arbre est comme
suit - En infixe, le parcours de cet arbre est comme
suit
128Implantation des arbres dans le cas général
- Comme le nombre de descendants varie dun noeud à
un autre, la déclaration dun noeud se fera
comme suit - Chaque noeud comprendra
- 1. la valeur de ce noeud
- 2. un pointeur vers le descendant le plus à
gauche - 3. un pointeur vers le frère/soeur (de même
niveau) de droite. -
129- typedef struct listelement
- int valeur
- struct listelement freredroit
- strcut listelement enfantgauche
- noeud
- noeud racine, p
130- Si par exemple, les fonctions suivantes
existaient déjà - noeud enfant_gauche(noeud n) retournant
ladresse du descendant le plus à gauche de n - noeud frere_droit(noeud n) retournant ladresse
du frère droit de n, alors la fonction prefixe
peut être comme suit
131- void prefixe(noeud racine)
- noeud C
- prinf(d,racine-gtvaleur)
- C enfant_gauche(racine)
- while (C ! NULL)
- prefixe(C)
- C frere_droit(C)
- / fin de la fonction /