Vous êtes sur la page 1sur 12

Département Sciences et Technologies

pour l’Ingénieur

Algorithmique et Programmation 3
Recueil d’exercices

Année 2021-2022

Pascal Berthomé
pascal.berthome@insa-cvl.fr

Résumé
Ce recueil d’exercices servira de base pour cette année dans le module Algorith-
mique et Programmation 3 pour la partie algorithmique. Certains exercices seront vus
directement en cours, d’autres en TD. Enfin, certains ne seront pas directement traités.
Les exercices qui seront traités dans la partie langage seront mis sur Celene (ce
recueil aussi).
1 Algorithmique et pseudo-langage

Résumé
Avec cette série d’exercices, vous apprendrez à écrire des algorithmes en pseudo-
code, définir des fonctions et les utiliser dans d’autres fonctions ou algorithmes. Le
pseudo-code choisi est typé explicitement, ce qui oblige à bien définir les variables
avec leur type.
Pour les fonctions, il est important de définir les paramètres et leur mode de pas-
sage (IN, IN-OUT).

1. Écrire un algorithme qui demande deux nombres à l’utilisateur et l’informe ensuite si


le produit est strictement négatif, strictement positif, ou nul, sans calculer ce produit.
2. Déclarer la structure Triplet qui contient trois champs entiers nommés p, d, t pour
premier, deuxième et troisième, respectivement.
3. Écrire une fonction triTriplet qui ordonne par ordre croissant un triplet passé en en-
trée. On écrira deux versions, l’une qui crée et renvoie un nouveau triplet ordonné et
l’autre ordonnant le triplet passé en entrée.
4. On rappelle le principe de la méthode égyptienne pour calculer le résultat de la mul-
tiplication de deux entiers naturels ci-après. Cette méthode se base sur la propriété
suivante :  m
m∗n= 2 ∗ (2n ) si m est pair
(m − 1) ∗ n + n sinon.
On en déduit la marche à suivre : "si m est pair, on divise m par deux et multiplie n
par deux, sinon on retranche une unité à m et on ajoute n au résultat ; puis on recom-
mence jusqu’à ce que m soit nul.".
Écrire un aglorithme multiplication_égyptienne, qui demande deux nombres en-
tiers naturels et affiche leur produit calculé avec la méthode de la multiplication égyp-
tienne.
5. Écrire une fonction pgcd qui accepte en entrée deux nombres entiers naturels, et qui
calcule leur plus grand diviseur commun. On utilisera l’algorithme dit d’Euclide se
reposant sur la propriété suivante : le plus grand diviseur commun de a et b est le
même que le plus grand diviseur commun de b et du reste de la division euclidienne
de a par b.
En des termes plus succincts et formels, pgcd(a,b) = pgcd(b,r) avec r définit par la
division euclidienne : ∃!(q, r ) ∈ N2 , a = bq + r ∧ r < b).
6. Écrire une fonction trouverMin qui accepte en entrée un tableau d’entiers et renvoie
l’élément minimum du tableau.
7. Modifier la fonction précédente pour permettre de renvoyer à la fois le plus petit
élément du tableau et son indice.
8. On cherche à implémenter un tri par sélection sur un tableau d’entiers.
(a) En adaptant la fonction précédente, écrire la fonction echangerMinPartiel qui prend
en entrée
— un tableau d’entier t
— un entier i compris entre 1 et taille(t)
et qui
— cherche l’élément minimum dans la sous partie du tableau comprise après
l’indice i
— échange cet élément minimum avec le i-ème élément du tableau.
(b) Écrire une fonction permettant de trier un tableau d’entrée à l’aide de la fonction
echangerMinPartiel. Comment ? Imaginons que les k plus petits éléments d’un ta-
bleau sont triés par ordre croissant dans ses k premières cases. Que ce passe-t-il
s’il on appelle echangerMinPartiel sur ce tableau avec l’indice k + 1 ?
Un tel tri est dit tri sélection ou tri par sélection.
9. Dans les conventions de pseudo-code données, on utilise les mots-clés In et In-Out
pour décrire des inputs qui seront en lecture seule ou en lecture/écriture (ne seront
pas ou seront modifiés) dans la fonction, respectivement. Quel est l’équivalent de ce
concept en C ?
10. Un nombre entier positif est parfait s’il est non nul et égal à la moitié de la somme de
tous ses diviseurs positifs. Par exemple
— 6 est parfait car 6 = (1 + 2 + 3 + 6)/2.
— 28 est parfait car 28 = (1 + 2 + 4 + 7 + 14 + 28)/2.
(a) Écrire la fonction estParfait qui prend un paramètre entier et qui retourne un
booléen indiquant si son paramètre est un nombre parfait ou pas.
(b) Écrire la fonction nombresParfaits qui prend en paramètre un entier n et affiche
tous les nombres parfaits ≤ à n.
11. Le crible d’Eratosthène permet de déterminer les nombres premiers inférieurs à une
certaine valeur N. On place dans un tableau unidimensionnel les nombres compris
entre 1 et N. Pour déterminer les nombres premiers, on parcourt ce tableau. À chaque
itération, on marque tous les multiples du nombre courant (on remplace par 0 ou -1
par exemple). Une fois le parcours effectué, les nombres qui n’ont pas été marqués
sont premiers.
(a) Proposez un algorithme réalisant le crible d’Eratosthène en suivant les indica-
tions.
(b) Essayez d’optimiser votre algorithme en prenant en compte les nombres déjà
marqués (sauf si vous l’aviez déjà fait !).

AP3 - TD1 2 / 11 2021-2022


2 Correction, terminaison et complexité

Résumé
Cette suite d’exercices a pour ambition d’aborder les thématiques de la vérification
des algorithmes. En particulier, la correction vérifie que l’algorithme proposé résout
bien le problème posé ; la terminaison regarde si l’algorithme proposé se termine dans
tous les cas ; et la complexité indique le nombre d’unités de ressources utilisée par l’al-
gorithme. La complexité est classiquement évaluée en fonction de la taille de l’entrée
et peut mesurer le temps ou l’espace nécessaire.
Un petit détour sur les comparaisons de croissance des fonction sera proposé.

1. On rappelle que l’algorithme d’Euclide est basé sur la propriété suivante :

pgcd( a, b) = pgcd(b, a mod b).

La fonction suivante implémente cet algorithme :

Fonction pgcd(In Variable m : Entier, In Variable n : Entier) : Entier


Variables tmp,a,b : Entiers
a ← m
b ← n
Si b ≥ a Alors
tmp ← a
a ← b
b ← tmp
FinSi
Tant Que a mod b , 0
tmp ← a
a ← b
b ← tmp mod b
FinTantQue
Retourne b

FinFonction

(a) Démontrer que ce code est correct, c’est à dire qu’il renvoie bien pgcd(m, n).
(b) Démontrer que cet algorithme termine. Pour ce faire, on pourra démontrer au
préalable que la propriété 0 < bi+1 < bi est vérifiée pour toute itération i de la
boucle while, où bi dénote la valeur de b au début de la iième itération ou à la fin
de la (i − 1)ième itération.
2. On dispose d’un programme qui peut résoudre un problème de taille maximale M en
une minute. On planifie l’achat d’un nouveau microprocesseur 100 plus rapide que le

AP3 - TD2 3 / 11 2021-2022


précédent. Avec ce microprocesseur, le programme pourra résoudre en 1 minute des
problèmes de taille plus grande. Quelle sera la nouvelle taille maximale si le nombre
d’opérations effectuées par l’algorithme est n, 2n, 3n, n2 , n4 , 2n ?
3. Donner, en fonction de m et n, le nombre d’itérations de la boucle tant que dans chacun
des algorithmes suivants (on supposera que m ≥ 1 et n ≥ 1) :
(a)
Variables m, n, i, j : Entiers
saisir(m, n)
i ← 1; j ← 1;
Tant Que i ≤ m Et j ≤ n
i ← i + 1
j ← j + 1
FinTantQue
(b)
Variables m, n, i, j : Entiers
saisir(m, n)
i ← 1; j ← 1;
Tant Que j , n
Si i , m Alors
i ← i + 1
Sinon
j ← j + 1
FinSi
FinTantQue
(c)
Variables m, n, i, j : Entiers
saisir(m, n)
i ← 1; j ← 1;
Tant Que j ≤ n
Si i ≤ m Alors
i ← i + 1
Sinon
j ← j + 1
i ← 1
FinSi
FinTantQue

4. Dans le TD précédent, on avait écrit une fonction echangerMinPartiel, que l’on avait
utilisée dans une fonction qui permettait de trier un tableau. Le pseudo-code ci-
dessous est une solution possible pour cet exercice :

AP3 - TD2 4 / 11 2021-2022


Fonction echangerMinPartiel(In-Out Variable t : Tableau d’Entier, In Variable i
: Entier) :
Variables indMin, tmp : Entiers
indMin ← i
Pour j allant de i+1 à taille(t) par pas de 1
Si t[j] < t[indMin] Alors
indMin ← j
FinSi
FinPour
tmp ← t[i]
t[i] ← t[indMin]
t[indMin] ← tmp

FinFonction
Fonction triSelection(In-Out Variable t : Tableau d’Entier) :
Pour i allant de 1 à taille(t)-1 par pas de 1
echangerMinPartiel(t, i)
FinPour

FinFonction

Donner la complexité en nombre d’opérations de ces deux algorithmes.


5. On considère la recherche d’un élément dans un tableau.
(a) L’algorithme rechercher_séquentiel accepte en entrée un tableau d’entier et un
élément quelconque et qui calcule l’indice de l’élément dans le tableau s’il s’y
trouve, ou la taille du tableau plus 1 sinon :

Fonction rechercher_séquentiel(In Variable t : Tableau d’Entier,


In Variable n : Entier,In Variable element : Entier) :
Variables i : Entiers
i ← 1
Tant Que t[i ] , element Et i ≤ n
i ← i+1
FinTantQue
Retourne i
FinFonction

Étudier la complexité de l’algorithme, dans le meilleur et le pire des cas.


(b) On suppose maintenant que le tableau est trié par ordre croissant. On considère
l’algorithme rechercher_dichotomie, qui est une méthode plus efficace pour re-
trouver un élément dans un tableau :

AP3 - TD2 5 / 11 2021-2022


Fonction rechercher_séquentiel(In Variable t : Tableau d’Entier,
In Variable n : Entier,In Variable element : Entier) :
Variables i,min,mid max : Entiers
min ← 1
max ← n
Tant Que min < max
mid ← (min + max ) /2
Si t[mid] < element Alors
min ← mid + 1
Sinon
max ← mid
FinSi
FinTantQue
Si t[min] = element Alors
Retourne min
Sinon
Retourne n + 1
FinSi

FinFonction

Étudier la complexité de cet algorithme, dans le meilleur et le pire des cas.


6. Soit U (n), V (n), A(n) et B(n) des suites réelles positives définies pour n ∈ N.
(a) Montrer que les complexités peuvent s’additionner et se multiplier, c’est-à-dire :

U = O( A) et V = O( B) ⇒ U + V = O( A + B) et U · V = O( A · B).

(b) Montrer que les complexités sont transitives, c’est-à-dire :

U = O( A) et A = O( B) ⇒ U = O( B).

(c) Montrer que les complexités peuvent se simplifier, c’est-à-dire :

U = O( A + B) et A = O( B) ⇒ U = O( B).

7. On rappelle le principe de la multiplication égyptienne. Cette méthode se base sur la


propriété suivante :
 m
m∗n= 2 ∗ (2n ) si m est pair
(m − 1) ∗ n + n sinon.

Le code suivant implémente la multiplication égyptienne :

AP3 - TD2 6 / 11 2021-2022


Variables n, m, res : Entiers
res ← 0
saisir(n, m)
Tant Que m , 0
Si m mod 2 = 0 Alors
m ← m/2
n ← n∗2
Sinon
m ← m−1
res ← res + n
FinSi
FinTantQue
afficher(res)

(a) Calculer les valeurs de la variable m ainsi que leurs écritures en base 2 au cours
des itérations, pour les entrées (4, 1), puis pour les entrées (7, 1).
(b) Si l’écriture binaire de la première entrée contient p bits, exprimer le coût tempo-
rel en fonction de p, dans le meilleur et dans le pire des cas.
(c) Pour prouver rigoureusement le point précédent, prouver un invariant de boucle
dans le meilleur et dans le pire des cas encadrant le nombre de bits dans l’écriture
binaire de la variable m en fonction du numéro de l’itération.
(d) En déduire la complexité de l’algorithme de la multiplication égyptienne.

AP3 - TD2 7 / 11 2021-2022


3 Piles, files et listes chaînées

Résumé
Ce chapitre illustre le fonctionnement des structures de données élémentaires, en
l’occurrence les piles, les files et les listes chaînées. Deux points de vues sont souvent
utilisés : celui de l’utilisateur de ces structures et celui du concepteur. Il est nécessaire
de bien identifier le point de vue demandé par l’exercice.

3.1 Piles et Files


1. On met en œuvre une file par un tableau. Représenter le tableau, la tête et la queue
de la file, après chacune des opérations suivantes : initFile(2) ; enfiler(f,‘Q’) ;
enfiler(f,‘V’) ; défiler(f) ; enfiler(f,‘L’) ; défiler(f) ; enfiler(f,‘C’)
2. 1 tableau pour 2 piles
(a) Expliquer comment mettre en œuvre deux piles dans un seul tableau de taille n
de manière à ce qu’aucune pile ne déborde à moins que le nombre total d’élé-
ments dans les deux piles ne soit égal à n. La complexité des opérations devra
rester Θ(1).
(b) Écrire une structure double_pile, et les foncions empiler_double et dépiler_double
qui permettent de les mettre en œuvre ; ces fonctions prendront en entrée un pa-
ramètre supplémentaire précisant la pile à utiliser
3. File à double entrée
Une file à double entrée est une file où l’on autorise l’insertion et la suppression à
chaque bout. Écrire 2 fonctions en Θ(1) pour insérer et supprimer de chaque côté
d’une file à double entrée, mise en oeuvre par un tableau.
4. Une expression est bien parenthésée si on peut associer deux à deux les parenthèses
ouvrantes et fermantes qu’elle contient, de telle sorte que :
— chaque parenthèse ouvrante se trouve avant la parenthèse fermante qui lui est
associée dans la chaîne, et
— si une parenthèse ouvrante se trouve entre une autre parenthèse ouvrante et la
parenthèse fermante associée, alors sa parenthèse fermante aussi.
Une telle association est qualifiée de correcte. On considérera deux types de paren-
thèses qu’on notra "( )" et "[ ]".
(a) Donner deux exemples d’expressions contenant quatre parenthèses, l’une bien
parenthésée et l’autre non.
(b) Écrire un algorithme analyse_parenthèses, qui accepte en entrée une chaîne de
caractères représentant une expression, et qui calcule si elle est bien parenthésée
(On pourra s’inspirer de l’algorithme pour un seul type de parenthèses vu en
cours).

AP3 - TD3 8 / 11 2021-2022


5. Dans notre pseudo-langage comme dans l’usage général, le format des opérateurs
arithmétiques est infixe : les opérandes s’écrivent de part et d’autre de l’opérateur,
par exemple x + y. Ce n’est pas la seule possibilité : dans le format postfixe l’opéra-
teur apparaît après, xy+. Ces formats présentent plusieurs avantages ; l’un d’eux est
de ne pas nécessiter de parenthèses. Par exemple dans le format postfixe, ( x + y) × z
s’écrit xy + z× ; en effet, en lisant de gauche à droite,on peut évaluer une opération
dès qu’on rencontre un opérateur ; le résultat peut alors être réutilisé pour l’opérateur
suivant. En s’inspirant de cette indication, écrire un algorithme évaluer_postfixe qui
accepte en entrée une chaîne de caractère représentant une expression arithmétique
postfixe, et calcule la valeur résultante. Pour simplifier, on supposera que chaque ca-
ractère dans la chaîne représente soit une variable, soit un opérateur ; s’il représente
une variable, on accède à sa valeur à l’aide de la routine valeur.

3.2 Listes Chaînées


1. Écrire un algorithme indexer_cellule qui accepte en entrée une liste chaînée et un
entier, et passe en sortie une référence sur la cellule dont l’indice dans la liste est
l’entier donné en entrée. Étudier sa complexité.
2. Écrire chacun des algorithmes suivants, qui acceptent en entrée une liste chaînée et
un élément quelconque, et modifient la liste si l’élément s’y trouve.
(a) supprimer_cellule, qui supprime de la liste la première occurrence de l’élément.
(b) ajouter_cellule, qui accepte en entrée supplémentaire une cellule, et l’insère
après la première occurrence du premier élément donné en entrée.
(c) modifier_cellule, qui accepte aussi en entrée un deuxième élément quelconque,
et remplace la première occurrence du premier élément par le deuxième élément.
3. Écrire un algorithme concaténer qui acceptent en entrée deux listes chaînées et lie la
deuxième à la suite de la première de sorte à ne former qu’une seule liste. Étudier sa
complexité.
4. On considère les listes doublement chaînées, où chaque élément pointe vers son pré-
décesseur et son successeur, et les listes chaînées circulaires, où le dernier élément
pointe vers le premier.
(a) Proposer une structure représentant une cellule de chaque variante.
(b) Écrire des algorithmes permettant d’insérer ou supprimer un élément en tête de
chaque variante ; la complexité doit rester constante.
(c) Écrire l’algorithme longueur qui calcule la taille d’une liste chaînée circulaire.

AP3 - TD3 9 / 11 2021-2022


4 Récursion

Résumé
Ce chapitre s’intéresse à un mode de réflexion un peu différent de l’algorithmique.
La récursion nécessite une absraction suffisante pour pouvoir traiter des problèmes
comme dans le cas de la récurrence : si je sais traiter un problème de plus petite taille,
est-ce que sais compléter ma solution partielle pour obtenir la solution globale.
Le mécanisme de récursion possède en général plusieurs avantages : une simpli-
cité d’écriture, des calculs de terminaison et de complexité souvent plus simples (ré-
currences). La difficulté réside dans la construction de l’algorithme.
1. On rappelle le principe de la méthode égyptienne pour calculer le résultat de la mul-
tiplication de deux entiers naturels :

 n si m = 1
m
m∗n= ∗ (2n) si m est pair
 2
(m − 1) ∗ n + n sinon.

Écrire une fonction récursive multiplication_égyptienne, qui calcule le produit de


deux nombres avec cette méthode.
2. Écrire une fonction récursive pgcd qui accepte en entrée deux nombres entiers na-
turels, et qui calcule leur plus grand diviseur commun. On utilisera l’algorithme dit
d’Euclide se reposant sur la propriété suivante : pgcd(m, n) = pgcd(n, m mod n).
3. Écrire une fonction récursive qui calcule la longueur d’une liste chaînée.
4. Donner une fonction récursive en Θ(n) qui inverse l’ordre d’une liste simplement
chaînée à n éléments. En dehors de l’espace nécessaire pour contenir la liste elle-
même, la fonction ne devra pas utiliser d’espace de stockage non constant (pas d’autre
liste).
5. Écrire une fonction récursive qui concatène deux listes. La liste résultante remplacera
la première liste.
6. L’algorithme de tri par fusion présenté vu en cours se prête bien au tri de listes chaî-
nées. On considère donc des listes dont les éléments sont comparables à l’aide de
l’opérateur ≤.
(a) Écrire un algorithme récursif fusionner qui accepte en entrée deux listes chaî-
nées chacune triée dans l’ordre croissant, et qui les fusionne en une seule liste
triée dans l’ordre croissant, passée en sortie par référence.
(b) Écrire un algorithmes scinder qui accepte en entrée une liste chaînée et la scinde
en deux listes indépendantes de longueur ne différant pas plus de un, et passant
en sortie une référence sur chacune d’elles.
(c) Écrire un algorithme tri_fusion qui accepte en entrée une liste chaînée et la trie
par ordre croissant

AP3 - TD4 10 / 11 2021-2022


7. Une fonction récursive peut se généraliser de la façon suivante, où test_input, calcul,
calcul_final, modif_input sont des fonctions prédéfinies, et où X et Y sont des struc-
tures.

Fonction récursif(In Variable x : X) : Y


Si test_input(x) Alors
Retourne calcul_final(x)
Sinon
Retourne calcul(x,récursif(modif_input(x)))
FinSi
FinFonction
(a) Donnez une instanciation des fonctions et des structures de ce modèle générique
pour le calcul de PGCD.
(b) Donnez une fonction générique non-récursive qui calcule le même résultat que
récursif. On pourra utiliser une pile pour simuler la récursion.

AP3 - TD4 11 / 11 2021-2022

Vous aimerez peut-être aussi