Introduction à la programmation 0.5em1em Cours 4

publicité
1/28
Qu’est-ce qu’un programme qui calcule bien ?
Un problème
Énoncé
Soit un ensemble de pots de poids inconnus et une balance. Comment
retourner les k pots les plus lourds grâce à la balance ?
2/28
Spécification de ce problème
Précondition
Entrée : Soient un tableau d’entiers t et un entier k.
Précondition : k est plus petit que la longueur de t.
Postcondition
Sortie : un tableau d’entiers t 0 .
Postcondition : t 0 contient les k éléments de t les plus grands.
3/28
Spécification
Précondition
Une précondition est une propriété attendue sur les entrées du programme. Si
la précondition n’est pas vérifiée alors le comportement du programme n’est
pas spécifié.
Postcondition
Une postcondition est une propriété promise sur les sorties du programme. Si
la postcondition n’est pas vérifiée alors cela signifie que le programme contient
une erreur.
4/28
Détecter une erreur
5/28
Comment détecter si un programme contient une erreur ?
Erreurs de programmation
Correction d’un programme
Un programme est correct si pour toute entrée vérifiant sa précondition alors il
produit une sortie vérifiant sa postcondition.
Preuve de correction et d’incorrection
I
On peut prouver qu’un programme est incorrect en trouvant une entrée
qui vérifie la précondition mais pour laquelle le programme produit une
sortie qui ne vérifie pas la postcondition ou bien ne produit pas de sortie
du tout.
I
On peut prouver qu’un programme est correct en faisant une
démonstration mathématique.
Valeur des tests
Un test n’est donc pas une preuve de la correction d’un programme.
6/28
Exemple de preuve de programme (hors programme)
7/28
/* Precondition : le tableau a est non vide. */
/* Postcondition : la fonction termine et renvoie un entier m tel que : */
/* Pour tout i dans les bornes de a, a[i] >= m et il existe j, a[j] = m. */
public static int min (int[] a) {
int m = a[0] ;
for (int i = 1 ; i < getArrayLength (a) ; i++) {
if (a[i] < m) {
m = a[i] ;
}
}
return m ;
}
1. Pourquoi le programme termine-t-il ?
2. Pourquoi l’entier m vérifie-t-il la postcondition ?
Terminaison du programme
Arguments pour la terminaison
I
Une boucle “for” (de la forme « i allant de 1 à N ») termine toujours.
I
Une séquence d’instructions qui terminent se termine aussi.
I
On suppose que l’appel à getArrayLength termine.
I
Le programme termine donc.
et en remplaçant la boucle “for” par la boucle “while” suivante ?
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
if (a[i] < m) {
m = a[i] ;
}
i++ ;
}
8/28
La terminaison d’une boucle “while”
9/28
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
if (a[i] < m) {
m = a[i] ;
}
i++ ;
}
La quantité “getArrayLength (a) - i” décroît strictement à chaque appel. Elle
finit donc par atteindre 0.
Or, quand elle est nulle, la condition est fausse et donc la boucle s’arrête.
L’existence de cette quantité permet de prouver que la boucle termine. Une
telle quantité est appelée un variant de la boucle.
Prouver la terminaison grâce à des variants de boucle
Variant de boucle
Un variant d’une boucle est un entier naturel qui décroit strictement à chaque
itération de la boucle.
10/28
Correction du programme
11/28
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
if (a[i] < m) {
m = a[i] ;
}
i++ ;
}
/* Pour tout k entre 0 et getArrayLength (a) - 1, m <= a[k] */
À la fin de la boucle, la postcondition doit être vraie.
Correction du programme
12/28
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
if (a[i] < m) {
m = a[i] ;
}
i++ ; /* ? */
}
/* Pour tout k entre 0 et getArrayLength (a) - 1, m <= a[k] */
Quelle propriété doit-on avoir à la fin de la dernière itération ?
Correction du programme
13/28
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
if (a[i] < m) {
m = a[i] ;
}
i++ ;
/* Pour tout k entre 0 et i - 1, m <= a[k] */
}
Il suffit de montrer que la propriété est vraie pour toutes les itérations !
Correction du programme
14/28
int m = a[0] ;
int i = 1 ;
/* Pour tout k entre 0 et i - 1, m <= a[k] */
while (i < getArrayLength (a)) {
if (a[i] < m) {
m = a[i] ;
};
i++ ;
}
La propriété est vraie la première fois que l’on rentre dans la boucle.
Correction du programme
15/28
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
/* Pour tout k entre 0 et i - 1, m <= a[k] */
if (a[i] < m) {
/* Pour tout k entre 0 et i - 1, m <= a[k] et a[i] < m */
m = a[i] ;
/* Pour tout k entre 0 et i, m = a[i] <= a[k] */
};
/* Pour tout k entre 0 et i, m <= a[k] */
i++ ;
/* Pour tout k entre 0 et i - 1, m <= a[k] */
}
En supposant qu’au début de chaque itération, la propriété est vraie, on peut
montrer qu’elle est vraie à la fin de la boucle.
Correction du programme
16/28
int m = a[0] ;
int i = 1 ;
while (i < getArrayLength (a)) {
/* Pour tout k entre 0 et i - 1, m <= a[k] */
if (a[i] < m) {
/* Pour tout k entre 0 et i - 1, m <= a[k] et a[i] < m */
m = a[i] ;
/* Pour tout k entre 0 et i, m = a[i] <= a[k] */
};
/* Pour tout k entre 0 et i, m <= a[k] */
i++ ;
/* Pour tout k entre 0 et i - 1, m <= a[k] */
}
Par récurrence sur le nombre d’itérations de la boucle,
on en conclut que la propriété est vraie pour toute itération !
Exemple : Le problème de l’urne
17/28
On place des boules noires et blanches dans une urne. On applique ensuite
le processus suivant tant que possible : on tire deux boules au hasard et si
elles sont de la même couleur alors on les jette toutes les deux et on rajoute
une boule noire dans l’urne. Sinon, si elles sont de couleurs différentes, on
remet la blanche dans l’urne et on jette la noire.
1. Est-ce que ce processus termine ?
2. À la fin du processus, combien reste-t-il de boules dans l’urne ?
3. À la fin du processus, de quelle(s) couleur(s) sont les boules dans l’urne ?
18/28
Comment savoir si un programme est meilleur qu’un autre ?
Coût d’un programme
Définition
On peut comparer deux programmes en comparant le temps qu’ils mettent à
résoudre le même problème. On pourrait utiliser l’unité de temps de la seconde
et chronométrant les programmes mais on obtiendrait alors une comparaison
peu reproductible. Une technique plus intéressante consiste à compter le
nombre de fois qu’une opération élémentaire est utilisée par les deux
programmes.
Le coût d’un programme est alors la fonction C(I) qui associe le nombre
d’opérations élémentaires nécessaires au traitement d’une donnée d’entrée I.
Exemple
Dans le cas du problème de la recherche des k poids les plus importants
parmi n poids, nous pouvons compter le nombre de comparaisons effectuées
par chaque programme. Dans cet exemple, la fonction de coût est donc une
fonction de k et de n.
19/28
Algorithme
Algorithme
Un algorithme est une méthode de calcul visant à la résolution d’un problème.
On peut décrire un algorithme dans un pseudo-langage de programmation.
Cela permet d’ignorer certains détails d’implémentation pour simplifier la
preuve de la terminaison, de la correction ou de l’étude de la fonction de coût
de l’algorithme.
20/28
Exemple d’algorithme en pseudo-code
21/28
Entrée : t un tableau d’entiers
Pour i allant de 1 à k :
Pour j allant de 1 à n − i + 1 :
Échanger les cases i et j de t si t[i] ≥ t[j]
Complexité algorithmique
Classes de comparaison des algorithmes
Les performances de deux algorithmes peuvent différer à une constante près
(ce qui n’est pas grand chose), ou de façon plus significative.
On peut utiliser les notations de Landau pour classer les fonctions de coût
en faisant abstraction des constantes.
Exemples
22/28
I
Le coût de la recherche du minimum dans un tableau est Θ(n).
I
Le coût de kbestBubble est k · n −
I
Le coût de kbestBubble est en Θ(n) pour k fixé.
I
Le coût de kbestTournament est n − 1 + k · log2 (n).
I
Le coût de l’initialisation de kbestTournament est en Θ(n) pour k fixé.
I
Le coût du calcul de chaque élément de kbest est en Θ(log(n)) pour k
fixé.
k·(k+1)
.
2
Un nouveau problème : le bon parenthésage
23/28
{ ... ( ... [ ] ... ) ... }
Soit un programme source formé par une séquence de lettres a0 . . . aN .
Comment déterminer si les accolades ouvrantes, les parenthèses ouvrantes
et les crochets ouvrants sont correctement fermés ?
Une pile
Opérations
On souhaite qu’une variable se comporte comme une pile, c’est-à-dire :
1. Qu’à sa création, la pile soit vide.
2. Que l’on puisse pousser un élément au sommet de la pile.
3. Que l’on puisse retirer le dernier élément poussé au sommet de la pile.
4. Que l’on puisse tester si une pile est vide.
stack = createEmptyStack () ;
push (stack, 42) ;
push (stack, 21) ;
b1 = isEmpty (stack) ; // Ici, b1 vaut “false”
x = pop (stack) ; // Ici, x vaut 21.
y = pop (stack) ; // Ici, y vaut 42.
b2 = isEmpty (stack) ; // Ici, b2 vaut “true”
24/28
Retour sur le bon parenthésage
25/28
Comment vérifier le bon parenthésage à l’aide d’une pile ?
Organiser un tableau sous la forme d’une pile
26/28
Comment utiliser un tableau pour y représenter une pile ?
Une structure de données
Structure de données
On appelle structure de données une discipline d’organisation des données en
mémoire qui permet de réaliser des opérations efficacement à partir de ces
données ou sur ces données.
La discipline d’organisation de la mémoire est souvent représentée par une
propriété qui est toujours vraie sur les données entre deux opérations
successives. Cette propriété s’appelle un invariant.
Exemple
Dans la pile représentée par un tableau, l’invariant de la pile est le fait que la
première case du tableau représente le sommet de la pile et que les cases
suivantes représentent les éléments de la pile lue du fond de la pile vers son
sommet.
27/28
Chercher dans un dictionnaire
28/28
Qu’est-ce qu’une structure de donnée de dictionnaire
et comment représenter un dictionnaire en mémoire ?
Téléchargement