Vous êtes sur la page 1sur 21

Université de Rouen Normandie Licence Informatique 2e année

UFR Sciences et Techniques 2022-2023

Algorithmique 2 : structures de données linéaires


Contrôle continu no 1
Date : 17 octobre 2022. Durée : 1 h 30. Documents, casques audio, oreillettes, calculatrices,
mobiles, tablettes, portables, objets connectés... interdits.
Chacune des questions des exercices 1 et 2 attend en réponse la ou les définitions en C d’une
ou plusieurs fonctions. En aucun cas ces définitions ne devront comporter de boucles ou nécessiter
la ou les définitions d’autres fonctions qui en comporteraient. Les définitions des fonctions devront
être récursives ou, à défaut, faire appel à une ou des fonctions auxiliaires dont les définitions seront
récursives. Pour obtenir la totalité des points indiqués dans le barème, toute fonction récursive
devra pouvoir être dérécursifiée de manière automatique (il n’est pas demandé de dérécursifier les
fonctions) et toute fonction auxiliaire doit être spécifiée.
Exercice 1 (2 + 2 points.)
Programmez en C les fonctions sur les chaines et les tableaux de caractères :
// str_prefcmp : renvoie un entier strictement inférieur, égal ou strictement
// supérieur à zéro selon que la chaine de caractères pointée par s1 est un
// préfixe propre de, est égale à ou n’est pas un préfixe de celle pointée
// par s2.
int str_prefcmp(const char *s1, const char *s2);

// mem_reverse : renverse le tableau de caractères de longueur n pointé par s.


void mem_reverse(void *s, size_t n);

Rappel : un mot u est un préfixe propre d’un mot v lorsque u est un préfixe de v mais que u
est différent de v .
Remarque : l’entier renvoyé par str_prefcmp est inférieur ou égal à zéro lorsque la chaine
pointée par s1 est un préfixe (au sens large) de s2.
Exercice 2 (2 + 4 points.)
Programmez en C les fonctions polymorphes sur les tableaux :
// is_palindrome : renvoie true ou false selon que la suite des composants d’un
// tableau est un palindrome ou non. Le premier composant du tableau est
// pointé par base, la longueur du tableau vaut nmemb, la taille des
// composants vaut size, la fonction de comparaison des composants est
// pointée par compar.
bool is_palindrome(const void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));

// select_min : renvoie l’adresse de la première occurrence du minimum des


// composants d’un tableau si la longueur du tableau n’est pas nulle, NULL
// sinon. Le premier composant du tableau est pointé par base, la longueur du
// tableau vaut nmemb, la taille des composants vaut size, la fonction de
// comparaison des composants est pointée par compar.
void *select_min(const void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 1 | 2

Exercice 3 (2 + 4 points.)
Soit fibone la fonction C définie par :
unsigned int fibone(unsigned int n) {
if (n <= 1) {
return 1;
}
return fibone(fibone(n - 1) - fibone(n - 2));
}

1) Montrez que, sauf à ce que se produise un débordement de pile, tout appel à fibone se
termine et que fibone renvoie l’entier 1.
2) Établissez le nombre d’appels récursifs r (n) à fibone générés par un appel initial avec n
comme argument, sans supposer que la fonction ait pu être dérécursifiée, même partiellement :
a) commencez par formuler la récurrence définissant r (n) en la justifiant ;
b) énoncez ensuite une forme close de cette récurrence, à exprimer, sans surprise, à l’aide des
nombres Fibonacci : F0 = 0, F1 = 1 et, pour n ⩾ 2, Fn = Fn−1 + Fn−2 ;
c) prouvez enfin que ce que vous venez d’énoncer est correct.
Exercice 4 (2 + 2 points.)
On s’intéresse ici à un cas de procédures qui ne peuvent être directement dérécursifiées par les
divers énoncés qui figurent dans le cours. Les procédures en question sont bien récursives terminales
mais avec l’appel récursif « coincé » dans une instruction conditionnelle. Autrement dit, elles ont
pour forme :
P (x)
A0
si C alors
A1
P (r )
en reprenant les conventions littérales du cours.
1) Procédez par équivalences pour aboutir à une forme P ′ (x) équivalente à P (x) dans ses
effets mais débarrassée de l’appel récursif.
2) Appliquez votre résultat aux procédures de la forme
P (x)
A0
si C0 alors
A1
si C1 alors
A
P (r )
sinon
A′
P (r ′ )
Algorithmique 2
3 | SOLUTIONS DES EXERCICES 2022-2023

Solutions des exercices


Solution à l’exercice 1
1
2 int str_prefcmp(const char *s1, const char *s2) {
3 if (*s1 == ’\0’) {
4 return *s2 == ’\0’ ? 0 : -1;
5 }
6 if (*s2 == ’\0’) fprintf(stderr, "$");
7 if (*s1 != *s2) {
8 if (*s2 == ’\0’) fprintf(stderr, "@");
9 return 1;

1 return str_prefcmp(s1 + 1, s2 + 1);


2 }
3
4 void mem_reverse(void *s, size_t n) {
5 if (n <= 1) {
6 return;
7 }
8 char t = ((char *) s)[0];
9 ((char *) s)[0] = ((char *) s)[n - 1];

Solution à l’exercice 2
1 bool is_palindrome(const void *base, size_t nmemb, size_t size,
2 int (*compar)(const void *, const void *)) {
3 if (nmemb <= 1) {
4 return true;
5 }
6 if (compar(base, (const char *) base + (nmemb - 1) * size) != 0) {
7 return false;
8 }
9 return is_palindrome((const char *) base + size, nmemb - 2, size, compar);
10 }

1 // select_min_aux : renvoie l’adresse de la première occurrence du minimum des


2 // composants d’un tableau si ce minimum est strictement inférieur à un
3 // objet d’adresse donnée, l’adresse de cet objet sinon. Si la longueur du
4 // tableau est nulle, renvoie l’adresse de l’objet. L’adresse de l’objet est
5 // key, le premier composant du tableau est pointé par base, la longueur du
6 // tableau vaut nmemb, la taille des composants et de l’objet vaut size, la
7 // fonction de comparaison des composants et de l’objet est pointée par
8 // compar.
9 void *select_min_aux(const void *key, const void *base, size_t nmemb,
10 size_t size, int (*compar)(const void *, const void *)) {
11 if (nmemb == 0) {
12 return (void *) key;
13 }
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 1 | 4

14 if (compar(key, base) > 0) {


15 key = base;
16 }
17 return select_min_aux(key, (const char *) base + size, nmemb - 1, size,
18 compar);
19 }
20
21 void *select_min(const void *base, size_t nmemb, size_t size,
22 int (*compar)(const void *, const void *)) {
23 if (nmemb == 0) {
24 return NULL;
25 }
26 return select_min_aux(base, (const char *) base + size, nmemb - 1, size,
27 compar);
28 }

Solution à l’exercice 3
1) On établit la propriété par récurrence sur n.
Base : n ⩽ 1. L’appel se termine et renvoie 1.
Induction : n ⩾ 2. Dans ce cas, 0 ⩽ n − 2 < n − 1 < n. L’hypothèse de récurrence s’applique
donc à la fois à n − 1 et à n − 2 : les appels imbriqués fibone(n − 1) et fibone(n − 2) se terminent
et renvoient 1. La différence des valeurs renvoyées par ces deux appels vaut donc 0. C’est donc 0
qui est passé en argument à l’appel le plus externe à fibone. Il s’ensuit que l’appel le plus externe
se termine et a pour valeur 1, puis que l’appel fibone(n) se termine et renvoie 1.
2)
a) On a r (n) = 0 pour n = 0 et pour n = 1. Sinon, pour n ⩾ 2, on a
r (n) = 1 + r (n − 1), pour l’appel interne fibone(n − 1),
+ 1 + r (n − 2), pour l’appel interne fibone(n − 2),
+ 1 + r (fibone(n − 1) − fibone(n − 2)), pour l’appel le plus externe à fibone.
Or :
r (fibone(n − 1) − fibone(n − 2)) = r (0), d’après le résultat à la question précédente,
= 0, d’après le constat posé plus haut.
Il s’ensuit que

0, si n ⩽ 1 ;
r (n) =
3 + r (n − 1) + r (n − 2), sinon.
b) La forme close pour la récurrence précédente est
r (n) = 3Fn+1 − 3.
c) Montrons-le par récurrence sur n.
Base : n ⩽ 1. Si n = 0, on a r (n) = r (0) = 0 et 3Fn+1 − 3 = 3F1 − 3 = 3 · 1 − 3 = 0.
Si n = 1, on a r (n) = r (1) = 0 et 3Fn+1 − 3 = 3F2 − 3 = 3 · 1 − 3 = 0.
Induction : n ⩾ 2. Il vient :
r (n) = 3 + r (n − 1) + r (n − 2), par définition de r (n),
= 3 + (3Fn − 3) + (3Fn−1 − 3), par application de l’hypothèse de récurrence,
= 3(Fn + Fn−1 ) − 3
= 3Fn+1 − 3.
Ce qui achève la preuve.
Algorithmique 2
5 | SOLUTIONS DES EXERCICES 2022-2023

Solution à l’exercice 4
1) La procédure P peut être réécrite comme suit :
P (x)
A0
si ¬C alors
ne rien faire
sinon
A1
P (r )
Par application de la proposition du cours, la procédure P ′ définie par :
P ′ (x)
A0
tant que ¬¬C faire
A1
x ← r
A0
ne rien faire
est équivalente dans ses effets à P . Elle se réécrit plus intelligiblement en :
P ′ (x)
A0
tant que C faire
A1
x ← r
A0
2) La procédure P peut être réécrite comme suit :
P (x)
A0
si C0 alors
A1
si C1 alors
A
x ← r
sinon
A′
x ← r′
P (x)
D’où, par application du résultat précédent et en s’abstenant de faire figurer l’inutile affectation
« x ← x », sa procédure équivalente dans ses effets P ′ :
P ′ (x)
A0
tant que C0 faire
A1
si C1 alors
A
x ← r
sinon
A′
x ← r′
A0
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 1 | 6

Solution alternative à boucle « tant que VRAI faire ».


1) La procédure P peut être réécrite comme suit :
P (x)
A0
si ¬C alors
renvoyer
A1
P (r )
Par application du corolaire du cours, la procédure P ′ définie par :
P ′ (x)
tant que VRAI faire
A0
si ¬C alors
renvoyer
A1
x ← r
est équivalente dans ses effets à P .
2) La procédure P peut être réécrite comme suit :
P (x)
A0
si ¬C0 alors
renvoyer
A1
si C1 alors
A
x ← r
sinon
A′
x ← r′
P (x)
D’où, par application du résultat précédent et en s’abstenant de faire figurer l’inutile affectation
« x ← x », sa procédure équivalente dans ses effets P ′ :
P ′ (x)
tant que VRAI faire
A0
si ¬C0 alors
renvoyer
A1
si C1 alors
A
x ← r
sinon
A′
x ← r′
Université de Rouen Normandie Licence Informatique 2e année
UFR Sciences et Techniques 2022-2023

Algorithmique 2 : structures de données linéaires


Contrôle continu no 2
Date : 14 novembre 2022. Durée : 1 h 30. Documents, casques audio, oreillettes, calculatrices,
mobiles, tablettes, portables, objets connectés... interdits.
Concernant le codage en C : des rappels figurent en fin de sujet ; toute fonction auxiliaire
doit être spécifiée ; seront considérées comme fautives toute mésécriture d’identificateur, toute
écriture de symbole (comme l’étoile ou l’esperluette) non conventionnelle, tout emploi inapproprié
d’un forceur de type (pour contrer le qualificatif de type const par exemple) ; aucune preuve de
correction des fonctions (invariant de boucle, quantité de contrôle, preuve par récurrence) n’est
attendue.
Pour rappel : logb x = ln x/ ln b ; ln x n = n ln x.
« To stutter » signifie « bégayer ».
Exercice 1 (3 points.)
Codez la fonction :
// str_stutter : range dans une zone mémoire allouée à cet effet la chaine de
// caractères obtenue en doublant chacun des caractères de la chaine de
// caractères pointée par s. Renvoie NULL en cas de dépassement de capacité.
// Renvoie sinon l’adresse de la zone allouée.
char *str_stutter(const char *s);

Exemple. La valeur renvoyée par la fonction str_stutter avec l’argument "AZERTY" devrait
raisonnablement être l’adresse d’une zone mémoire allouée pour l’occasion et de contenu la chaine
de caractères "AAZZEERRTTYY".
Exercice 2 (3 + 3 + 3 points.)
Soit type un type compatible avec les six opérateurs de comparaisons usuels : ==, <=, >=, !=,
< et >. Soit maintenant struct cell la structure définie par :

struct cell {
type value;
struct cell *next;
};

Codez les fonctions suivantes :


1)
// is_stuttered : renvoie true si la liste dynamique simplement chainée
// pointée par p est constituée de paires consécutives de cellules de valeurs
// égales (liste vide incluse). Renvoie false dans le cas contraire.
bool is_stuttered(const struct cell *p);

2)
// stutter : double chacune des cellules de la liste dynamique simplement
// chainée pointée par *pp. En cas de dépassement de capacité, renvoie le
// pointeur vers la première cellule de la liste qui n’a pu être doublée.
// Renvoie sinon NULL.
struct cell *stutter(struct cell **pp);
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 2 | 8

3)
// delete_one_in : sans effet si n vaut zéro. Supprime sinon une cellule sur n
// de la liste dynamique simplement chainée pointée par *pp en commençant par
// la première (autrement dit : supprime toutes les cellules dont l’indice
// est un multiple de n). Les cellules supprimées sont désallouées.
void delete_one_in(struct cell **pp, size_t n);

Exemples. Supposons que le mot ⟨a0 , a1 , . . ., an−1 ⟩ est implanté par la liste dynamique simple-
ment chainée repérée par p. Alors :
— si la fonction stutter est appelée avec l’argument &p et que la valeur qu’elle renvoie est
NULL, p repère désormais la liste qui implante le mot ⟨a0 , a0 , a1 , a1 , . . ., an−1 , an−1 ⟩ et la valeur de
is_sluttering(p) est true ;
— si la fonction delete_one_in est appelée avec les arguments &p et 2, p repère désormais
la liste qui implante le mot ⟨a1 , a3 , . . ., an−1−⌊⌈n impair⌋⌉⟩. Et si le deuxième argument de l’appel est 1,
p vaut NULL ; autrement dit p est associé à la liste vide.

Exercice 3 (2 + 2 points.)
1) Par la méthode de votre choix, résolvez la récurrence nun = (n + 1)un−1 + 1, pour n ⩾ 1,
avec u0 = 0. N’omettez pas de préciser la méthode.
2) Résolvez par la méthode du répertoire la récurrence (n − 1)un = nun−1 + 1, pour n ⩾ 2,
avec u0 = ln(π/e) et u1 = 1. Remarque : la récurrence s’ancre au rang 1 ; ce n’est donc pas la
colonne « u0 » qu’il faut faire figurer dans le répertoire mais la colonne « u1 ».
Exercice 4 (2 + 2 points.)
Par la proposition « récurrence de partition », résolvez, en identifiant les données, argumentant
puis identifiant les cas, les récurrences :
1) t(n) = 16t(n/4) + n ln n ;
2) t(n) = 3t(n/3) + n.
Rédigez vos solutions : les réponses limitées à « Cas x. Donc t(n) ∈ . . . » ne seront pas prises en
compte.

Macroconstantes et fonctions standards du C utiles :


#include <stdbool.h>
bool
true
false

#include <stddef.h>
size_t
NULL

#include <stdint.h>
SIZE_MAX

#include <stdlib.h>
void free(void *ptr);
void *malloc(size_t size);

#include <string.h>
size_t strlen(const char *s);
Algorithmique 2
9 | SOLUTIONS DES EXERCICES 2022-2023

Solutions des exercices


Solution à l’exercice 1
char *str_stutter(const char *s) {
size_t n = strlen(s);
if (n > (SIZE_MAX - 1) / 2) {
return NULL;
}
char *s2 = malloc(2 * n + 1);
if (s2 == NULL) {
return NULL;
}
char *p2 = s2;
for (const char *p = s; *p != ’\0’; ++p) {
*p2 = *p;
++p2;
*p2 = *p;
++p2;
}
*p2 = ’\0’;
return s2;
}

Solution à l’exercice 2
Les solutions proposées ci-dessous font appel à la programmation récursive. Des équivalentes
itératives s’obtiennent par dérécursification à l’aide des résultats du cours.
1)
bool is_stuttered(const struct cell *p) {
if (p == NULL) {
return true;
}
if (p->next == NULL || p->value != p->next->value) {
return false;
}
return is_stuttered(p->next->next);
}

2)
struct cell *stutter(struct cell **pp) {
if (*pp == NULL) {
return NULL;
}
struct cell *p = malloc(sizeof *p);
if (p == NULL) {
return *pp;
}
p->value = (*pp)->value;
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 2 | 10

p->next = *pp;
*pp = p;
return stutter(&p->next->next);
}
3)
// delete_one_in_aux : supprime toutes les cellules de la liste dynamique
// simplement chainée pointée par *pp dont l’indice est divisible par n.
// Si la liste n’est pas vide, k est l’indice de sa première cellule.
void delete_one_in_aux(struct cell **pp, size_t n, size_t k) {
if (*pp == NULL) {
return;
}
if (k % n == 0) {
struct cell *p = *pp;
*pp = p->next;
free(p);
} else {
pp = &(*pp)->next;
}
delete_one_in_aux(pp, n, k + 1);
}

void delete_one_in(struct cell **pp, size_t n) {


if (n == 0) {
return;
}
delete_one_in_aux(pp, n, 0);
}
Solution à l’exercice 3
1) nun = (n + 1)un−1 + 1, pour n ⩾ 1, avec u0 = 0.
Une première solution.
En réécrivant :
un = (n + 1)/n · un−1 + 1/n, pour n ⩾ 1,
puis en itérant, il vient :
u1 = 2/1 · 0 + 1/1 = 1 ;
u2 = 3/2 · 1 + 1/2 = 2 = 3/2 + 1/2 = 4/2 = 2 ;
u3 = 4/3 · 2 + 1/3 = 8/3 + 1/3 = 9/3 = 3 ;
u4 = 5/4 · 3 + 1/4 = 15/4 + 1/4 = 16/4 = 4.
D’où l’on pense comprendre que
un = n, pour tout naturel n.
Ce que l’on montre par une preuve par récurrence.
Base : n = 0. La propriété est satisfaite puisque u0 = 0.
Induction : n ⩾ 1. Par application de l’hypothèse de récurrence :
un = (n + 1)/n · un−1 + 1/n = (n + 1)/n · (n − 1) + 1/n = (n2 − 1)/n + 1/n = n2 /n = n.
La propriété est encore satisfaite. Ce qui achève la preuve.
Algorithmique 2
11 | SOLUTIONS DES EXERCICES 2022-2023

Une deuxième solution.


La récurrence est linéaire du premier ordre :
un = (n + 1)/n · un−1 + 1/n, pour n ⩾ 1,
Elle a donc pour solution explicite
n  n
 Y
X 1 j +1
un = ⌊⌈k = 0⌋⌉ · 0 + ⌊⌈k ̸= 0⌋⌉ · .
k j
k=0 j=k+1
Soit :
n
X 1 n+1
un = ·
k k +1
k=1
n
X 1
= (n + 1)
k(k + 1)
k=1
n
X 1  
1
= (n + 1) −
k k +1
k=1
 
1
= (n + 1) · 1 −
n+1
= (n + 1) − 1
= n.
Une troisième solution.
Par la méthode du répertoire :
un u0 nun − (n + 1)un−1 = f (n)
n 0 n · n − (n + 1) · (n − 1) = 1
Donc un = n.
2) (n − 1)un = nun−1 + 1, pour n ⩾ 1, avec u1 = 1.
un u1 (n − 1)un − nun−1 = f (n)
1 1 (n − 1) · 1 − n · 1 = −1
n 1 (n − 1) · n − n · (n − 1) = 0
En multipliant la deuxième ligne par 2 et en lui retranchant la première, il vient que un = 2 · n − 1
est solution avec f (n) = 2 · 0 − (−1) = 1 et u1 = 2 · 1 − 1 = 1. Donc
un = 2n − 1, pour n ⩾ 1.

Solution à l’exercice 4
1) t(n) = 16t(n/4) + n ln n.
On identifie : a = 16, b = 4 et f (n) = n ln n.
On calcule : logb a = log4 (16) = ln 16/ ln 4 = ln(42 )/ ln 4 = 2 ln 4/ ln 4 = 2.

Comme f (n) ∈ O(n n), f (n) ∈ O(nlogb a−ε ) avec ε = 0,5.
Il s’ensuit que le cas 1) s’applique.
Donc t(n) ∈ Θ(nlogb a ), soit t(n) ∈ Θ(n2 ).
2) t(n) = 3t(n/3) + n.
On identifie : a = 3, b = 3 et f (n) = n.
On calcule : logb a = log3 (3) = ln 3/ ln 3 = 1.
Comme f (n) ∈ Θ(n), f (n) ∈ Θ(nlogb a ).
Il s’ensuit que le cas 2) s’applique.
Donc t(n) ∈ Θ(nlogb a ln n), soit t(n) ∈ Θ(n ln n).
Université de Rouen Normandie Licence Informatique 2e année
UFR Sciences et Techniques 2022-2023

Algorithmique 2 : structures de données linéaires


Contrôle continu no 3
Date : 14 décembre 2022. Durée : 2 h. Documents, casques audio, oreillettes, calculatrices,
mobiles, tablettes, portables, objets connectés... interdits.
Toute copie se doit d’être écrite à l’encre indélébile et d’être agréable à lire et à corriger. Les
ratures sont autorisées ; une rature, c’est un trait droit qui barre de biais un mot ou un paragraphe ;
en aucun cas une mise entre parenthèses. Toute copie ou partie de copie écrite au crayon à papier
ou assimilable à un brouillon sera ignorée lors de l’évaluation.
Concernant le codage en C : seront ignorés lors de d’évaluation tout code non correctement
indenté ou à appariement d’accolades défectueux, toute fonction auxiliaire non spécifiée ; seront
considérées comme fautives toute mésécriture d’identificateur, toute écriture de symbole (comme
l’étoile ou l’esperluette) non conventionnelle, tout emploi inapproprié d’un forceur de type (pour
contrer le qualificatif de type const par exemple).
Exercice 1 (2 + 6 + 4 = 12 points.)
On rappelle ici une version de la spécification formelle LISTER des listes récursives définissant
le TDA ListeR(T ) sur le type arbitraire T :
spécification LISTER
définit ListeR(T )
opérations
vide : → ListeR(T )
insère-tête : ListeR(T ) × T → ListeR(T )
reste : ListeR(T ) ̸→ ListeR(T )
tête : ListeR(T ) ̸→ T
est-vide : ListeR(T ) → Booléen
longueur : ListeR(T ) → Naturel
axiomes
(1) est-vide(vide) = VRAI
(2) est-vide(insère-tête(s, x)) = FAUX
(3) reste(insère-tête(s, x)) = s
(4) tête(insère-tête(s, x)) = x
(5) longueur (vide) = 0
(6) longueur (insère-tête(s, x)) = 1 + longueur (s)
préconditions
reste(s), tête(s) : ¬est-vide(s)
Conventions. Dans les formules que vous aurez à écrire par la suite, n’hésitez pas à abrévier
les opérations. En suivant l’ordre dans lequel elles figurent ci-dessus : v , it(s, x), r (s), t(s), ev (s)
et l(s). N’hésitez pas non plus à utiliser des formes de parenthésage différenciées : paires plus ou
moins grandes, crochets, accolades. Par exemple :
  
it it it(v , a), b , c , it[it{it(v , a), b}, c].

1) Avec T = Naturel, calculez en justifiant chacune des étapes ou alors refusez de calculer en
justifiant :
a) longueur (reste(insère-tête(vide, 1))) ;
b) tête(reste(insère-tête(insère-tête(insère-tête(vide, 2), 3), 4))) ;
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 3 | 14

c) reste(insère-tête(reste(vide), 5)) ;
d) reste(insère-tête(insère-tête(reste(insère-tête(vide, 6)), 7), 8)).
2) Établissez les preuves que :
a) les formes canoniques du TDA ListeR(T ) sont les expressions de la forme
insère-tête(. . . insère-tête(insère-tête(vide, a0 ), a1 ) . . . , an−1 )
avec n ⩾ 0, ak ∈ T , pour 0 ⩽ k < n ;
b) pour tout naturel n, pour tous ak ∈ T , avec 0 ⩽ k < n,
longueur (insère-tête(. . . insère-tête(insère-tête(vide, a0 ), a1 ) . . . , an−1 )) = n ;
c) ¬est-vide(s) ⇒ longueur (reste(s)) = longueur (s) − 1.
3) Une liste ordonnée est une liste linéaire ordonnée selon ⩽T . Autrement dit, elle est ordonnée
et peut posséder des doublons, des éléments consécutifs égaux selon =T . Le TDA associé est noté
ListeO(T ).
Typiquement, deux opérations d’insertion et autant d’opérations de suppression sont définies
pour ce TDA :
— insère-avant : ListeO(T ) × T → ListeO(T ), où l’élément x donné en argument est inséré
immédiatement avant toute éventuelle occurrence d’un élément présent dans la liste égal à x ;
— insère-après : ListeO(T ) × T → ListeO(T ), où l’élément x donné en argument est inséré
immédiatement après toute éventuelle occurrence d’un élément présent dans la liste égal à x ;
— supprime-avant : ListeO(T ) × T → ListeO(T ), où l’éventuelle première occurrence d’un
élément de la liste égal à l’élément donné en argument est supprimée ;
— supprime-après : ListeO(T ) × T → ListeO(T ), où l’éventuelle dernière occurrence d’un
élément de la liste égal à l’élément donné en argument est supprimée.
Exemple informel. Avec l’ordre usuel sur les lettres, où les marques « prime » et « seconde »
ne modifient pas les valeurs des lettres et ne sont introduites que pour pouvoir les distinguer lors
des insertions et dans leurs placements :
liste antécédent opération liste image
⟨A, R, T, Z⟩ insertion « avant » ou « après » de E ⟨A, E, R, T, Z⟩
⟨A, E, R, T, Z⟩ insertion « avant » de E′ ⟨A, E′ , E, R, T, Z⟩
⟨A, E′ , E, R, T, Z⟩ insertion « après » de E′′ ⟨A, E′ , E, E′′ , R, T, Z⟩
⟨A, E′ , E, E′′ , R, T, Z⟩ suppression « avant » de E ⟨A, E, E′′ , R, T, Z⟩
⟨A, E, E′′ , R, T, Z⟩ suppression « avant » de E ⟨A, E′′ , R, T, Z⟩
⟨A, E′′ , R, T, Z⟩ suppression « avant » ou « après » de E ⟨A, R, T, Z⟩
⟨A, R, T, Z⟩ suppression « avant » ou « après » de Y ⟨A, R, T, Z⟩
Exprimez les axiomes pour ces quatre opérations de manière récursive en faisant appel aux
opérations du TDA ListeR(T ).
Indication : pour chacune des opérations, un axiome utilisant l’alternative fonctionnelle poly-
morphe si _ alors _ sinon _ une ou deux fois suffit.
Abréviations possibles : iav (s, x), iap(s, x), sav (s, x) et sap(s, x).

Exercice 2 (6 + 4 = 10 points.)
On cherche à développer des outils C de passage d’une liste dynamique simplement chainée
(LDSC) à un tableau et réciproquement. Plus précisément, on veut pouvoir :
— à partir d’une LDSC, allouer un tableau pour y ranger les valeurs contenues dans un champ
particulier des cellules de la liste ;
— à partir d’un tableau, créer une LDSC en donnant à un champ particulier des cellules de la
liste les valeurs des composants du tableau.
Algorithmique 2
15 | CONTRÔLE CONTINU No 3 2022-2023

Dans un premier temps, partie 1) de l’exercice, le champ à copier et son type sont fixés. Dans un
deuxième temps, partie 2), une générification est proposée.
Le codage de l’ensemble des fonctions et macrofonctions qu’il est demandé d’implanter par la
suite nécessite le recours à la macroconstante SIZE_MAX de l’en-tête standard <stdint.h> et aux
fonctions malloc et free de l’en-tête standard <stdlib.h> :
void free(void *ptr);
void *malloc(size_t size);

Pour les copies des champs, il est conseillé de se tourner vers la fonction memcpy l’en-tête standard
<string.h> :

void *memcpy(void *s1, const void *s2, size_t n);

laquelle copie à partir de l’adresse s1 les n (premiers) caractères de la zone mémoire pointée par s2
puis renvoie la valeur de s1. Pour la partie 2) de l’exercice, vous pourrez utiliser la macrofonction
non standard sizeof_memb :
sizeof_memb(structure-designator , identifier-of-member ) : expression du type size_t qui a pour
valeur la taille du champ d’identificateur désigné par identifier-of-member appartenant à la
structure désignée par structure-designator .
Pour simplifier — mais aussi parce que l’hypothèse est plus que raisonnable —, il est supposé
que la longueur des LDSC est codable sur le type size_t.
1) Soit type un type ou le nom d’un type. Soit struct cell un type qui possède au moins
comme composants les champs info du type type et next du type struct cell *.
Implantez les deux fonctions dont les prototypes sont :
type *sll_to_array(const struct cell *head, size_t *nmembptr);
struct cell *sll_from_array(const type *base, size_t nmemb);

et dont les spécifications sont :


sll_to_array : si la LDSC pointée par head est vide, affecte à *nmembptr la valeur zéro et renvoie
NULL. Tente sinon d’allouer un tableau de type de longueur égale à celle de la liste. En cas
d’échec, affecte à *nmembptr la valeur (size_t) (-1) et renvoie NULL. En cas de succès,
recopie séquentiellement dans le tableau alloué les valeurs du champ info de la liste, affecte à
*nmembptr la longueur de la liste puis renvoie l’adresse du tableau.
sll_from_array : tente de créer une LDSC de cellules de type struct cell et d’y recopier séquen-
tiellement dans le champ info les nmemb (premiers) composants du tableau pointé par base. En
cas de succès, renvoie le pointeur de tête de la liste. En cas d’échec, restitue l’espace mémoire
qui a éventuellement été alloué puis renvoie NULL.
2) Donnez l’expression des deux macrofonctions dont les identificateurs sont SLL_TO_ARRAY
et SLL_FROM_ARRAY, dont la liste des paramètres est :
structdes, idmembnext, idmembcopy, sllhead, arraybase, arraynmemb

avec, comme spécification commune portant sur les quatre premiers paramètres :
— structdes : paramètre désignant une structure ;
— idmembnext et idmembcopy : identificateurs de deux champs du type désigné par structdes ;
— idmembnext : du type structdes * ;
— idmembnext : champ capable d’assurer le chainage de toute LDSC de cellules du type
structdes ;
— sllhead : du type structdes * ;
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 3 | 16

puis, comme spécifications particulières :


SLL_TO_ARRAY :
Il est supposé en outre que : sllhead a pour valeur le pointeur de tête d’une LDSC
de cellules du type désigné par structdes ; arraybase est une variable pointeur vers un type
quelconque ; arraynmemb est une variable du type size_t.
Si la liste pointée par sllhead est vide, affecte à arraybase la valeur NULL et à arraynmemb
la valeur zéro. Tente sinon d’allouer un tableau de composants de la taille du champ idmembcopy
et de longueur égale à celle de la liste ; en cas d’échec, affecte à arraybase la valeur NULL
et à arraynmemb la valeur (size_t) (-1) ; en cas de succès, recopie séquentiellement dans le
tableau alloué les valeurs du champ idmembcopy de la liste, affecte à arraybase l’adresse du
tableau et à arraynmemb la longueur de la liste.
SLL FROM_ARRAY :
_
Il est supposé en outre que : arraybase est l’adresse d’un tableau dont la taille des
composants est égale à celle du champ idmembcopy et dont la longueur est (supérieure ou)
égale à arraynmemb.
Tente de créer une LDSC de cellules du type structdes et d’y recopier séquentiellement
dans le champ idmembcopy les arraynmemb (premiers) composants du tableau, le chainage de
la liste étant assuré par le champ idmembnext. En cas de succès, affecte à sllhead la valeur
du pointeur de tête de la liste. En cas d’échec, restitue l’espace mémoire qui aurait pu être
alloué et affecte à sllhead la valeur NULL.
Indication. La première expression attendue débute par
#define SLL_TO_ARRAY(structdes, idmembnext, idmembcopy, \
sllhead, arraybase, arraynmemb) \

Exemples pour les parties 1) et 2) de l’exercice. Avec h du type struct cell *, pointeur de
tête d’une LDSC donnée, a du type type * et n du type size_t, l’instruction
SLL_TO_ARRAY(struct cell, next, info, h, a, n);

est équivalente à l’instruction


a = sll_to_array(h, &n);

Si a est un tableau de type et que h est du type struct cell *, l’instruction


SLL_FROM_ARRAY(struct cell, next, info, h, a, sizeof a / sizeof *a);

est équivalente à l’instruction


h = sll_from_array((const type *) a, sizeof a / sizeof *a);
Algorithmique 2
17 | SOLUTIONS DES EXERCICES 2022-2023

Solutions des exercices


Solution à l’exercice 1
1)
a)
longueur (reste(insère-tête(vide, 1)))
= longueur (vide) par (LISTER.3)
= 0. par (LISTER.5)
b)
tête(reste(insère-tête(insère-tête(insère-tête(vide, 2), 3), 4)))
= tête(insère-tête(insère-tête(vide, 2), 3)) par (LISTER.3)
= 3. par (LISTER.4)
c) Pour reste(insère-tête(reste(vide), 5)), le calcul est impossible : l’expression « reste(vide) »
est interdite.
d)
reste(insère-tête(insère-tête(reste(insère-tête(vide, 6)), 7), 8))
= insère-tête(reste(insère-tête(vide, 6)), 7) par (LISTER.3)
= insère-tête(vide, 7). par (LISTER.3)
2) Le 2a) est une reprise de la proposition qui figure à la page [92] du support du cours et
d’exercices. Le 2c) provient de l’exercice 8.2 du même support, et donc du théorème qui suit la
proposition. Sauf à passer du lexique des piles à celui des listes récursives. Quant au 2b), il s’agit
d’une étape de la preuve du 2c).
a) Voir page [92] du support de cours et d’exercices.
b) Montrons par récurrence sur n que, pour tous ak ∈ T , avec 0 ⩽ k < n,
longueur (insère-tête(. . . insère-tête(vide, a0 ), . . . , an−1 )) = n.
Base : n = 0. On a :
longueur (insère-tête(. . . insère-tête(vide, a0 ), . . . , an−1 ))
= longueur (vide) puisque n = 0
=0 par (LISTER.5)
= n.
Induction : n ⩾ 1. On a :
longueur (insère-tête(. . . insère-tête(vide, a0 ), . . . , an−1 ))
= 1 + longueur (insère-tête(. . . insère-tête(vide, a0 ), . . . , an−2 )) puisque n ⩾ 1 et par
(LISTER.6)
= 1 + (n − 1) par application de l’hypo-
thèse de récurrence
= n.
Ce qui achève la preuve.
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 3 | 18

c) Il suffit d’établir la propriété pour toute forme canonique


s = insère-tête(. . . insère-tête(vide, a0 ), . . . , an−1 )
avec n ⩾ 1 :
longueur (reste(s))
= longueur (reste(insère-tête(. . . insère-tête(vide, a0 ), . . . , an−1 )))
= longueur (insère-tête(. . . insère-tête(vide, a0 ), . . . , an−2 )) puisque n ⩾ 1 et par
(LISTER.3)
=n−1 d’après 2b)
= longueur (s) − 1.
3)
insère-avant(s, x) =
si est-vide(s) ∨ x ⩽T tête(s) alors
insère-tête(s, x)
sinon
insère-tête(insère-avant(reste(s), x), tête(s))
insère-après(s, x) =
si est-vide(s)
∨ x <T tête(s) alors
insère-tête(s, x)
sinon
insère-tête(insère-après(reste(s), x), tête(s))
supprime-avant(s, x) =
si est-vide(s) alors
s
sinon
si x =T tête(s) alors
reste(s)
sinon
insère-tête(supprime-avant(reste(s), x), tête(s))
supprime-après(s, x) =
si est-vide(s) alors
s
sinon
si x =T tête(s)
∧ (est-vide(reste(s)) ∨ x ̸=T tête(reste(s))) alors
reste(s)
sinon
insère-tête(supprime-après(reste(s), x), tête(s))
Algorithmique 2
19 | SOLUTIONS DES EXERCICES 2022-2023

Solution à l’exercice 2
1)
type *sll_to_array(const struct cell *head, size_t *nmembptr) {
size_t n = 0;
for (const struct cell *p = head; p != NULL; p = p->next) {
++n;
}
if (n == 0) {
*nmembptr = 0;
return NULL;
}
type *a = (n > SIZE_MAX / sizeof(type) ? NULL : malloc(n * sizeof(type)));
if (a == NULL) {
*nmembptr = (size_t) (-1);
return NULL;
}
type *q = a;
for (const struct cell *p = head; p != NULL; p = p->next) {
memcpy(q, &p->info, sizeof(type));
++q;
}
*nmembptr = n;
return a;
}

struct cell *sll_from_array(const type *base, size_t nmemb) {


struct cell *h;
struct cell **pp = &h;
size_t k = 0;
while (k < nmemb && (*pp = malloc(sizeof(struct cell))) != NULL) {
memcpy(&(*pp)->info, base + k, sizeof(type));
++k;
pp = &(*pp)->next;
}
*pp = NULL;
if (k < nmemb) {
struct cell *p = h;
while (p != NULL) {
struct cell *t = p;
p = p->next;
free(t);
}
h = NULL;
}
return h;
}
Algorithmique 2
2022-2023 CONTRÔLE CONTINU No 3 | 20

2)
#define SLL_TO_ARRAY(structdes, idmembnext, idmembcopy, \
sllhead, arraybase, arraynmemb) \
{ \
size_t n = 0; \
for (const structdes *p = sllhead; p != NULL; p = p->idmembnext) { \
++n; \
} \
if (n == 0) { \
arraybase = NULL; \
arraynmemb = 0; \
} else { \
size_t s = sizeof_memb(structdes, idmembcopy); \
arraybase = (n > SIZE_MAX / s ? NULL : malloc(n * s)); \
if (arraybase == NULL) { \
arraynmemb = (size_t) (-1); \
} else { \
char *q = (char *) arraybase; \
for (const structdes *p = sllhead; p != NULL; p = p->idmembnext) { \
memcpy(q, &p->idmembcopy, s); \
q += s; \
} \
arraynmemb = n; \
} \
} \
}
#define SLL_FROM_ARRAY(structdes, idmembnext, idmembcopy, \
sllhead, arraybase, arraynmemb) \
{ \
size_t s = sizeof_memb(structdes, idmembcopy); \
size_t k = 0; \
structdes **pp = &(sllhead); \
while (k < arraynmemb && (*pp = malloc(sizeof(structdes))) != NULL) { \
memcpy(&(*pp)->idmembcopy, (const char *) (arraybase) + k * s, s); \
++k; \
pp = &(*pp)->idmembnext; \
} \
*pp = NULL; \
if (k < arraynmemb) { \
structdes *p = sllhead; \
while (p != NULL) { \
structdes *t = p; \
p = p->idmembnext; \
free(t); \
} \
sllhead = NULL; \
} \
}
Algorithmique 2
21 | SOLUTIONS DES EXERCICES 2022-2023

À titre indicatif, la macrofonction sizeof_memb peut être définie par :


#define sizeof_memb(structdes, idmemb) \
sizeof(((structdes *) 0)->idmemb)

On peut toutefois ne pas passer par elle dans les définitions des deux macrofonctions. Il suffit d’y
remplacer les instructions
size_t s = sizeof_memb(structdes, idmembcopy);

par
structdes x;
size_t s = sizeof(x.idmembcopy);

Vous aimerez peut-être aussi