La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Les adresses et les pointeurs. Les adresses............ 0 1 2 3 4 max Les cases mémoires ont toutes un numéro qui les distingue les une des autres: ce.

Présentations similaires


Présentation au sujet: "Les adresses et les pointeurs. Les adresses............ 0 1 2 3 4 max Les cases mémoires ont toutes un numéro qui les distingue les une des autres: ce."— Transcription de la présentation:

1 Les adresses et les pointeurs

2 Les adresses max 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.

3 Les 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 c1: 1 2 c2: 3 4 max char c1 = c2; Lire le contenu de la case 3. Mettre ce qui a été lu dans la case 1.

4 Le 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.

5 Adresses 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.

6 Position des variables dans la mémoire Sauf pour les tableaux, il ny a aucune garantie que les variables occupent des cases adjacentes en mémoire. Exemple. int a,b[4],c,d[3]; a b[0] b[1] b[2] b[3] d[1] d[2] d[0] c int

7 Les 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: c2: 5 max char int c1: 4 char

8 c1: 4 Remarque n: 0 c2: 5 max char int char int On peut aussi voir la mémoire comme une suite de cases de taille variable.

9 Les adresses et les tableaux Le nom dun tableau correspond à ladresse du début du tableau. Exemple: char tab[5]; printf(%p\n, tab); printf(%p\n, tab+1); printf(%p\n, tab+2); Note: %p sert à afficher les pointeurs.

10 Les tableaux dentiers Exemple: int tab[5]; printf(%p\n, tab); printf(%p\n, tab+1); printf(%p\n, tab+2); Question: Pourquoi? +4

11 Lincrémentation dune adresse 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 a: b[0]: b=24600 b[1]: b+1=24604 b[2]: b+2=24608 b[3]: b+3= d[1]: d+1=54317 d[2]: d+2= d[0]: d=54316 int char Ladresse a+1 nest pas valide

12 Remarque Si Tab est un tableau alors Ladresse de Tab[0] est Tab, ladresse de Tab[1] est Tab + 1, ladresse de Tab[2] est Tab + 2, etc. Cela est vrai quelque soit le type des éléments de Tab.

13 Lopé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, tab[1000]; Ladresse de c est &c Ladresse de n est &n Ladresse de tab[3] est &tab[3]

14 Lopé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 tab[1000]; Le contenu de ladresse tab + 25 est *(tab + 25) *(tab + 25) est donc identique à tab[25] *(&c) est identique à c *c na aucun sens

15 Exemple Les expressions logiques suivantes sont vraies: &n == *(12560) == 60 *(12560) < c2 *(&r) == char double n: int c1: 12560char r: c2: 12561

16 Ré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)

17 Les 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

18 Les pointeurs: exemple Exemple: int *pn, m; pn: m: int* int

19 Les pointeurs: exemple Exemple: int *pn, m; pn = &m; pn: m: int* int

20 Les pointeurs: exemple Exemple: int *pn, m; pn = &m; m = 6; pn: m: int* int

21 Les pointeurs: exemple Exemple: int *pn, m; pn = &m; m = 6; *pn = 38; pn: m: int* int

22 Les pointeurs et les tableaux En C les pointeurs sont intimement liés aux tableaux. Exemple: int tab[10], *p; p=tab; tab[3] = 70; *(tab + 3) = 70; p[3] = 70; *(p + 3) = 70; tous équivalents:

23 Remarque Le nom dun tableau est une adresse constante et non pas un pointeur qui est une variable. Exemple: int tab[10], *p; p=tab; p = tab;/* Valide */ tab = p;/* Non valide */ tab : p:

24 Quelques 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

25 Les pointeurs comme paramètres void echanger( int &x, int &y){ int tmp; tmp = x; x = y; y = tmp; } En C++: echanger(a, b)

26 Les pointeurs comme paramètres void echanger( int *x, int *y){ int tmp; tmp = *x; *x = *y; *y = tmp; } En C: echanger(&a, &b)

27 Les 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.

28 Les tableaux passés en paramètre void liretab(int tab[], int max) { int c; int i=0; while ((c=getchar()) != EOF){ tab[i] = c; i = i + 1; }

29 Les tableaux passés en paramètre void liretab(int *tab, int max) { int c; int i=0; while ((c=getchar()) != EOF){ *(tab+i) = c; i = i + 1; }

30 Les indices de tableaux Exemple 1 Tableau dentiers dont les indices vont de -5 à 5: int tab[11]; int *ptab; ptab = tab + 5; ptab[0] est identique à tab[5] ptab[-5] est identique à tab[0] ptab[5] est identique à tab[10] tab : ptab:

31 Les indices de tableaux Exemple 2 Tableau dentiers dont les indices vont de A à Z: int tab[26]; int *ptab; ptab = tab - A;/* A vaut 65 en ASCII */ ptab[A] est identique à tab[0] ptab[Z] est identique à tab[25] tab : ptab: -65

32 Allocation 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.

33 Exemple 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.

34 Le 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..

36 Exemple 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 Après malloc

37 Exemple 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 Après malloc Pourquoi?

38 Pointeurs 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

39 Conversion 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 */

40 Conversion 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 lexpression est convertie dans le type précisé (selon certaines règles).

41 Exemple 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));

42 void 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.

43 void *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.

44 void *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é.

45 Exemple 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, 2*n); p: …

46 Exemple 5 Liste chaînée struct noeud{ int valeur; noeud *suivant; }; struct noeud *chaine, *p; chaine: p:

47 Exemple 5 chaine = (struct noeud) malloc(sizeof(struct noeud)); chaine: p: valeursuivant

48 Exemple 5 chaine = (struct noeud) malloc(sizeof(struct noeud)); chaine -> valeur = 8; chaine: 8 p:

49 Exemple 5 chaine -> suivant = (struct noeud) malloc(sizeof(struct noeud)); chaine: 8 p:

50 Exemple 5 p = chaine -> suivant; chaine: 8 p:

51 Exemple 5 p -> valeur = 5; chaine: 8 5 p:

52 Exemple 5 p -> suivant = (struct noeud) malloc(sizeof(struct noeud)); chaine: 8 5 p:

53 Exemple 5 p = p -> suivant; chaine: 8 5 p:

54 Exemple 5 p -> valeur = 13; p -> suivant = NULL; chaine: 8 5 p: 130

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.

56 Impression 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.

57 Exemple 6 p = chaine; while (p != NULL){ printf(%d\n, p->valeur); p = p->suivant; } chaine: 8 5 p: 130

58 Exemple 6 La solution précédente consiste à mettre ladresse du premier élément dans le pointeur p. La valeur p->valeur est alors affichée; pour aller à lélément suivant, il suffit davoir son adresse qui est dans p->suivant. Ce processus est répété jusquà atteindre l fin de la chaine qui est donnée par le pointeur NULL.

59 Suppression 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 :

61 chaine: P Q chaine: P Q->Suivant = P->suivant free(P) Chaine = chaine->suivant Free(P) Lélément à supprimer nest pas le premier de la liste Lélément à supprimer est le le premier de la liste

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->suivant ou alors par chaine = chaine->suivant

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->suivant = p->suivant 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)

64 Lalgorithme est alors comme suit: p = chaine; trouve = 0 while ((p != NULL) && (!trouve)) if (p->valeur = x) trouve =1; else { q = p; /* sauvegarde ladresse actuelle avant daller à lélément suivant p = p->suivant; } If (trouve) /* lélément existe*/ { if (p == chaine) /* cet élément est le premier de la liste chaine = p->suivant; else q->suivant = p->suivant; free(p); } else printf(élément inexistant);

65 Ajout 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->suivant (mettre comme suivant de X le suivant de V P.suivant = Q (mettre comme suivant de V lélément X chaine: 8 V130 P X Q

68 Lalgorithme est alors comme suit: p = chaine; trouve = 0 while ((p != NULL) && (!trouve)) if (p->valeur = V) trouve =1; else p = p->suivant; If (trouve) /* lélément existe */ { Q = (struct noeud) malloc(sizeof (struct noeud));/* alloue une addresse mémoire Q->valeur = X; /* y mettre la valeur X */ Q-> suivant = p->suivant; P->suivant = 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->suivant = 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->suivant = Q (le suivant du précédent de V est X) Q->suivant = p (le suivant de X est V)

71 Lalgorithme est alors comme suit: p = chaine; /* initialiser p au début de la liste */ trouve = 0 while ((p != NULL) && (!trouve)) if (p->valeur = x) trouve =1; /* élément trouvé */ else { preced = p; p = p->suivant; } If (trouve) /* lélément existe { Q = (struct noeud) malloc(sizeof (struct noeud));/* alloue une addresse mémoire Q->valeur = X; if (chaine == p) /* élément V est en première position de la liste*/ { Q->suivant = chaine; /* (le suivant de X est V) */ chaine = Q; /* (X devient le premier élément de la liste chaînée)*/ } else { Q->valeur = X; /* mettre la valeur X dans la case mémoire référencée par Q */ preced-> suivant = Q; /* mettre le suivant du précédent de V lélément X */ Q->suivant = p; /* mettre le précédent de V lélément X */ }

72 chaine: 8 V130 P preced chaine: V 5130 P Q->suivant = chaine Chaine = Q Lélément V nest pas le le premier de la liste Precedent->suivant = Q Q->suivant = P Lélément V est le le premier de la liste X Q X Q

73 Avantages 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.

74 Listes 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;

76 Cré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 (data==fin) debut = NULL; else { debut = (noeud *) malloc(sizeof (noeud)); debut-> valeur= donnee; debut->suivant = NULL; debut->preced = 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->valeur = donnee; /* remlissage de la mémoire*/ nouveaupoint->suivant = NULL; /* cet élément ne pointe vers aucun autre élément*/ nouveaupoint->preced = derriere; /*ladresse du précédent est mise dans ce pointeur*/ derriere = nouveaupoint; /*sauvegarde de ladresse de cet élément } return (debut) /* retourne ladresse du premeir élément */ }

77 Impression de la liste void impression (noeud *debut) { noeud *pointeur; pointeur = debut; while (pointeur != NULL) { printf(%d\n, pointeur->valeur); pointeur = pointeur->suivant; }

78 Ajout 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->valeur = data trouve = 1; else pointeur = pointeur->suivant; /* 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->valeur = valeur; /* tester maintenant si lélément est le premier de la liste if (pointeur == debut) { nouveaupoint->suivant = debut; nouveaupoint->preced = NULL; debut = nouveaupoint; } else { avant = pointeur->preced; nouveaupoint->suivant = pointeur; nouveaupoint->preced = avant; avant->suivant = nouveaupoint; pointeur->preced = nouveaupoint; } } /* fin de la fonction */

79 Ajout 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->valeur = data trouve = 1; else pointeur = pointeur->suivant; /* 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->valeur = valeur; apres = pointeur->suivant; nouveaupoint->suivant = apres; nouveaupoint->preced = pointeur; apres->preced = nouveaupoint; pointeur->suivant = nouveaupoint; } } /* fin de la fonction */

80 Suppression 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->valeur = data trouve = 1; else pointeur = pointeur->suivant; /* aller à lélément suivant*/ if (trouve) /* élément existe */ { /* tester si cet élément est premier dans la liste */ avant = pointeur->preced; apres = pointeur->suivant; if (debut == pointeur){ apres->preced = NULL; debut = apres; } else { avant->suivant = apres; apres->preced = avant; } } /* fin de la fonction */

81 Récursivité dans les listes chaînées Reprenons les liste chainées et essayons de donner des versions récursives.

82 Création dune liste #include /* 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 (data==fin) debut = NULL; else { debut = (noeud *) malloc(sizeof (noeud)); debut-> valeur= donnee; debut ->suivant = creation (); /* faire la même chose que précédemement*/ } return (debut) /* retourne ladresse du premier élément */ }

83 Rechercher un élément /* cette fonction recherche un élément dans une liste */ noeud *recherche (noeud *debut, int data){ noeud *pointeur; pointeur = debut; if pointeur->valeur = data return (pointeur); else pointeur = recherche(pointeur->suivant, data); return(NULL); }

84 Les 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 25

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:

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;

88 Cré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.

89 EXEMPLE SUPPOSONS QUE LA SUITE DE NOMBRES SUIVANTS EST INTRODUITE DANS CET ORDRE: LARBRE DE RECHERCHE OBTENU EST COMME SUIT:

90 Version non récursive noeud *inserer(noeud *racine, int data){ noeud *p, *q; if (racine == NULL){/* arbre vide*/ racine = (noeud *) malloc(sizeof (noeud)); racine->valeur = data; racine->droit = NULLl; racine->gauche = NULL; return(racine); } p = racine; while (p != NULL){ /* trouver lemplacement pour linsertion */ q = p; /* sauvegarder le parent avant daller vers les enfants */ if (data valeur) p = p->gauche; /* aller vers les enfants de gauche */ else p = p ->droit; /* aller les enfants de droite */ } p = (noeud *) malloc(sizeof (noeud)); p ->valeur = data; if (data valeur) q->gauche = p; /* relier le parent référencé par q au nouvel élément comme enfant gauche */ else q->droite = p; /* relier le parent référencé par q au nouvel élément comme enfant droit */ return(p); }

91 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->valeur = new; racine->gauche = NULL; racine->droit = NULL; } else if data valeur inserer(racine->gauche, data); else inserer(racine->droit, data); return(racine); } Version récursive

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 */ }

93 Recherche 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->valeur == data) trouve =1; else if (p->gauche > data) p = p->gauche; else p = p->droit; if (trouve) printf(élément trouvé); else printf(élément inexistant); return(p) }

95 La version récursive est comme suit: noeud * rechercher (noeud * racine, int data){ if (racine == NULL) p = NULL; else if (racine->valeur == data) p = racine; else if (racine->valeur > data) p = recherche(racine->gauche,data); else p = recherche(racine->droit,data); if (p ==NULL) printf(élément inexistant ); else printf(élément existe); return(p); }

96 Suppression 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 enl`ve un élément situé à la racine illustré par la figure suivante: B A C Élément à supprimer

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

99 racine = ancien pointeur droit de B C Ancien pointeur gacuhe de B A Avec C comme racine, nous aurions:

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:

101 D B A C On a: A < B < C < D Si on supprime B, on a donc: A < C < D Élément à supprimer

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 F EG Élément à supprimer

107 Dans ce cas, nous avons: D < E < F < G Si on supprime F, on a: D < E < G Ce qui donne deux possibilités :

108 E D G 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->valeur == data){/* supprimer la racine*/ PT = racine->droite; if (PT != NULL){ while(PT != NULL){ PG = PT; PT = PT->gauche; /* aller chercher le plus petitélément de la partie droite */ } PG->gauche =racine->gauche; racine = racine->droite; } /* fin du if else racine = racine ->gauche; else /*de racine-> != data */ {

111 if (data == racine->gauche->valeur) { printf(élément trouvé à gauche); PT = racine->gauche->droite; if (PT != NULL){ while (PT!= NULL){ PG = PT; PT = PT->gauche; } PG->gauche = racine->gauche->gauche; racine->gauche = racine->gauche->droite; } */fin du if */ else racine ->gauche = racine ->gauche->gauche; } else /* data != racine->gauche->valeur */

112 If (data == racine ->droite->valeur){ printf(élément trouvé à droite); PT = racine->droite->gauche; If (PT != NULL){ while (PT != NULL){ POINTD = PT; PT = PT->droite; } POINTD->droite = racine->droite->droite; racine->droite racine ->droite->gauche; } else racine ->droite = racine ->droite->droite; } else /* data != racine->droite->valeur */

113 if (data valeur) supprimer(racine->gauche,data) else supprimer(racine->droite,data) }

114 Parcours dans un arbre Il existe différentes manière de parcourir un arbre: Parcours postfix Parcours infix Parcours prefix

115 Exemple 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.

117 Exemple

118 Le parcours en postfixe Le parcours en infixe Le parcours en prefixe

119 void postfixe(noeud *racine){ if (racine != NULL){ postfixe(racine->gauche); postfixe(racine ->droite); printf(%d,racine->valeur); } void infixe(noeud *racine){ if (racine != NULL){ infixe(racine->gauche); printf(%d,racine->valeur); postfixe(racine ->droite); }

120 void prefixe(noeud *racine){ if (racine != NULL){ printf(%d,racine->valeur); prefixe(racine->gauche); prefixe(racine ->droite); }

121 Les arbres (suite) Soit larbre suivant: N T1 T2 T3 Tk

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;

126 Exemple

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:

128 Implantation 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->valeur); C = enfant_gauche(racine) while (C != NULL){ prefixe(C) C = frere_droit(C); } /* fin de la fonction */


Télécharger ppt "Les adresses et les pointeurs. Les adresses............ 0 1 2 3 4 max Les cases mémoires ont toutes un numéro qui les distingue les une des autres: ce."

Présentations similaires


Annonces Google