Cours d'Algorithmique II - SMI 3
Cours d'Algorithmique II - SMI 3
Cours : Algorithmique II
SMI - Semestre 3
Pr. A. MOULOUDI
1
[Link]
Plan du cours
2
[Link]
Chapitre 1 : Compléments algorithmiques
Chapitre 1
Compléments algorithmiques
I. Rappels
I.1 INTRODUCTION
L’algorithmique est une science apparue, il y a très longtemps, bien avant l’idée
même d’ordinateur.
• Citons, vers 1800 avant J-C, les babyloniens de l’époque d’Hammurabi
formulant des règles précises pour la résolution de certains types d’équations.
• Plus tard, au 3ème siècle avant J-C, chez les grecs, fleurisse un grand nombre de
procédés de calcul, dont le célèbre algorithme d'Euclide.
• En Perse, au 9ème siècle après J-C, on trouve l’origine du mot «algorithme », qui
provient du nom de Abu Ja'far Mohammed Ibn Mûsâ Al-Khowâ-rismi, qui écrit
un ouvrage d’arithmétique utilisant les règles de calcul sur la représentation
décimale des nombres, et montra l’inutilité des tables et abaques. Il eut une
influence capitale pendant plusieurs siècles.
• Au fil du temps, la signification du mot s’élargit et finalement vient à désigner
tout procédé de calcul systématique, voire automatique, mais sans référence
nécessaire à une machine.
• Côté informatique du 21ème siècle et en première approche, on peut dire qu'un
algorithme est un « mode d'emploi pour résoudre un problème ».
Définition
Un algorithme est un procédé de calcul automatique composé d’un ensemble
fini d’étapes, chaque étape étant formée d’un nombre fini d’étapes élémentaires, qui
permet de résoudre le problème en donnant la sortie requise. Chaque étape
élémentaire est :
▪ définie de façon rigoureuse et non ambiguë
▪ effective, c-à-d, pouvant être réalisée par une machine
De plus, un algorithme doit toujours terminer après un nombre fini d’opérations,
quelle que soit la donnée en entrée, et fournir un résultat.
Les algorithmes que nous considérons sont déterministes : toute exécution de
l’algorithme sur les mêmes données donne le même résultat.
À tout algorithme écrit en langage naturel ou pseudo-code, on peut associer un
programme implémentant l’algorithme. Ce programme écrit avec des règles
syntaxiques rigoureuses et destinées à être compris par une machine.
En résumé, un algorithme doit être
3
[Link]
Chapitre 1 : Compléments algorithmiques
4
[Link]
Chapitre 1 : Compléments algorithmiques
Analyse du problème:
Bien comprendre l'énoncé du problème: Il est inutile et dangereux de passer à la
phase suivante si vous n'avez pas bien discerné le problème.
Expression du raisonnement
Bien souvent, quelques lignes écrites en langage courant suffisent pour décrire
succinctement l'essentiel du problème. L'intérêt de cette étape est qu'elle permet
de vérifier rapidement que l'on se trouve sur la bonne voie. De plus, ces quelques
lignes seront un support efficace lors de l'écriture de l'algorithme.
Expression d'une solution en pseudo-langage
Il peut arriver que plusieurs solutions répondent à un problème donné. Il faudra
choisir la solution la plus judicieuse et rester cohérent jusqu'au bout.
Tests et Vérification de l'adéquation de la solution
Vérifier l'exactitude du comportement de l'algorithme, son bon déroulement. Si
l'algorithme ne répond pas parfaitement à toutes les requêtes exprimées dans
l'énoncé du problème, retournez à la phase n°1.
5
[Link]
Chapitre 1 : Compléments algorithmiques
Exemple :
VAR g : ENTIER
PrixHT, TauxTVA, PrixTTC : REEL
6
[Link]
Chapitre 1 : Compléments algorithmiques
Dans ce tableau, les opérateurs sont classés par ordre de priorité décroissante
mais prudence! dans l'utilisation des priorités car il existe souvent des différences
d'un langage de programmation à l'autre.
En absence de parenthèses, l'évaluation se fait de gauche à droite.
I.3.3.2 Affectation
L'affectation a pour rôle d'attribuer une valeur, résultat d'une évaluation, à un
objet. La valeur doit être compatible avec le type de la valeur à gauche de
l'affectation. Le symbole utilisé pour l'affectation est .
Exemple:
nb_kg : ENTIER
prix_du_kg, prix_total : REEL
……………
prix_total nb_kg * prix_du_kg
Exemple d'algorithme
ALGORITHME Epicier
VAR
prix_total , prix_du_kg : REEL
nb_kg : ENTIER
DEBUT
ECRIRE('Entrez le prix d'un kilogramme de choux : ')
LIRE(prix_du_kg)
ECRIRE('Entrez le nombre de kilogramme de choux : ')
LIRE(nb_kg)
prix_total prix_du_kg * nb_kg
ECRIRE('Le prix total de l''achat est :',prix_total)
FIN
I.3.3.4 Commentaires
Afin d'améliorer la lisibilité d'un algorithme, on peut utiliser des commentaires.
Un commentaire est une suite de caractères quelconques encadrée par les
symboles /* et */.
Dans le même but, on utilise des sauts de lignes et des espaces blancs en tête des
lignes de façon à ce que l’écriture de l’algorithme (ou le programme) mette en
valeur sa structure intrinsèque.
7
[Link]
Chapitre 1 : Compléments algorithmiques
Exemple:
/* ceci est un commentaire */
8
[Link]
Chapitre 1 : Compléments algorithmiques
SI Condition ALORS
T1
SINON
T2
FSI
Condition désigne une expression booléenne dont la valeur est VRAI ou FAUX.
T1 et T2 désignent une suite d'instructions (pouvant elles mêmes être des
structures alternatives).
La structure alternative est une structure parenthésée, chaque partie étant
délimitée par les symboles SI, ALORS, SINON et FSI.
Le traitement T2 est facultatif.
Exemple :
ALGORITHME Maximum_de_Deuxnombres
VAR
nb1,nb2 : ENTIER; /* les deux nombres à comparer */
max : ENTIER; /* le plus grand */
DEBUT
ECRIRE( Entrez le premier nombre :)
LIRE(nb1);
ECRIRE(Entrez le deuxième nombre :)
LIRE(nb2);
SI nb1 >= nb2 ALORS
max nb1
SINON
max nb2
FINSI
ECRIRE(le maximum de , nb1, et de ,nb2, est : ,max)
FIN
max nb2;
SI nb1 > nb2 ALORS
max nb1
FINSI
On remarque que cette alternative ne comporte pas de clause SINON.
9
[Link]
Chapitre 1 : Compléments algorithmiques
10
[Link]
Chapitre 1 : Compléments algorithmiques
CAS valeur_Expression
valeur1 : T1
valeur2 :
DEBUT
T21
T22
FIN
valeur3 : T3
valeur4 : T4
AUTRE :
T_Autre
FINCAS
Ce traitement pourrait être obtenu à l'aide de SI imbriqués.
SI valeur_Expression = valeur1 ALORS
T1
SINON
SI valeur_Expression = valeur2 ALORS
T21
T22
SINON
SI valeur_Expression = valeur3 ALORS
T3
SINON
SI valeur_Expression = valeur4 ALORS
T4
FSI
FSI
FSI
FSI
I.5.2 La répétition
11
[Link]
Chapitre 1 : Compléments algorithmiques
DEBUT
T
TANT QUE NON Condition FAIRE
T
FINTANTQUE
FIN
12
[Link]
Chapitre 1 : Compléments algorithmiques
Un sous-programme peut comporter des données (types, variables etc...) qui lui
sont propres. On parle alors de variables locales au sous-programme. On peut
noter dès à présent que ces variables existeront uniquement pendant l'exécution
du sous-programme.
Bien souvent, une procédure utilise des données fournies par le programme
principal (ou par une autre procédure).
La procédure sera écrite avec des arguments fictifs appelés paramètres formels
auxquels on fera correspondre lors de l'appel les données réelles appelées
paramètres effectifs ou réels.
Traduction de la procédure avec paramètre fixe en pseudo-langage
PROCEDURE id_procedure ( parametre_formel : type du paramètre)
VAR
i : ENTIER
DEBUT
T
FIN
13
[Link]
Chapitre 1 : Compléments algorithmiques
...
id_procedure(j)
...
id_procedure(5)
...
FIN
II.4 Fonctions
Lorsqu'un sous-programme doit rendre un résultat unique de type simple, il est
commode de définir ce sous-programme comme une fonction dont l'utilisation est
analogue à celui des fonctions mathématiques usuelles (SIN(x), TANG(x), COS(x)
etc...).
Traduction de la fonction en pseudo-langage
ALGORITHME Nom_algo
VAR
i, j, k : ENTIER
DEBUT
...
i Id_Fonction(j)
...
FIN
14
[Link]
Chapitre 1 : Compléments algorithmiques
IV Les fichiers
IV.1 Généralités sur l'organisation des informations
15
[Link]
Chapitre 1 : Compléments algorithmiques
Les différentes informations que l'on aura à traiter seront donc stockées sur un
support physique.
Il va donc falloir organiser les différents transferts entre la mémoire et ce support
physique grâce à différentes primitives d'accès.
L'organisation d'un fichier est la structure logique permanente établie au moment où
le fichier est créé.
Définition
Un fichier est un ensemble organisé d'informations (articles ou enregistrements) de
même nature susceptibles de faire l'objet de traitements divers.
VAR F : FICHIER
16
[Link]
Chapitre 1 : Compléments algorithmiques
Fermeture.
FERMER(nom_de_fichier_logique)
17
[Link]
Chapitre 2 : Récursivité
Chapitre 2
Récursivité
I. Définitions et Exemples
I.1 Introduction
La récursivité est un domaine très intéressant de l'informatique, un peu abstrait,
mais très élégant; elle permet de résoudre certains problèmes d'une manière très
rapide, alors que si on devait les résoudre de manière itérative, il nous faudrait
beaucoup plus de temps et de structures de données intermédiaires.
La récursivité, c'est la possibilité de faire figurer dans la définition d'un objet une
référence à ce même objet. Beaucoup de langages autorisent la récursivité simple
et la récursivité croisée.
I.1.1 Définitions
Une procédure P, qui s'appelle elle-même ou qui appelle une autre procédure P'
contenant un appel de P est une procédure récursive.
Elle possède 2 propriétés:
• Il doit exister des critères pour lesquels les appels cessent
• Chaque fois que la procédure s'appelle (directement ou indirectement), elle doit
être plus proche des ses critères d'arrêt.
Il y a récursivité simple lorsqu'une fonction (ou procédure) en cours d'exécution
s'appelle elle-même :
Fonction f1(…) : type_fonction
var x : type_fonction
debut
x = f1(…)
fin
Il y a récursivité croisée (ou appel indirect) lorsqu'une fonction (ou procédure) f1
appelle une fonction (ou procédure) f2 qui elle-même appelle la procédure f1 :
Fonction f1(…) : type_fonction Fonction f2( …) : type_foction
var x : type_fonction var x : type_fonction
debut debut
x = f2(…) x = f1( …)
fin fin
A chaque appel du sous-programme récursif, les variables locales, les paramètres
et l'adresse de retour sont empilés dans une zone mémoire appelée pile.
18
[Link]
Chapitre 2 : Récursivité
I.2 Exemples
I.2.1. Calcul de la factorielle d'un nombre n
n! = n(n-1)(n-2) ... 2*1 si n > 0
n! = 1 si n = 0
- Expression récursive du problème :
n! = n ( n – 1) … (2) (1) = n (n – 1)!
- Condition d’arrêt:
n = 1 ou n = 0
- Convergence (vers la condition d’arrêt):
Si n = 1 ou n = 0, alors on a « convergé »!
Si n > 1, alors la soustraction à l’étape suivante nous approche de n = 1. D’où la
convergence
On peut bien entendu résoudre ce problème de manière itérative classique
19
[Link]
Chapitre 2 : Récursivité
20
[Link]
Chapitre 2 : Récursivité
Remarque
Ces deux derniers algorithmes sont basés sur le fait que :
pcgd(a, b) = pgcd(b, a mod b).
I.2.4 Le problème des Tours de Hanoi
21
[Link]
Chapitre 2 : Récursivité
3:c3
4:c2
5:c1
FINCAS
SI (n = 1) ALORS deplacer(a,b) /* déplace le disque du haut de a vers b */
SINON
Hanoi(n-1, a, c)
deplacer (a, b)
Hanoi(n-1, c, b)
FINSI
FIN /* fin de la procedure Hanoi */
Exemple d’application :
1ère étape:
Je déplace un disque du pilier n°1 vers le pilier n°3
2ème étape:
Je déplace un disque du pilier n°1 vers le pilier n°2
3ème étape:
Je déplace un disque du pilier n°3 vers le pilier n°2
22
[Link]
Chapitre 2 : Récursivité
4ème étape:
Je déplace un disque du pilier n°1 vers le pilier n°3
5ème étape:
Je déplace un disque du pilier n°2 vers le pilier n°1
6ème étape:
Je déplace un disque du pilier n°2 vers le pilier n°3
7ème étape:
Je déplace un disque du pilier n°1 vers le pilier n°3
PROCEDURE compter
VAR i: ENTIER
DEBUT
POUR i 1 à 10 FAIRE
ECRIRE(i)
FINPOUR
FIN
Cette procédure peut être traduite en une procédure récursive, qui admet un
paramètre; l'instruction qui l'appellera sera "compter(1)":
PROCEDURE compter(i: ENTIER)
DEBUT
SI i < 11 ALORS
ECRIRE(i)
compter(i + 1)
FINSI
FIN
24
[Link]
Chapitre 2 : Récursivité
affiche(a + 1)
FINSI
FIN
Il ne nous reste plus qu'à supprimer la deuxième boucle; en sachant que lorsque
b=10 dans la procédure initiale, le programme revient à la boucle sur a et remet b
à zéro, alors on a 2 appels récursifs :
25
[Link]
Chapitre 2 : Récursivité
factorielleT(2,3)
factorielleT(1,6)
III.3 Dérécursification
Une fonction récursive terminale a pour forme générale :
26
[Link]
Chapitre 2 : Récursivité
Bloc I2
P <- f(P);
Bloc I0;
fintantque
Bloc I1
fin
27
[Link]
Chapitre 3 : Conception des algorithmes
Chapitre 3
• Analyse du problème
• Approche incrémentale
• Recherche exhaustive
• Méthodes approchées
o Algorithme glouton
o Diviser pour régner
o Heuristiques
I. Analyse du problème
I.1 Analyse descendante
I.1.1 Buts
- Obtenir une analyse et une programmation de haut en bas selon une
décomposition arborescente qui autorise le fractionnement des programmes en
modules de taille facilement lisible (par exemple de la taille d'une page de listing).
- Supprimer, grâce à cette analyse descendante, la phase d'intégration (partie très
délicate lors de projets importants).
- Rendre possible la lecture des modules de haut niveau par des non-spécialistes.
- Faciliter la maintenance et l'extensibilité en donnant la possibilité de modifier
une arborescence, sans affecter ce qui est au niveau supérieur.
- Diminuer les risques d'erreurs de programmation.
- Fournir aux programmeurs un mode commun de décomposition des problèmes.
I.1.2 Principe
C'est une méthode de décomposition des problèmes.
Soit le problème A à résoudre
28
[Link]
Chapitre 3 : Conception des algorithmes
29
[Link]
Chapitre 3 : Conception des algorithmes
Algorithme glouton : trier les pièces en fonction de leur taille et placer d’abord les pièces
les plus grandes.
30
[Link]
Chapitre 3 : Conception des algorithmes
31
[Link]
Chapitre 3 : Conception des algorithmes
Autre exemple, le tri rapide, le tri fusion, l’exponentiation rapide, recherche dans
un dictionnaire
IV.3 Heuristique
Une heuristique est un algorithme qui fournit rapidement une solution pas
nécessairement optimale à un problème d’optimisation. Ce n’est donc pas un
algorithme exact. Sur certains problèmes, un algorithme qui donnera une solution
exacte pourra être de complexité exponentielle (la complexité est expliquée plus
loin), donc très longs.
Définition
Une heuristique est une méthode de calcul qui fournit rapidement
(en temps polynomial) une solution réalisable, pas nécessairement optimale, pour
un problème d'optimisation NP-difficile.
Pour les problèmes de grandes tailles :
- pas de temps de calculs ”raisonnables” avec les méthodes exactes
- recherche de ”bonnes” solutions approchées.
Méta-heuristiques : algorithmes d’optimisation (généralement de type
stochastique) combinant plusieurs approches heuristiques.
Quelques méthodes heuristiques
- Programmation dynamique
- Recuit simulé
- Algorithmes génétiques
- Algorithme de colonies de fourmis, recherche Tabou, · · ·
V. Et des mélanges
On n’est pas obligé de suivre un modèle de conception d’algorithme, il peut
être avantageux de prendre le meilleur dans chaque modèle. Par exemple, on va
voir que sur les petites valeurs, le tri par insertion est très efficace, mais que sur
les grandes valeurs, le tri rapide l’emporte. On choisit donc d’utiliser le tri rapide,
avec pour restriction que, si à un moment donné, l’algorithme de tri est appelé sur
moins de 15 éléments, alors on trie ces éléments avec le tri par insertion.
32
[Link]
Chapitre 4 : Complexité algorithmique
Chapitre 4
Complexité algorithmique
I. Complexité des algorithmes
I.1. Introduction
Ce que l’on entend par complexité des algorithmes est une évaluation du coût
d’exécution d’un algorithme en termes de temps (complexité temporelle) ou
d’espace mémoire (complexité spatiale).
Ce qui suit traite de la complexité temporelle, mais les mêmes notions permettent
de traiter de la complexité spatiale.
Ce coût d’exécution dépend de la machine sur laquelle s’exécute l’algorithme, de la
traduction de l’algorithme en langage exécutable par la machine. Mais nous ferons
ici abstraction de ces deux facteurs, pour nous concentrer sur le coût des actions
résultant de l’exécution de l’algorithme, en fonction d’une “taille” n des données
traitées. Ceci permet en particulier de comparer deux algorithmes traitant le
même calcul. Nous verrons également que nous sommes plus intéressés par un
comportement asymptotique (que se passe-t-il quand n tend vers l’infini?) que
par un calcul exact pour n fixé. Enfin, le temps d’exécution dépend de la nature des
données (par exemple un algorithme de recherche d’une valeur dans un tableau
peut s’arrêter dès qu’il a trouvé une occurrence de cette valeur. Si la valeur se
trouve toujours au début du tableau, le temps d’exécution est plus faible que si
elle se trouve toujours à la fin). Nous nous intéresserons d'abord dans ce qui suit à
la complexité en “pire des cas”, qui est une manière, pessimiste, d’ignorer cette
dépendance (on évaluera le temps d’exécution, dans le cas évoqué ci-dessus, en
supposant qu’il faut parcourir tout le tableau pour trouver la valeur cherchée).
I.2. Exemples
Considérons l’exemple 1 suivant:
{début}
R0
I1
{#1}
TANT QUE I ≤ N {#2} FAIRE
R R+T[I] {#3}
I I+1 {#4}
FINTANTQUE
{fin}
Le temps d’exécution t(n) de cet algorithme en supposant que:
- N=n
- t1 est le temps d’exécution entre {début} et {#1}
- t2 est le temps d’exécution de la comparaison {#2}
- t3 est le temps d’exécution de l’action {#3}
33
[Link]
Chapitre 4 : Complexité algorithmique
Autrement dit t(n) est équivalent à l’infini à (tit x n), ce qui s’écrit:
t ( n ) tit n
L’algorithme est donc asymptotiquement linéaire en n.
Dans cet exemple simple l’évaluation en “pire des cas” est immédiate puisque t(n)
ne dépend pas de la nature des données, ce qui n’est pas le cas de l’exemple 2
suivant:
{début}
R0
I1
{#1}
TANT QUE I ≤ N {#2} FAIRE
R R+T[I] {#3}
SI R>1000 {#3’} Alors
R 2*R {#3’’}
FINSI
I I+1 {#4}
FINTANTQUE
{fin}
Ici, le pire des cas (celui qui conduit au temps d’exécution le plus grand) est celui
où la condition {#3’} est toujours vraie. En effet dans ce cas là R 2*R {#3’’} est
exécutée à chaque itération.
Ce qui correspond à l’évaluation suivante du temps d’exécution:
34
[Link]
Chapitre 4 : Complexité algorithmique
- tp, tm, tc les temps associés respectivement à une addition, une multiplication
et une comparaison, on obtient le temps suivant:
Exemple 1
f(n) = 3n +1, g(n) = n
3n+1 est en O(n)
35
[Link]
Chapitre 4 : Complexité algorithmique
36
[Link]
Chapitre 4 : Complexité algorithmique
Remarque importante:
L’affectation, la lecture, l’écriture d’une variable de type élémentaire se fait
en un temps constant (indépendant de n) et est compté comme une opération. Il
en va différemment de l’affectation d’une variable de type non élémentaire,
lorsque ce type dépend de n (qui est une copie de la valeur d’une expression dans
une variable). Ainsi soit un tableau T de taille n, alors:
T2 T correspond à n affectations élémentaires, et si on note Aff(n) le nombre
d’affectations et d’opérations dans l’algorithme suivant:
T[1] T[N] -T[N-1]
TAMP T
T T2
T2 TAMP
37
[Link]
Chapitre 4 : Complexité algorithmique
On obtient Aff(n) =2 + 3n
38
[Link]
Chapitre 4 : Complexité algorithmique
En ce qui concerne les structures itératives, une règle générale pourrait être la
suivante:
Règle 3: Itération (TANTQUE)
Soit une action A de la forme « TANTQUE C FAIRE A1 FINTANTQUE»
En notant niter(n) le nombre d’itérations, on a:
Cependant cette règle est trop abstraite et ce qu’il faut en retenir est surtout le
lien entre les structures répétitives et la sommation, comme nous l’avons vu
précédemment. En particulier il est bon de lier la variable indice de la sommation
avec une variable « compteur » de la structure
répétitive lorsqu’elle existe. Il est cependant intéressant de noter que:
- La condition C est exécutée dans le « TANTQUE» une fois de plus que
le « corps » A1 de la boucle.
- TC n’est pas toujours une constante, mais peut dépendre de n et du rang i de
l’itération, qui est souvent lié à une variable.
Exemple 7
// Nous nous intéressons ici au nombre d’opérations (+,-,*), Op(n), avec N=n
// Nous supposons ici que Truc(l,n)<n et que Truc(l,n) nécessite Tt(n)= (n-l) opérations
// On suppose aussi que Tab est de dimension Nmax ≥ N+1
Res 0
L2
TANTQUE L ≤ Truc(L, N) FAIRE
Res Res+2*Tab[L+1]+Truc(L,N)
L L+2
FINTANTQUE
Remarquons d’abord que L augmente de 2 à chaque itération et que dans le pire
des cas la condition testée est L≤ N, pour toute valeur de L (puisque Truc(L,N) ≤N).
Nous pouvons alors écrire Op(n) de la manière suivante:
39
[Link]
Chapitre 4 : Complexité algorithmique
Remarquons que, sans faire tous les calculs, on pouvait anticiper que Op(n) serait
en Θ(n2) en remarquant que le terme de plus haut degré serait de degré 2.
En ce qui concerne la structure « POUR ........ FAIRE ......... FINPOUR » on procèdera
de la manière suivante: on considère la boucle TANTQUE équivalente:
POUR I ideb à ifin FAIRE
Action1
FINPOUR
est considéré équivalent à
I ideb -1
TANTQUE I < ifin FAIRE
I I+1
Action1
FINTANTQUE
C’est à dire que l’on compte en plus des (ifin-ideb+1) itérations, (ifin-ideb+2)
affectations, additions, comparaisons.
Remarquons qu’une pratique courante consiste à négliger (lorsque cela ne change
pas la complexité) ces opérations implicites dans le «POUR..... », comme nous
l’avons fait ci-dessus.
Exemple 8
POUR I 1 à N FAIRE
Res Res+I
FINPOUR
Le nombre d’additions est ici N si on néglige ces opérations implicites, et 2N+1 si
on les compte (ici ifin-ideb+1 = N).
Règle 4 : Fonctions et Procédures non récursives
On évalue d’abord les fonctions et procédures qui ne contiennent pas d’appels à
d’autres fonctions et procédures, puis celles qui contiennent des appels aux
précédentes, etc....
Exemple 9
Algorithme Truc
VAR C: CARACTERE
N, R0, R1,I: ENTIER
PROCEDUR A (VAR R : ENTIER)
VAR I: ENTIER
DEBUT
POUR I 1 à N FAIRE
R R*I
FINPOUR
FIN
PROCEDUR B (Var R: ENTIER)
VAR I,J: ENTIER
DEBUT
J1
POUR I 1 à N FAIRE
A(J)
40
[Link]
Chapitre 4 : Complexité algorithmique
R R*J
FINPOUR
FIN
DEBUT
LIRE(C)
LIRE(N)
R0 1
R1 1
{debut #1}
SI C =‘#’ ALORS
{début ##1}
POUR I 1 à N FAIRE
R1 2*R1
B(N)
FINPOUR
{fin ##1}
FINSI
{Fin #1}
{début #2}
A(N)
{fin #2}
FIN
Nous calculons ici le nombre de multiplications Op(n) pour N=n.
Nous observons d’abord que l’algorithme, dans le pire des cas (ici C=‘#’)
Et finalement
Remarque importante:
Lorsqu’on fait un appel, il faut en toute rigueur compter l’appel lui-même
comme une opération particulière, mais aussi compter les opérations
correspondant au passage de l’argument.
41
[Link]
Chapitre 4 : Complexité algorithmique
Plus précisément: lors d’un passage par valeur quelles sont les opérations mises
en jeu? Pour chaque argument passé il faut évaluer l’argument (par exemple
l'addition pour Factorielle(n+1), et affecter cette valeur à une nouvelle variable
(locale à la fonction). On néglige souvent cette dernière opération, cependant si
l’argument passé est un tableau de taille N, alors l’affectation correspond à N
affectations élémentaires, et ce coût n’est plus négligeable. C’est en particulier une
des raisons pour lesquelles on évite souvent de passer par valeur un tableau
même si sa valeur ne doit pas être modifiée par la procédure (ou fonction). En
effet un passage par adresse ne correspond pas à N affectations élémentaires
puisque seule l’adresse en mémoire du tableau est fournie à la procédure lors de
l’appel.
42
[Link]
Chapitre 4 : Complexité algorithmique
Plus précisément :
43
[Link]
Chapitre 4 : Complexité algorithmique
........
{n-1} 2n-2 Op(2) =2n-1 Op(1) +2n-2 t1
{n} 2n-1 Op(1) = 2n t0 + 2n-1 t1
------------------------------------
Op(n) = 2n t0 + ( 1+2 + 22+ ...+2n-1) t1
= 2 t0 +((2 -1) / (2-1)) t1
n n
44
[Link]
Chapitre 4 : Complexité algorithmique
II.1.2 Illustration
En gras, les éléments déjà triés, en italique, les éléments à permuter.
Algorithme
45
[Link]
Chapitre 4 : Complexité algorithmique
nécessaire à l'étape précédente. Les éléments les plus grands se déplacent ainsi
comme des bulles vers la droite du tableau.
II.2.2. Illustration
En italique, les deux éléments à comparer, en gras les éléments en bonne place.
Algorithme
j N-1
FAIRE
POUR i 0 à J-1 FAIRE
SI t[i] > t[i+1] ALORS
permuter t[i] et t[i+1]
permutation VRAI
FIN SI
FIN POUR
TANT QUE permutation=VRAI
II.2.3 Analyse
Dans le pire des cas, le nombre de comparaisons et le nombre de permutations à
effectuer sont de n2/2. Dans le meilleur des cas (ensemble déjà trié), le nombre de
comparaisons est de n-1 et l'algorithme est donc de complexité linéaire.
46
[Link]
Chapitre 4 : Complexité algorithmique
II.3.2 Illustration
Algorithme
II.3.3 Analyse
Dans le pire des cas, le nombre de comparaisons et de n2/2. Dans le meilleur des
cas il est de N.
L'algorithme est de complexité O(N2) mais il est plus efficace que les deux
précédents si le tableau est en partie trié.
II.4.2 Illustration
47
[Link]
Chapitre 4 : Complexité algorithmique
Algorithme
On considère un nouveau tableau temp de même type que t
48
[Link]
Chapitre 4 : Complexité algorithmique
FINSI
FINPOUR
FIN
II.4.3 Analyse
La complexité de cet algorithme est donnée par la relation suivante:
O ( 1 ) Si n = 1
T(n) =
T ( n / 2 ) + T ( n / 2 ) + O ( n )
Si n 1
n étant le nombre d’éléments dans le tableau.
Dans le but de simplifier la résolution de l’équation ci-dessus, nous supposons
que n=2k pour un entier k ≥ 0. En remplaçant O(n) par n, on obtient (en principe,
on doit la remplacer par cn):
1 Si n = 1
T(n) =
2T ( n / 2 ) + n Si n 1
T(n) = 2T(n/2) + n
= 4T(n/’) + 2n
= 8T(n/8) + 3n
:
= 2k T(n/2k) + kn
:
= nT(1) + nlog2(n)
= n + nlog2(n)
La complexité temporelle de tri par fusion est donc en O(n log n).
49
[Link]
Chapitre 4 : Complexité algorithmique
tri_rapide_bis(tableau,pivot+1,fin)
FINSI
FIN
50
[Link]
Chapitre 4 : Complexité algorithmique
Partitionnement :
On parcourt le tableau de gauche à droite jusqu'à rencontrer un élément
supérieur au pivot
II.5.3 Illustration
II.5.4 Analyse
À l’appel de QuickSort (1,n), le pivot se place en position i. Ceci nous laisse avec un
problème de tri de deux sous parties de taille i-1 et n-i. L’algorithme de partition a
une complexité au plus de cn, pour une constante c.
51
[Link]
Chapitre 4 : Complexité algorithmique
………….. …………………………
Cas favorable
Dans le meilleur des cas, le pivot est, à chaque fois, situé au milieu de la partie à
trier.
Ce développement s’arrête dès qu’on atteint T(1). Autrement dit, dès que
52
[Link]
Chapitre 4 : Complexité algorithmique
53
[Link]
Chapitre 4 : Complexité algorithmique
54
[Link]
Chapitre 5 : Preuve d’algorithmes
Chapitre 5
Preuve d’algorithmes
I. Introduction
La question principale qui se pose à tout informaticien est :
Comment savoir qu’un algorithme donne le résultat espéré ?
Voici quelques exemples où il est nécessaire d’avoir des algorithmes corrects
• Contrôle de pilote automatique
• Contrôle des commandes d’un avion
• Centrale nucléaire
• Robots médicaux
• Bases de données critiques : Chemins de fer, Banque, etc
• Circuits électroniques
• Sécurité des systèmes d’information
• Sécurité des puces électroniques
Comment faire en sorte que l’on soit sûr de la correction de l’algorithme ?
• Générer un ensemble de tests. Mais la plupart du temps, on n’a pas de
garantie que l’algorithme est correct.
• Vérifier que l’algorithme correspond bien aux attentes. Cette opération
couvre plusieurs méthodes, parfois compliquées mais très sûr (à condition
de ne pas faire d’erreur dans la preuve…)
Exemple : Recherche du maximum dans un tableau de réels
FONCTION maximum(T :Reel[0..n]) : Reel
VAR Max : Reel
i : Entier
Début
Max T[0]
POUR i1 a n-1 FAIRE
SI T[i-1]<T[i] ALORS
Max T[i]
FIN SI
FIN POUR
Retourner Max
FIN
• La fonction donne-t-elle bien l’élément maximum du tableau
• En général les étudiants sont très confiants de leur algorithme
55
[Link]
Chapitre 5 : Preuve d’algorithmes
II.1.2 Exemples
▪ Algorithme de division euclidienne par soustraction
Bb
Ra
Q0
TANT QUE R >= B FAIRE
RR−B
QQ+1
FINTANTQUE
Remarquons que les conditions initiales donnent :
a = B x Q + R.
Montrons que la propriété a = B x Q + R est un invariant de boucle : notons R′, B′, Q′
les nouvelles valeurs en sortie de B, Q et R. Alors
R′= R − B et Q′= Q + 1.
Ceci prouve que
B′x Q′+ R′= B xQ+ R.
De plus la quantité entière R-B diminue strictement à chaque tour, donc le
programme se termine et après la boucle on a :
a = B x Q + R et R < B.
▪ Version binaire de l’algorithme de division euclidienne
On calcule avant toute chose par duplications successives le plus petit entier n ≥ 0
tel que 2nb > a.
Bb
Ra
Q0
Nn
Aux 2NB
56
[Link]
Chapitre 5 : Preuve d’algorithmes
R0 |a|
R1 |b| /* b différent de 0 */
TANT QUE R1 > 0 FAIRE
R Reste_Division(R0,R1)
R0 R1
R1 R
FINTANTQUE
En sortie R1 = 0 et R0 = pgcd(a, b).
Les conditions :
• L’ensemble des diviseurs communs de R0 et R1 est l’ensemble des
diviseurs communs de a et b.
57
[Link]
Chapitre 5 : Preuve d’algorithmes
• R1 ≥ 0
Constituent un invariant de boucle.
Remarquons qu’initialement l’ensemble des diviseurs communs de R0 et R1 est
l’ensemble des diviseurs communs de a et de b. Notons R0′, R1′ les nouvelles
valeurs de R0, R1 en sortie d’un tour de boucle. Nous avons alors
R0′= R1 et R1′= R0 - QR1 avec 0 ≤ R1′< R1.
Donc tout diviseur de R1 et R0 est diviseur de R0′ et R1′, et réciproquement.
Cet algorithme se termine car R1 décroît strictement à chaque tour de boucle. A la
fin R1 = 0, donc l’ensemble des diviseurs de R0 et de R1 est l’ensemble des
diviseurs de R0, et par conséquent R0=pgcd(a,b).
▪ Algorithme d’Euclide étendu
Là encore nous supposerons que a≥0 et b>0. Le cas général s’en déduit. Notons
d=pgcd(a, b).
Voici un algorithme (algorithme d’Euclide étendu, adaptation de l’algorithme
précédent) qui permet de trouver explicitement un couple (u,v) qui vérifie : ua +
vb = d.
R0 a /* a ≥ 0 */
R1 b /* b > 0 */
U0 1
U1 0
V0 0
V1 1
TANT QUE R1 > 0 FAIRE
Q Quotient_Division(R0,R1)
R Reste_Division(R0,R1)
U U0 − Q x U1
V V0 − Q x V1
R0 R1
R1 R
U0 U1
U1 U
V0 V1
V1 V
FINTANTQUE
Remarquons qu’il s’agit d’une amélioration de l’algorithme d’Euclide donné
précédemment pour le calcul du pgcd. Comme précédemment l’algorithme se
termine avec R1 = 0 et R0 = pgcd(a, b).
Montrons que les conditions :
U 0 a + V 0 b = R0
U 1a + V 1b = R1
R 1 0
58
[Link]
Chapitre 5 : Preuve d’algorithmes
Constituent un invariant de boucle. Pour cela notons R0′, R1′, U0′, U1′, V0′, V1′ les
nouvelles valeurs de R0, R1, U0, U1, V0, V1 en sortie d’un tour de boucle. On a :
R0 = Q x R1 + R,
U = U0 − Q x U1,
V = V0 − Q x V1,
puis
R0′= R1
R1′= R = R0 − Q x R1
U0′= U1
U1′= U = U0 − Q x U1
V0′= V1
V1′= V = V0 − Q x V1
Si bien que :
U0′a + V0′b = U1a + V1b = R1 = R0′
La première condition est bien réalisée en sortie. De même on a :
U1′a + V 1′b = U0a + V0b − Q x (U1a + V1b) = R0 − Q x R1 = R′1.
Donc la deuxième condition est aussi réalisée.
Il est facile de voir qu’à l’instant initial ces deux conditions sont bien réalisées. En
sortie on a R1 = 0 et R0 = pgcd(a, b) si bien que U0 et V0 contiennent une solution
du problème.
▪ Calcul d’une puissance
Soit n ≥ 1 un entier. On veut calculer an (a est par exemple dans Z/nZ ou dans R,
C,...). On considère l’algorithme suivant:
Aa
Nn
R1
TANT QUE N > 0 FAIRE
SI N pair ALORS
AAxA
N N/2
SINON
RRxA
NN−1
FINSI
FINTANTQUE
Cet algorithme se termine et en sortie R contient an.
Preuve :
La valeur de N ≥ 0 décroît strictement à chaque tour de boucle, donc
l’algorithme se termine.
Au début on a AN × R = an.
Si en entrée de boucle AN × R = an alors il est facile de voir que dans les deux cas N
pair ou N impair on a la même égalité en sortie de boucle. Mais à la fin on a N = 0 et
par conséquent R = an.
III. TESTS
59
[Link]
Chapitre 5 : Preuve d’algorithmes
Définition
Le test consiste à essayer de trouver des erreurs ou à augmenter la
confiance dans la correction d’une implantation par rapport à sa spécification
Spécification : propriété (abstraite) qui caractérise une fonction de l’algorithme
Implantation : programme exprimant un algorithme dans un langage donné
En fait, le test n’a pas pour but d’établir la correction d’un algorithme..
Avec un langage impératif
- Dans ce cas l’état peut être l’état de la mémoire :
Après chaque opération, un état de la mémoire est attendu
- Il faut alors tester toutes les transitions possibles et comparer à l’état
attendu
- Ou alors exhiber un contre-exemple, trouver une exécution où le résultat
attendu est différent du résultat constaté
Il est possible d’automatiser cette opération mais nous n’allons pas le faire dans
ce cours.
Exemple
FONCTION maximum(T :Reel[0..n]) : Reel
VAR Max : Reel
i : Entier
Début
Max T[0]
POUR i1 a n-1 FAIRE
SI Max<T[i] ALORS
Max T[i]
FIN SI
FIN POUR
Retourner Max
FIN
Attention : ce n’est pas parce que l’algorithme fonctionne sur 1 exemple que l’on
peut généraliser
Exemple : Recherche du maximum dans un tableau de réels
60
[Link]
Chapitre 5 : Preuve d’algorithmes
61
[Link]
Série d’exercices à traiter en TD
Exercice 1
Ecrivez un algorithme permettant, à l’utilisateur de saisir les notes d'une classe. Une fois la
saisie terminée, l’algorithme renvoie le nombre des notes supérieures à la moyenne de la classe.
Exercice 2
Ecrire un algorithme qui calcule, à une valeur approchée u donnée, la racine carrée d'un
nombre a > 0 sachant que la suite suivante :
X1 = a
Xn = (a/Xn-1 + Xn-1 ) / 2
converge vers la racine carrée de a.
Exercice 3
Considérons un tableau T de N nombres entiers (N > 1). On veut écrire un seul algorithme
qui répond aux questions suivantes :
- déterminer le troisième nombre premier s'il existe,
- déterminer le deuxième carré parfait s'il existe,
- déterminer le nombre de diviseurs du premier nombre pair et du dernier nombre impair.
Pour cela, créer et utiliser les fonctions suivantes dans l’algorithme développé :
- la fonction prem(nombre) de type booléen qui est égal à vrai si le paramètre nombre est
premier, faux sinon.
- la fonction carré(nombre) de type booléen qui est égal à vrai si le paramètre nombre est
un carré parfait, faux sinon.
- la fonction nbdiv(nombre) de type entier qui donne le nombre de diviseurs du paramètre
nombre.
62
Série d’exercices à traiter en TD
On fait appel à la fonction f avec n=3 et cmpt=0. Quelle est la valeur retournée par cet appel ?
Quelles sont les valeurs de n et de cmpt après l’exécution de cet appel ?
Exercice 2
On considère la procédure suivante :
Procédure mystere (T: Entier[1..N])
Var i, j : entier
Début
i 1
j N
Tant que (i < j) Faire
Si (T[i] = 0) Alors
i i+1
Sinon
Echanger(T[i], T[j])
j j-1
Fin Si
Fin Tant que
Fin
Où Echanger(T[i],T[j]) est la procédure qui permute les valeurs des cases d’indices i et j.
1. Que fait-elle en supposant que le tableau T ne contient que des 0 et des 1 ?
2. Modifier la procédure de façon à l'appliquer à des tableaux contenants, au plus, trois valeurs
différentes : par exemple 0, 1 et 2 (rappel : chaque élément de T doit être examiné au maximum
une fois).
3. Donner une version récursive de la procédure.
63
Série d’exercices à traiter en TD
Exercice 5 :
Ecrire une procédure itérative puis récursive permettant de renverser les éléments d’un
tableau T[1..N] de réels.
Exercice 6 :
Ecrire une fonction itérative puis récursive qui retourne Vrai si un tableau T[1..N] de réels
est trié par ordre croissant, sinon elle retourne Faux.
Exercice 2
Trouver l’ordre de grandeur des fonctions suivantes :
1. n2 + 2n ; log n2
2. log 3n ; n + 1/n
3. n + log n
Exercice 3
Parmi les relations suivantes, quelles sont celles qui sont correctes.
1. O(1) = O(10); O(1) = 10; n+1 = O(n), étant une constante
n
2. (n + 1)! = O(n!) ; 2 = O(n!), ; n! = O(2n); log nn = O(log n!); nn = O(n!)
Exercice 4
Quelle est la valeur de k à la sortie de la boucle de la portion d’algorithme ci-dessous :
k 1
Tant que (k <= n) Faire
kk*2
Fin tant que
En déduire la complexité temporelle de cette portion d’algorithme
Exercice 5
Calculer les complexités temporelles en fonction de la variable n des portions d’algorithme
ci-dessous.
Algorithme 1
d 1
Tant que (d*d <= n) Faire
dd*2
Fin Tant que
Algorithme 2
64
Série d’exercices à traiter en TD
d1
Tant que (d*d <= n) Faire
dd*2+1
Fin tant que
Algorithme 3
Pour (i0; i<n; ii+1) Faire
Pour (j0; j<n; jj+1) Faire
ss+1
Fin Pour
Fin Pour
Exercice 6
Que font les fonctions récursives suivantes :
1. Fonction mystere1( n: Entier): Entier
Début
SI (n = 0) Alors
retourner 0
Fin Si
Retourner (mystere1(n-1) + n*n)
Fin
2. Fonction mystere2(b : Reel, n: Entier): Reel
Début
SI (n = 0) Alors
retourner 1
Fin Si
retourner (1+ b*mystere2(b,n-1))
Fin
Exercice 7
Déterminer pour chacune de ces fonctions récursives sa complexité temporelle.
Exercice 8 : Puissance récursive
La fonction ci-dessous calcule récursivement nk avec n 0 et k≥ 0
FONCTION puissance( n : ENTIER, k : ENTIER) : ENTIER
DEBUT
SI ( k = 0 ) ALORS
Retourner 1
SINON
SI ( k MOD 2 = 0 ) ALORS
Retourner puissance( n*n , k DIV 2 )
SINON
Retourner n*puissance( n*n , k DIV 2 )
FIN SI
FIN SI
FIN
Déterminer la complexité temporelle de la fonction puissance(n, k)
Exercice 9
On considère un ensemble de n ≥ 2 entiers distincts stockés dans un tableau T (T n’est
pas supposé trié). Résoudre les questions suivantes :
1 - Proposer un algorithme en O(n) pour trouver deux éléments x et y de T tels que :
|x − y|≥|u − v| pour tout u, v T.
2 - Proposer un algorithme en O(n log n) pour trouver deux éléments x et y de T tels que : x y et
|x− y| |u − v| pour tout u, v T, u v.
65
Série d’exercices à traiter en TD
3 - Soit m un entier arbitraire (pas nécessairement dans T), proposer un algorithme en O(nlog n)
pour déterminer s’il existe deux éléments x et y de T tels que x + y = m.
66
Série d’exercices à traiter en TD
Exercice 2 :
FONCTION g(n : Entier) : Entier
//Données : un entier n appartenant à IN
VAR m, i, R : Entier
DEBUT
R0
mn
TANT QUE (m > 0) FAIRE
POUR (i 1 à m) FAIRE
1. RR+1
FIN POUR
POUR (i m à n) FAIRE
2. RR+1
FIN POUR
mm-1
FIN TANT QUE
Retourner R
FIN
a) Exprimez en fonction de la donnée n, le nombre de fois que la fonction g exécute les
affectations des lignes 1 et 2. Vous donnerez l'expression exacte de ce nombre ainsi que les
étapes de son calcul.
b) Donnez, en fonction de n, l'ordre de grandeur du nombre d'affectations exécutées par la
fonction g.
c) Quelle est la valeur renvoyée par g(n) ?
Exercice 3
Montrer la correction de l’algorithme de tri par insertion vu à la fin du dernier cours :
On prouvera un invariant pour chaque boucle. Donner le pire et le meilleur cas pour la complexité
et évaluer cette complexité dans les deux cas.
Exercice 4
On se donne un tableau S d’entiers triés par ordre croissant et un entier cible x. Proposer
un algorithme linéaire pour trouver s’il existe deux éléments de S dont la somme vaut x. Prouver
que la boucle termine (quelle quantité décroît à chaque itération ?) et que votre algorithme est
correct (en utilisant la méthode par invariant de boucle).
67