STRUCTURES DE DONNÉES STRUCTURES DE DONNÉES

publicité
Centre CPGE TSI - Safi
2010/2011
Algorithmique et programmation :
STRUCTURES DE DONNÉES
A.
Structure et enregistrement
1) Définition et rôle des structures de données en programmation
1.1) Définition :
En informatique, une structure de données est une structure logique destinée à contenir des données,
afin de leur donner une organisation permettant de simplifier leur traitement. Une structure de
données implémente concrètement un type abstrait.
1.2) Objectifs de l’utilisation des structures de données
Exemple de la vie quotidienne : les numéros de téléphone peuvent être présentés par nom, par
profession (comme les Pages jaunes), par numéro téléphonique (comme les annuaires destinés au
télémarketing), par rue ou une combinaison quelconque de ces classements. À chaque usage
correspondra une structure d'annuaire appropriée.
En organisant d'une certaine manière les données, on permet un traitement automatique de ces
dernières plus efficace et plus rapide.
1.3) Exemples de structures de données
Différentes structures de données existent pour des données différentes ou répondant à des
contraintes algorithmiques différentes :
Exemples:
Variable, Tableau (à une et à deux dimension), enregistrements, Listes chainées, Piles, Files,
les arbres, les graphes,...
2) Les enregistrements
Jusqu'à présent, nous n'avons utilisé que des types primitifs (caractères, entiers, réels, chaînes) et
des tableaux de types primitifs. Mais nous pouvons créer nos propres types puis déclarer des
variables ou des tableaux d'éléments de ce type.
2.1) Définition :
Les enregistrements sont des structures de données dont les éléments peuvent être de type
différent. Contrairement aux tableaux qui sont des structures de données dont tous les éléments
sont de même type,
Pour créer des enregistrements, il faut déclarer un nouveau type (type structuré), basé sur d'autres
types existants.
Après avoir défini un type structuré, on peut l'utiliser comme un type normal en déclarant une ou
plusieurs variables de ce type.
Les variables de type structuré sont appelées enregistrements.
Les éléments qui composent un enregistrement sont appelés champs
2.2) Déclaration
Type
Structure nom_type
nom_champ1: type_champ1
…
nom_champN: type_champN
FinStruct
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
Exemple
Type
Structure Tpersonne
nom : chaîne
adresse : chaîne
âge : entier
FinStruct
Une fois qu'on a défini un type structuré, on peut déclarer des variables enregistrements exactement de la
même façon que l'on déclare des variables d'un type primitif.
Syntaxe
Variables
nom_var : nom_type
Exemples :
P,P1 : Tpersonne
2.3) Manipulation des enregistrements
La manipulation d'un enregistrement se fait au travers de ses champs. Comme pour les tableaux, il n'est pas
possible de manipuler un enregistrement globalement, sauf pour affecter un enregistrement à un autre de
même type (exemple PP1). Par exemple, pour afficher un enregistrement il faut afficher tous ses champs
uns par uns.
2.4) L’accès aux champs d’un enregistrement
L’accès aux champs se fait par le nom de la variable suivi d’un point suivi du nom du champ
Exemple :
P.age21
Lire(P.nom)
2.5) Les tableaux d'enregistrement
Il est possible de déclarer un tableau de type enregistrement:
nom_Tab: TABLEAU[taille] DE Type_Structuré;
Dans ce cas, l’accès aux différents champs se fait ainsi:
nom_Tab [indice].NomChamp
Exemple :
TabP : TABLEAU[10] de Tpersonne
TabP[i].nom
2.6) Passage d'un enregistrement en paramètre d'un sous-programme
Il est possible de passer tout un enregistrement en paramètre d'une fonction
Exemple : une fonction qui renvoie la différence d'âge entre deux personnes
Fonction difference (p1, p2 : Tpersonne)
Debut
Si p1.age > p2.age Alors
Retourne ( p1.age – p2.age )
Sinon
Retourne ( p2.age – p1.age )
FinSi
Fin
2.7) L'imbrication des structures
Un type structuré peut être utilisé comme type pour des champs d'un autre type structuré
Exemple :
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
Type
Structure adresse
num : entier
rue: chaîne
codePostal: entier
ville: chaîne
FinStructure
Structure Tpersonne
nom : chaîne
adr : adresse
âge : entier
FinStructure
Pour accéder à la ville d'une personne, il faut utiliser deux fois l'opérateur « . »
p1.adre.ville
3) Les enregistrements (structures) en C :
3.1) Types, l’instruction typedef
En plus des types de base : int, float, double, char. Le langage C permet de définir de
nouveaux types composés des types de base à l’aide de l'instruction typedef:
Exemples :
Typedef int entier ; // entier est l’equivalent de int
typedef unsigned int uint;
typedef float VecteurT[4]; /*VecteurT represente un type tableau de
quatre réels.*/
Ces nouveaux types peuvent ensuite être utilisés pour déclarer des variables exactement comme les
types primitifs du langage:
Exemples :
entier e ;
uint i;
VecteurT x, y, z;
3.2) Définition
struct
nomType{
Type1 nomChamp1
...
Typen nomChampn
} ;
Déclaration
struct
nomType
nomVariable ;
Exemple :
struct
Etudiant{
int num ;
char nom[20] ;
} ;
struct Etudiant
e1,e2 ;
…
Struct Etudiant
e3 ;
…
typedef struct Etudiant
Tetudiant;
Tetudiant e1,e2;
…
Tetudiant e3 ;
…
Il est possible, mais peu recommander de regrouper la définition de la structure et la déclaration des
variables
j. BAKKAS
Centre CPGE TSI - Safi
struct
nomType{
Type nomChamp1
...
Type nomChampn
}enreg1,enreg2 ;
2010/2011
Il est possible aussi de définir une structure sans nom de type et déclarer des variables possédants ce type :
struct {
Type nomChamp1
...
Type nomChamp
}enreg ;
En définissant une structure pareille il sera impossible de créer, par la suite, des enregistrements
ayant comme type cette structure.
3.3) Les tableaux d’enregistrements
Déclaration :
struct Nom_Structure Nom_Tableau[Nb_Elements];
Exemple :
struct Etudiant classe[28];
2
3
4
1
Alami Eljabri Hamdi Khaldi
B.
…
28
Zaker
Allocation dynamique de mémoire en C
1) Adresse mémoire
Une adresse mémoire est l'adresse d'un octet en mémoire centrale (ou du premier octet d'une série
d'octets d'adresses successives). Chaque octet de la mémoire a une adresse unique. Les adresses
permettent aux programmes de se repérer dans la mémoire.
L’espace mémoire occupé par une variable dépend de son type
Adresse
Valeur
0
8
12
16
17
0.321456
7658
23
‘A’
547652
9 123 432 567
…
1.000028
…
double
entier
caractère
Pour vérifier la taille de l’espace mémoire occupée par un type ou une variable on utilise
l'opérateur sizeof().
Exemple :
struct Point{
int x;
int y;
};
typedef struct Point point ;
int va ;
printf(“la variable va occupe : %d octets\n“, sizeof(va)) ;
printf(“le type char : %d octets \n“, sizeof(char)) ;
printf("Le type Point : %d octets\n", sizeof(point));
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
2) Allocation dynamique
2.1) Définition :
L'allocation dynamique de mémoire permet la réservation d'un espace mémoire pour un programme
au moment de son exécution. Contrairement à l'allocation statique de mémoire, où la réservation se
fait dès le début de l'exécution d'un bloc.
3) Allocation dynamique de mémoire en C
Il existe deux principales fonctions C de la bibliothèque <stdlib.h> permettant de demander de la
mémoire au système d'exploitation et de la lui restituer. Elles utilisent toutes les deux les pointeurs,
parce qu'une variable allouée dynamiquement n'a pas d'identificateur, étant donné qu'elle n'est pas
déclarée. Les pointeurs utilisés par ces fonctions C n'ont pas de type. On les référence donc avec des
pointeurs non typés. Leur syntaxe est la suivante :
void *malloc(taille)
free(pointeur)
malloc (abréviation de « Memory ALLOCation »). Elle attend comme paramètre la taille de la
zone de mémoire à allouer et renvoie un pointeur non typé (void *).
free (pour « FREE memory ») libère la mémoire allouée. Elle attend comme paramètre le
pointeur sur la zone à libérer et ne renvoie rien.
Lorsqu'on alloue une variable typée, on doit faire un transtypage du pointeur renvoyé par malloc
en pointeur de ce type de variable.
3.1) Pour réserver un entier
Déclaration d’un pointeur sur un entier :
int *pN ;
pN = (int*)malloc(sizeof(int));
pN pointe maintenant sur une variable (sans nom) de type entier qui a été réservée en mémoire.
Remarque : le (int *) transforme la valeur de retour en pointeur d'entier. On appelle cela un
transtypage (ou casting en anglais).
Manipulation de la variable
*pN = 3; //par exemple
Libération de la mémoire occupée par cette variable :
free(pN);
3.2) Allocation dynamique de tableaux
Déclaration d’un pointeur sur un entier :
int *tab ;
tab = (int*)malloc(3*sizeof(int));
tab pointe vers l'adresse du bloc alloué, c'est à dire le début du tableau. C'est donc un pointeur vers
le premier élément du tableau qui est renvoyé.
Ceci est donc conforme avec le fait qu'un tableau est égal (en terme de pointeur) au premier élément
du tableau. (ie : tab == tab[0] ) . L’accès aux cases du tableau se faire comme suit :
En utilisant les pointeurs
*tab= 10;
*(tab + 1) = 15;
*(tab + 2) = 1;
En utilisant les indices
tab[0]=10 ;
tab[1]=15 ;
tab[1]=1 ;
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
3.3) Les chaînes de caractères
Les chaînes de caractères en C sont définies comme des tableaux de caractères.
Rappel : Chaines de caractères statiques : (comme un tableau de caractères)
Initialisation lors de la déclaration :
•
•
•
char str[20]={'b','o','n','j','o','u','r','\0'};
char str[20]="bonjour";
char str[]="bonjour"; /*la taille du tableau est longueur de la chaîne +1*/
Initialisation en dehors de la zone de déclaration :
char str[20];
str="bonjour" ne peut plus s'écrire car str est un pointeur constant. Il faudra donc
affecter une valeur à chaque élément du tableau :
str[0]='b';
str[1]='o';
…
3.4) Chaines de caractères dynamiques :
Initialisation lors de la déclaration :
char *ch="bonjour"; /*fait pointer ch sur le début de la chaîne bonjour */
Initialisation en dehors de la zone de déclaration :
char *ch;
ch=(char*)malloc(taille);
ch="bonjour"; /*ch reçoit l'adresse de la chaîne constante "bonjour"*/
Dans ce mode de déclaration, on peut faire varier la place mémoire occupée par la chaîne en cours
de programme.
3.5) Pour réserver un espace pour un enregistrement
Déclarer un type de structure :
struct Fiche{
char Nom [10];
char Prenom[10];
int age ;
};
Déclarer une variable de type pointeur sur cette structure :
struct Fiche *pF; //Par exemple
pF = (Fiche*)malloc(sizeof(Fiche));
pF pointe maintenant une variable de type "Fiche" qui a été réservée en mémoire pas l'OS.
printf("Nom : %s", pF->Nom]); // Par exemple
pF est un pointeur (contient une adresse) sur une structure et non pas une structure d’où l’utilisation
de « -> »
Pour libérer la mémoire de ces variables :
free(pF);
C.
Les listes chainées
1)
Définition
Les listes chaînées sont des structures de données semblables aux tableaux sauf que l'accès à un
élément par un pointeur au lieu d’indice. Chaque élément étant repéré par ses voisins auxquels il est
relié.
L'allocation de la mémoire est faite d’une manière dynamique.
j. BAKKAS
Centre CPGE TSI - Safi
2)
2010/2011
Liste simplement chainée
ALAMI
123
Suivant
Suivant
Données
Données
Données
Données
Suivant
Suivant
Suivant
tete
Une liste étant complètement définie par :
• tete : Pointeur vers le premier élément
• Suivant : chaque élément possède un pointeur vers un autre nœud
• Valeur : valeur(s) de la donnée (des données) d’un nœud
• Fin : Le pointeur suivant du dernier élément doit pointer vers NULL (la fin de la liste)
Pour accéder à un nœud, la liste est parcourue dans une seule direction par un pointeur en
commençant du début vers la fin, le pointeur suivant permettant le déplacement vers le prochain
nœud.
3)
Représentation des listes chainées en C
Une liste chainée, en langage C est définie par :
• Des nœuds : Un nœud est représenté par d'une structure contenant une ou plusieurs
informations et un pointeur (le lien) vers le nœud suivant dans la liste.
• Une tête : représentée par un pointeur vers le premier enregistrement de la liste
o Ce pointeur vaut NULL si la liste est vide.
o Il pointe vers le premier nœud si la liste n'est pas vide.
FATIHI Omar
124
Suivant
ALAMI Said
123
Suivant
Saadi jalil
125
Suivant
KAMALI Ali
126
Suivant
tete
Les nœuds sont créés dynamiquement (avec la fonction malloc) au fur et à mesure des besoins et
détruits (avec la fonction free) s'ils ne sont plus utilisés.
3.1) Déclaration
typedef struct noeud
{
char *nom;
int num ;
struct noeud * suivant;
} Noeud;
Noeud * tete;
Tete=NULL ;
3.2) Insertion d'un élément au début de la liste
SALHI Sara
1
122
Suivant
2
ALAMI
Said
ALAMI
123
123
Suivant
Suivant
FATIHI Omar
124
Suivant
Saadi jalil
125
Suivant
KAMALI Ali
126
Suivant
tete
Pour cela il faut :
• La définition du nouveau pointeur :
Noeud * Nouveau;
•
allouer la mémoire nécessaire au nouveau nœud :
Nouveau = (Noued*)malloc(sizeof(Noeud));
Nouveau->nom= "SALHI Sara" ;
j. BAKKAS
Centre CPGE TSI - Safi
Nouveau->num= 122 ;
•
2010/2011
Pointer le nouveau élément vers la tète :
Nouveau->Suivant = Tete;
•
définir le nouveau maillon comme maillon de tête :
Tete = Nouveau;
3.3) Insertion d'un élément à la fin de liste
ALAMI
Said
ALAMI
123
123
Suivant
Suivant
FATIHI Omar
124
Suivant
Saadi jalil
125
Suivant
KAMALI Ali
126
Suivant
pCourant
tete
SALHI Sara
127
Suivant
Nouveau
Pour cela il faut parcourir la liste jusqu'à atteindre le dernier maillon (celui dont le pointeur possède
la valeur NULL). Le parcours se fait par un pointeur (appelé généralement pointeur courant) :
•
La définition d'un pointeur courant :
Noeud * pCourant;
•
Le parcours de la liste chaînée jusqu'au dernier noeud :
if (Tete != NULL) {
pCourant = Tete;
while (pCourant->Suivant != NULL) pCourant = pCourant->Suivant;
}
•
•
L'allocation de mémoire pour le nouvel élément :
Nouveau = (Noeud *)malloc(sizeof(Noeud));
Nouveau->nom= "SALHI Sara" ;
Nouveau->num= 127 ;
Faire pointer le pointeur courant vers le nouveau noeud, et le nouveau noeud vers NULL :
pCourant->Suivant = Nouveau;
Nouveau->Suivant = NULL;
3.4) Insertion d’un élément au milieu de la liste
ALAMI
Said
ALAMI
123
123
Suivant
Suivant
FATIHI Omar
124
Suivant
Saadi jalil
125
Suivant
2
tete
pCourant
KAMALI Ali
127
Suivant
SALHI Sara
126
Suivant
Zerwali saad
128
Suivant
1
Nouveau
•
Le parcours de la liste chaînée jusqu'à l’emplacement voulu:
if (Tete != NULL) {
pCourant = Tete;
while (pCourant->Suivant->num < 122) pCourant = pCourant->Suivant;
}
•
L'allocation de mémoire pour le nouvel élément :
Nouveau = (Noeud *)malloc(sizeof(Noeud));
Nouveau->nom= "SALHI Sara" ;
Nouveau->num= 127 ;
• Faire pointer le pointeur de nouveau noeud vers pCourant->Suivant avant de pointer le
pointeur courant vers le nouveau nœud
Nouveau->Suivant = NULL;
pCourant->Suivant = Nouveau;
j. BAKKAS
Centre CPGE TSI - Safi
D.
2010/2011
Les Piles
1) Définition.
La pile est une structure de données, qui permet de stocker les données dans l'ordre LIFO (Last In
First Out) - en français Dernier Entré Premier Sorti).
Une pille a un seul point d’accès, les données sont ajoutées ou retranchées par l’intermédiaire de la
tête d’accès appelé sommet de la pile.
Les opérations caractéristiques d’une pile sont empiler pour ajouter un élément et dépiler pour
retirer un élément.
Empiler
Dépiler
.
.
.
.
2) Représentation d'une pile
2.4) par tableau
Avantage: Facile car on ne modifie une pile que par un bout. Les opérations sont faciles.
Inconvénient: la hauteur est bornée (allocation statique de la mémoire)
struct pile{
int isommet ;
int TPile[50] ;
}
TPile 17 47 31 5 29 0 0
isommet
Par la suite on s’intéresse à la représentation d’une pile par liste chainée
2.5) Représentation par liste chaînée
Avantage: facile avec la tête de liste chaînée sur le haut de la pile (en particulier p = 0 si la pile
est vide)
Inconvénient: espace occupé par les pointeurs
sommet
données
suivant
données
suivant
données
suivant
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
2.6) Fonctions de manipulation d’une Pile.
Une pile est une liste sur laquelle on autorise opérations:
• Accès au sommet de la pile
• tester si la pile est vide
• empiler un élément, le mettre au sommet de la pile ==> PUSH
• dépiler un élément (par le sommet) ==> POP
2.6.1) La structure d'un élément
Pour représenter un élément de la pile, il suffit de reprendre la structure d'un élément d'une liste
doublement chaînée.
struct element{
int donnee;
struct pile *next;
};
typedef struct element Element;
2.6.2) Initialisation
Element *nouvellePile(void){
return (NULL);
}
sommet=nouvellePile() ;
2.6.3) Empiler un élément
Les éléments ne peuvent être ajoutés qu’au début de la liste, Voici l'algorithme d'insertion dans la
pile :
donnee
donnee
ALAMI
123
next
Suivant
donnee
donnee
donnee
next
next
next
next
nouveau
•
•
•
•
sommet
déclaration d'élément(s) à insérer
allocation de la mémoire pour le nouvel élément
remplir le contenu du champ de données
mettre à jour le pointeur début vers le 1er élément (le haut de la pile)
Element *empiler (Element *sommet, int donnee){
Element *nouveau;
nouveau = (Element *) malloc (sizeof (Element))
nouveau->donnee=donnee ;
nouveau ->next = sommet;
sommet = nouveau;
return sommet ;
}
NB : Il n’est pas nécessaire de faire un passage par adresse au paramètre « sommet » puisque la
fonction le retourne.
2.6.4) Dépiler un élément
L'élément retiré sera le dernier élément que l'on a ajouté, c'est-à-dire l'élément se trouvant au sommet de la
pile.
int depiler (Element **sommet){
int ret = -1;
if (*sommet!= NULL) {
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
Pile *temp = *sommet;
ret = (*sommet)->donnee;
*sommet=(*sommet)->next ;
free(temp) ;
}
return ret;
}
NB : ici il est nécessaire de faire un passage par adresse au paramètre « sommet » pour ne pas
perdre la pile après l’appel de la fonction qui retourne l’élément dépilé.
Exemple : utilisation d’une pile pour l’évaluation d’une expression mathématique
Soit l’expression mathématique suivante :
(2*5) + (2*(cos((18*10)/3)))
L’expression peut être représentée sous forme d’un arbre comme suit:
En déduit la notation postfixée (appelé aussi notation polonaise)
2 5 * 2 18 10 * 3 / cos * +
Procédure d’évaluation
Expression 2
5
*
2
18
10
*
3
/
Etat de la
Pile
2
E.
5
2
10
2
10
10
18
2
10
18
2
10
180
2
10
3
180
2
10
cos
*
+
60
2
10
0.5
2
10
1
10
10
Les files
1) Définition
Une file est une structure de données, qui permet de stocker les données dans l'ordre FIFO (First In
First Out) - en français Premier Entré Premier Sorti) avec un accès en entrée et un accès en sortie.
Les données sont ajoutées par l’intermédiaire d’un poste d’écriture appelé queue (fin de file), Et
prélever par l’intermédiaire d’un poste de lecture appelé tête (tête de file).
Enfiler
.
.
.
.
.
.
Défiler
j. BAKKAS
Centre CPGE TSI - Safi
2010/2011
2) Représentation d'une file
2.1) Représentation par tableau
Une file peut être représentée par un tableau :
Exemple :
struct maFile{
int Tab[20] ;
int iTete ;
int iQueue ;
};
Tab
0 0 0 17 47 31 5 29 0 0
iTete
iQueue
Par la suite on s’intéresse à la représentation de la file par une liste chaine.
2.2) Représentation par une liste chainée
Une file peut être représentée par une liste chainée comme ceci :
data
ALAMI
123
next
Suivant
data
data
data
next
next
next
tete
queue
2.3) Les opérations de manipulation d’une file.
Une file est une liste sur laquelle on autorise opérations:
• Accès à la tête et à la queue de la file
• tester si la file est vide
• enfiler un élément, le mettre à la fin de la file
• défiler un élément (par la tête)
2.3.1) Structure d’un élément
Pour représenter un élément de la file, il suffit de reprendre la structure d'un élément d'une liste
chaînée.
typedef struct noeud{
int data;
struct noeud *next;
} Noeud;
Noeud *tete,*queue ;
2.3.2) Initialisation
Noeud *nouvelleFile ()
{
return (NULL);
}
tete=nouvelleFile() ;
queue=nouvelleFile() ;
2.3.3) Ajouter un élément
Les éléments doivent être ajoutés au début, il faut donc insérer le nouvel élément avant le premier
de la liste.
j. BAKKAS
Centre CPGE TSI - Safi
data
ALAMI
123
next
Suivant
2010/2011
data
data
data
next
next
next
data
tete
queue
next
nouveau
1. On se positionne au début de la file, c'est au début qu'il faut ajouter un élément
2. Création d'un nouvel élément
3. On relie le nouvel élément avec le premier maillon de la file
Noeud *enfiler (Noeud *tete,Noeud **queue, int x){
Noeud *nouveau;
nouveau=(Noeud *)malloc(sizeof(Noeud));
nouveau->data=x;
nouveau->next=NULL;
if((*queue)!=NULL){
(*queue)->next=nouveau;
(*queue)=nouveau;
}else{
(*queue)=nouveau;
tete=nouveau;
}
return tete;
}
NB : normalement on effectue l’ajout à la fin (queue), mais le passage du pointeur tête est aussi
nécessaire pour ajouter le premier nœud de la file. Le passage de la queue par adresse est dû au
fait qu’une fonction ne peut pas retourner deux résultats au même temps.
2.3.4) Extraire un élément
data
next
temp
1.
2.
3.
4.
5.
data
ALAMI
123
next
Suivant
data
data
data
next
next
next
tete
queue
On se positionne à la fin de la file grâce au pointeur tete, c'est l'élément à supprimer
On sauvegarde le futur dernier élément de la file grâce au pointeur temp
L’avant dernier élément sera la tête de la file
Libération de la mémoire pointé par temp
On met le pointeur next de tete à NULL pour marquer la fin de la file
int defiler (Noeud **tete,Noeud **queue){
Noeud *temp;
int x;
if(queue!=NULL){
x=(*tete)->data;
temp=*tete;
*tete=(*tete)->next;
if(*tete==NULL) *queue=NULL;
free(temp);
}
return x;
}
j. BAKKAS
Téléchargement