Académique Documents
Professionnel Documents
Culture Documents
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 *));
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
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 }
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
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;
};
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.
#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
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);
}
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
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> :
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);
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
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);
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;
}
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
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);