ift-2000: structures de données tableaux dominic genest, 2009

24
IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Upload: anne-hubert

Post on 04-Apr-2015

115 views

Category:

Documents


6 download

TRANSCRIPT

Page 1: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

IFT-2000: Structures de données

Tableaux

Page 2: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Les tableaux

• Tableaux– Statiques– Chaînes de caractères– Dynamiques– À plusieurs dimensions

Page 3: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux statiques• float x[100];

x[0]=4.5f;x[1]=2.3f;printf(« %f »,x[0]);printf(« %f »,x[1]);printf(« %f »,x[2]); // ?

• Quand on utilise un tableau statique pour emmagasiner un nombre indéterminé de données, il faut l’accompagner d’une variable de type « int » pour savoir combien de données sont vraiment considérées comme faisant partie de notre structure. La taille du tableau lui-même est une limite maximale.

• typedef struct{

float x[100];int n;

} Nombres;CodeErreur initNombres(Nombres *n);CodeErreur ajoutNombres(Nombres *n , float nouv_nbr);// etc.

En C++:struct Nombres{

Nombres();void ajout(float nouv_nbr);// etc.

private:float x[100];int n;

};

Page 4: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux statiques

• Quand « n » vaut zéro, on considère que le tableau est vide.• Quand on initialise le tableau, on met 0 dans n. Quand on

ajoute un élément, on incrémente ce n. Quand on en supprime un, on le décrémente. Quand on fait une recherche, on ne traite comme candidats que les éléments de 0 à n-1.

• Quand n vaut la taille allouée entre les crochets (dans l’exemple précédent, c’était 100), alors le tableau n’a plus d’espace pour ajouter quoi que ce soit. Le principal désavantage des tableaux statiques est d’avoir à fixer une telle limite.

Page 5: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux statiques

• Une autre convention, pour déterminer la fin utilisable d’un tableau statique, est de remplacer l’utilisation d’un nombre entier à côté par une valeur spéciale supplémentaire dans le tableau lui-même, placée à la fin.

• C’est la convention adoptée pour les chaînes de caractères. Le caractère zéro est placé à la fin de toute chaîne de caractères en guise de marqueur de fin. Ce caractère est reconnu par les fonctions « strlen », « strcpy », « strcat », et compagnie.

Page 6: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux statiques

En Ctypedef enum { OK, Erreur } CodeErr;typedef struct{

int matricule,nb_cours,age;char nom[50];

} Etudiant;typedef struct{

Etudiant etudiants[40000];int n;

} Universite;CodeErr initUniversite(Universite *u);CodeErr ajoutUniversite(Universite *u, Etudiant et);int trouve_age(const Universite *u,

int matricule);void detruitUniversite(Universite *u);

En C++struct Etudiant{public:

int matricule,nb_cours,age;char nom[50];

};struct Universite{public:

Universite();void ajout(Etudiant et);int trouve_age(int matricule) const;~Universite();

private:Etudiant etudiants[40000];int nb_etudiants;

};

Page 7: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux statiquesEn C

CodeErr initUniversite(Universite *u){

u->n=0;return OK;

}CodeErr ajoutUniversite(Universite *u, Etudiant et){

if(u->n==40000) return Erreur;u->etudiants[u->n++] = et;return OK;

}int trouve_age(const Universite *u,

int matricule){

int i;for(i=0;i<u->n;i++)

if(u->etudiants[i].matricule==matricule)return u->etudiants[i].age;

return -1; // Convention pour matricule introuvable.}void detruitUniversite(Universite *u){}

En C++using namespace std;Universite::Universite(){

n=0;}void Universite::ajout(Etudiant et){

if(n==40000)throw runtime_error(‘’L’université est

pleine!’’);etudiants[n++]=et;

}int Universite::trouve_age(int matricule) const{

for(int i=0;i<n;i++)if(etudiants[i].matricule==matricule)return etudiants[i].age;

return -1; // Convention pour matricule introuvable.

}Universite::~Universite(){}

Page 8: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux dynamiques• Rappel: Une variable déclarée comme tableau utilisée à elle seule

sans les crochets est de type « pointeur » et désigne l’adresse-mémoire du début de ce tableau. On peut lui appliquer toutes les opérations qu’on peut appliquer à un tel pointeur.

• Inversement, on peut appliquer l’opérateur [] à un pointeur, de sorte qu’on considère le contenu de ce pointeur comme étant l’adresse-mémoire du début d’un tableau auquel on veut accéder.

• float t[20];float *p;p=t;p[4]=2.3f;printf(« %f »,t[4]); // Ceci affiche 2.3

Page 9: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux dynamiques

En Cfloat *p;p = malloc(sizeof(float)*20);p[4]=2.3f;printf(‘’%f’’,p[4]);free(p);

En C++float *p;p = new float[20];p[4]=2.3f;printf(‘’%f’’,p[4]);delete[] p;

Page 10: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

La fonction « malloc »• La fonction « malloc » sert à demander au système d’exploitation un certain

espace mémoire, en cours d’exécution du programme.• Son avantage est que la taille de ce bloc-mémoire peut ne pas encore être

déterminée à la compilation.• La fonction prend un paramètre qui est un nombre d’octets (donc il faut

nous-mêmes multiplier le nombre d’éléments par le nombre d’octets pour chaque élément, à l’aide de l’opérateur sizeof).

• La fonction retourne l’adresse-mémoire du début du bloc-mémoire alloué, laquelle doit normalement être emmagasinée dans un pointeur.

• Si jamais le système d’exploitation n’arrive pas à allouer l’espace demandé, la valeur zéro est retournée. Si on veut vérifier tous les cas d’erreurs, il faut donc comparer la valeur retournée avec zéro (ou certains préfèrent la constante NULL, mais c’est exactement la même chose que zéro…) et traiter ce cas spécial avant de continuer.

Page 11: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

La fonction « free »

• La fonction free sert à libérer un espace-mémoire précédemment alloué à l’aide de malloc.

• On doit lui passer en paramètre le début d’un espace-mémoire, lequel doit obligatoirement avoir été retourné par un appel à malloc.

• On ne peut pas libérer une partie d’un bloc-mémoire alloué. On doit toujours le libérer en entier.

• Si on passe la valeur zéro à free, il ne se passe rien. Cela s’avère pratique dans certains cas.

Page 12: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

La fonction « realloc »

#include<math.h>int i;float *t;t = malloc(sizeof(float)*10);If(t==0) { printf(‘’Pas assez de mémoire.’’); exit(-1); }for(i=0;i<10;i++) t[i]=sin(i*2*M_PI/10);// Disons qu’on se rend compte ici qu’on a// besoin de plus d’espace…t = realloc(t,sizeof(float)*14);If(t==0) { printf(‘’Pas assez de mémoire.’’); exit(-1); }for(i=10;i<14;i++) t[i]=sin(i*2*M_PI/10);free(t);

Page 13: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

La fonction « realloc »

• La fonction realloc prend deux paramètres: une adresse-mémoire obligatoirement retournée par un appel précédent à malloc (ou à realloc), puis un nouveau nombre d’octets désiré pour le bloc (qui peut être plus petit ou plus grand).

• realloc retourne l’adresse-mémoire du début du bloc-mémoire, qui peut très souvent être changé de place lors de l’agrandissement (ou même lors du rapetissement).

• Il y a deux cas spéciaux d’appels à realloc:– Un appel à realloc avec zéro comme nouveau nombre d’octets

(deuxième paramètre) est équivalent à un appel à « free ».– Un appel à realloc avec zéro comme adresse-mémoire (premier

paramètre) est équivalent à un appel à « malloc ».

Page 14: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux dynamiques

typedef enum { OK, Erreur } CodeErr;typedef struct{

int matricule,nb_cours,age;char nom[50];

} Etudiant;typedef struct{

Etudiant *etudiants;int n;

} Universite;CodeErr initUniversite(Universite *u);CodeErr ajoutUniversite(Universite *u, Etudiant et);int trouve_age(const Universite *u,

int matricule);void detruitUniversite(Universite *u);

CodeErr initUniversite(Universite *u){

u->n=0;return OK;

}CodeErr ajoutUniversite(Universite *u, Etudiant et){

Etudiant *e;if(u->n==0){

u->etudiants = malloc(sizeof(Etudiant));if(!u->etudiants) return Erreur;u->etudiants[0]=et;u->n=1;return OK;

}e = realloc(u->etudiants,sizeof(Etudiant)*(u->n+1));if(!e) return Erreur;u->etudiants = e;u->etudiants[u->n++] = et;return OK;

}void detruitUniversite(Universite *u){

if(u->n>0) free(u->etudiants);}

Page 15: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Simplification grâce aux cas particuliers de « realloc » avec zéro

CodeErr initUniversite(Universite *u){

u->n=0;u->etudiants=0;return OK;

}CodeErr ajoutUniversite(Universite *u, Etudiant et){

Etudiant *e = realloc(u->etudiants,sizeof(Etudiant)*(u->n+1));if(!e) return Erreur;u->etudiants = e;u->etudiants[u->n++] = et;return OK;

}void detruitUniversite(Universite *u){

free(u->etudiants);}

Page 16: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Remarques sur « malloc », « free » et « realloc »

• Les appels à ces trois fonctions sont très lents et on doit s’organiser pour en faire un minimum. Une implémentation comme l’exemple précédent est très peu performante car elle implique un appel à « realloc » lors de chaque ajout.

• Il est préférable d’agrandir le tableau plus rarement que les ajouts; cela se fait en réservant à l’avance de la place dans le tableau.

Page 17: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Réduction du nombre d’allocations

typedef enum { OK, Erreur } CodeErr;typedef struct{

int matricule,nb_cours,age;char nom[50];

} Etudiant;typedef struct{

Etudiant *etudiants;int n;int nb_alloues;

} Universite;CodeErr initUniversite(Universite *u);CodeErr ajoutUniversite(Universite *u, Etudiant et);int trouve_age(const Universite *u,

int matricule);void detruitUniversite(Universite *u);

CodeErr initUniversite(Universite *u){

u->n=0;u->etudiants=0;u->nb_alloues=0;return OK;

}CodeErr ajoutUniversite(Universite *u, Etudiant et){

if(u->n==u->nb_alloues){ // On double la taille du tableau

Etudiant *e = realloc(u->etudiants,sizeof(Etudiant)*((u->n+1)*2));

if(!e) return Erreur;u->etudiants = e;u->nb_alloues = (u->n+1)*2;

}u->etudiants[u->n++] = et;return OK;

}void detruitUniversite(Universite *u){

free(u->etudiants);}

Page 18: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Chaînes de caractères dynamiques

• Puisqu’une chaîne de caractères n’est finalement qu’un tableau de caractères, on peut en faire une version dynamique.

char *x;x = malloc(sizeof(char)*(strlen(‘’Bonjour’’)+1));if(!x) { …erreur… }strcpy(x, ’’Bonjour’’);printf(‘’%s’’,x);free(x);

Page 19: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Copies profondes et copies superficielles

• Lorsqu’une structure a un membre qui est un pointeur, copier le contenu de la structure d’une variable à une autre avec l’opérateur « = » ne va que copier le contenu du pointeur en question. Ainsi, les deux variables partageront un même espace-mémoire.

• Cela peut être désirable dans certains cas particuliers (si on implémente un index, par exemple), mais c’est rarement le cas. Par exemple, si on fait free sur l’une des copies, alors la deuxième copie n’est plus utilisable non plus et fera planter le logiciel.

typedef struct{

int matricule,age,nb_cours;char *nom;

} Etudiant;Etudiant a,b;a.nom = malloc(sizeof(char)*(strlen(‘’Dominic’’)+1));strcpy(a.nom,’’Dominic’’);b = a; // Dangereux!

Page 20: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux à plusieurs dimensions

• Puisqu’une chaîne de caractères est un tableau, si on veut faire un tableau de chaînes de caractères, on a alors à faire à un tableau de tableaux.

• On a le choix entre:– Un tableau statique de chaînes de caractères statiques– Un tableau dynamique de chaînes de caractères statiques– Un tableau statique de chaînes de caractères dynamiques– Un tableau dynamique de chaînes de caractères

dynamiques

Page 21: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Les suppressionsCodeErreur supprimer_dernier(Universite

*u){ // Très rapide!

if(u->n==0) return Erreur;u->n--;if(u->n<=u->nb_alloues/2){ // On compacte le tableau

Etudiant *e = realloc(u->etudiants,sizeof(Etudiant)*(u->n));

if(!e) return Erreur;u->etudiants = e;u->nb_alloues = u->n;

}return OK;

}

CodeErreur supprimer_premier(Universite *u)

{ // Beaucoup plus long!int i;if(u->n==0) return Erreur;for(i=0;i<u->n-1;i++)

u->etudiants[i]=u->etudiants[i+1];return supprimer_dernier(u);

}

Page 22: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Les suppressions• Dans un tableau tel qu’on l’a implanté jusqu’à maintenant, qu’il

soit dynamique ou statique, la suppression du premier élément est beaucoup plus lente que la suppression du dernier, puisqu’on doit décaler tous les éléments.

• On pourrait croire que pour supprimer au début, il suffirait d’incrémenter le pointeur du tableau, mais cela ne fonctionnerait pas car nous perdrions l’adresse-mémoire du début du tableau qui est nécessaire aux appels à « realloc » ou à « free ». En effet, l’adresse-mémoire du début d’un bloc-mémoire alloué dynamiquement fait office d’identifiant de ce bloc-mémoire.

• On peut par contre déclarer un troisième nombre entier dans la structure, celui-ci permettant de faire varier le début logique du tableau. Il faut alors ajuster toutes les fonctions en conséquence.

Page 23: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Optimisation de la suppression du premier élément grâce à un début variable.

typedef struct{

Etudiant *etudiants;int n,nb_alloues,debut;

} Universite;CodeErreur initUniversite(Universite *u){

u->etudiants=0;u->n=0;u->nb_alloues=0;u->debut=0;return OK;

}CodeErreur ajoutUniversite(Universite *u, Etudiant et){

if(u->debut+u->n==u->nb_alloues){

Etudiant *e = realloc(u->etudiants,sizeof(Etudiant)*(u->debut+u->n+1)*2);

if(!e) return Erreur;u->etudiants = e;u->nb_alloues=(u->n+1)*2;

}u->etudiants[u->debut+u->n++] = et;return OK;

}

CodeErreur compacterUniversite(Universite *u){ // Puisqu’on doit utiliser ce code dans deux fonctions, il vaut mieux en faire

une fonction utilitaireif(u->debut+u->n<=u->nb_alloues/2){

Etudiant *e = realloc(u->etudiants,sizeof(Etudiant)*(u->debut+u->n));

if(!e) return Erreur;u->etudiants = e;u->nb_alloues = u->debut+u->n;

}return OK;

}CodeErreur supprimer_premier(Universite *u, Etudiant et){

if(u->n==0) return Erreur;u->debut++;u->n--;return compacterUniversite(u);

}CodeErreur supprimer_dernier(Universite *u, Etudiant et){

if(u->n==0) return Erreur;u->n--;return compacterUniversite(u);

}

Page 24: IFT-2000: Structures de données Tableaux Dominic Genest, 2009

Dominic Genest, 2009

Tableaux circulaires

• Si on fait varier le début, on peut se retrouver à gaspiller un espace important avant le début.

• On peut utiliser cet espace en s’organisant pour que toutes nos fonctions n’utilisent plus simplement l’indice « u->debut+u->n », mais plutôt « (u->debut+u->n)%u->nb_alloues » (rappel: le symbole « % » veut dire « modulo », soit « reste de la division entière »).

• L’implémentation de l’exemple précédent avec cela est laissée en exercice (remarque: ceci implique qu’il faut réorganiser les éléments lors de l’agrandissement ou de la compaction du tableau).