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 sicapacity
valait 1). - Invariant: le champ
capacity
est 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__add
qui double la capacité des
lorsqu’il est plein. - Donner un algorithme prenant en compte l’allocation mémoire dans
set__remove
qui divise par 2 la capacité des
quand 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.h
qui contient la définition de la structurestruct set
, ainsi que les prototypes des fonctions outilsfind
,shift_left
etshift_right
ci-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)size
octets consécutifs dans le tas. Elle retourne l’adresse du 1er octet alloué, ouNULL
si l’allocation a échoué;void * realloc(void * ptr, size_t size);
allouesize
octets consécutifs dans le tas, si possible à partir de l’adresseptr
. Plus précisément, la fonctionrealloc
tente de modifier la taille du bloc précédemment alloué pointé parptr
. Si ce n’est pas possible, la fonctionrealloc
allouesize
octets à 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 êtreptr
ou pas). Elle renvoieNULL
si la réallocation a échoué. Attention, sisize
vaut0
, le comportement derealloc
est non spécifié.void free(void * ptr);
libère le bloc pointé parptr
. Celui-ci doit avoir été alloué parmalloc
ourealloc
.
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.