Mise en œuvre du TAD `set` à l'aide d'un tableau trié de capacité variable
Notre première mise en œuvre du type set repose sur un tableau d’entiers de capacité SET__SIZE, fixée à la compilation. Nous souhaitons réaliser une deuxième mise en œuvre où la capacité du tableau varie dynamiquement à l’exécution du progamme, en fonction de la taille de l’ensemble à représenter.
Pour cela, il est nécessaire que le tableau d’entiers dans struct set soit alloué dynamiquement. La bibliothèque standard stdlib.h fournit trois fonctions décrites en annexe.
Dans cette seconde mise en œuvre de set, la définition de struct set devient:
struct set {
int * s;
size_t capacity;
size_t size;
};
où s pointe sur un tableau d’entiers alloué dynamiquement, capacity est le nombre d’entiers alloués dans la zone mémoire pointée par s et size est le nombre d’entiers stockés dans le tableau s. Cette mise en œuvre stocke la taille de l’ensemble, il n’est donc pas nécessaire d’utiliser une butée.
Allocation de la mémoire
Afin de valider les tests automatiques sur thor, l’implémentation d’un tableau trié à capacité variable doit vérifier les propriétés suivantes:
- l’ensemble vide est tel que
.size=0,.capacity=0, et.s=NULL - si, avant d’ajouter un élément dans le tableau, celui-ci était tel que
(size == capacity), alors on réalloue le tableau de manière à doubler sa capacité si elle était non nulle, sinon fixer cette capacité à 1. - si, après avoir retiré un élément dans le tableau, celui-ci était tel que
(size <= capacity/2), alors on réalloue le tableau de manière à ce que sa capacité deviennecapacity/2(et donc vaut 0 sicapacityvalait 1). - Invariant: le champ
capacityest toujours représentatif de la taille mémoire allouée pour le tableau pointé pars
Dans l’exemple suivant, on affiche les tailles, capacités, et contenus du tableau lors de l’ajout successif de 5 éléments, puis le retrait successif de ces 5 éléments:
{ .size = 0, .capacity = 0, .s = {} } // s is the NULL pointer
{ .size = 1, .capacity = 1, .s = {1} }
{ .size = 2, .capacity = 2, .s = {1,2} }
{ .size = 3, .capacity = 4, .s = {1,2,3} }
{ .size = 4, .capacity = 4, .s = {1,2,3,4} }
{ .size = 5, .capacity = 8, .s = {1,2,3,4,5} }
{ .size = 4, .capacity = 4, .s = {2,3,4,5} }
{ .size = 3, .capacity = 4, .s = {3,4,5} }
{ .size = 2, .capacity = 2, .s = {4,5} }
{ .size = 1, .capacity = 1, .s = {5} }
{ .size = 0, .capacity = 0, .s = {} } // s is the NULL pointer
- Justifier le stockage de la taille de l’ensemble (
size) au lieu de l’utilisation d’une butée comme précédemment.
Algorithmes d’allocation
- Donner un algorithme prenant en compte l’allocation mémoire dans
set__addqui double la capacité deslorsqu’il est plein. - Donner un algorithme prenant en compte l’allocation mémoire dans
set__removequi divise par 2 la capacité desquand il est à moitié vide.
Libération de la mémoire
Pour que cette mise en œuvre gère la mémoire de façon satisfaisante, la mémoire allouée dynamiquement pour une struct set doit être libérée explicitement par le programme. Il faut donc mettre en œuvre la fonction:
void set__free(struct set * se);
- Pourquoi cette fonction n’apparaît-elle pas dans le TAD?
- Expliquer ce qu’il est nécessaire de libérer dans la mise en œuvre du tableau dynamique et donner le code de la fonction
void set__free().
Mise en œuvre
Comme pour le tableau trié à sentinelle, cette mise en œuvre va se baser sur 3 fonctions outils:
// returns the first position p in s such that s[p]>=c
// or size if no such p exists
size_t find(int const s[], size_t size, int c);
// moves elements in s[begin..end[ to s[begin+1..end+1[
// assuming all indices are valid, s[begin] is left unchanged
void shift_right(int s[], size_t begin, size_t end);
// moves elements in s[begin..end[ tp [begin-1..end-1[
// assuming all indices are valid, s[end-1] is left unchanged
void shift_left(int s[], size_t begin, size_t end);
- Écrire le fichier en-tête
dynamic/set_dynamic.hqui contient la définition de la structurestruct set, ainsi que les prototypes des fonctions outilsfind,shift_leftetshift_rightci-dessus. - Programmer les trois fonctions outils, puis celles du type
set(définies dansset/set.h) en utilisant les fonctions outils, dans le fichierdynamic/set_dynamic.c.
Test structurels
Écrire les tests structurels dans le fichier dynamic/test_dynamic_struc.c. En particulier, veuillez à tester les trois fonctions outils find, shift_left et shift_right de manière exhaustive.
Annexe
La bibliothèque standard stdlib.h fournit trois fonctions pour la gestion dynamique de la mémoire:
void * malloc(size_t size);alloue (au moins)sizeoctets consécutifs dans le tas. Elle retourne l’adresse du 1er octet alloué, ouNULLsi l’allocation a échoué;void * realloc(void * ptr, size_t size);allouesizeoctets consécutifs dans le tas, si possible à partir de l’adresseptr. Plus précisément, la fonctionrealloctente de modifier la taille du bloc précédemment alloué pointé parptr. Si ce n’est pas possible, la fonctionreallocallouesizeoctets à une autre adresse, puis elle recopie le contenu pointé parptrà cette nouvelle adresse, et enfin elle libère le bloc pointé parptr. Elle retourne l’adresse du 1er octet alloué (qui peut donc êtreptrou pas). Elle renvoieNULLsi la réallocation a échoué. Attention, sisizevaut0, le comportement dereallocest non spécifié.void free(void * ptr);libère le bloc pointé parptr. Celui-ci doit avoir été alloué parmallocourealloc.
Une documentation complète est fournie par les pages de manuel malloc(3), realloc(3) et free(3). Alors que la mémoire allouée sur la pile est gérée automatiquement, toute mémoire allouée dynamiquement doit être libérée par le programme.