22bis Cours Correction
Questions fondamentales⚓︎
Les trois questions fondamentales à se poser à propos de tout algorithme sont les suivantes :
- donne-t-il un résultat ou ne s’arrête-t-il jamais ? (Notion de terminaison) ;
- donne-t-il le résultat attendu ou calcule-t-il autre chose ? (Notion de correction) ;
- donne-t-il le résultat en un temps raisonnable ou faut-il attendre très longtemps ? (Notion de complexité).
Correction⚓︎
Comment prouve-t-on qu’un algorithme fait bien ce qui est attendu de lui ? Basons-nous sur un exemple : celui de la division euclidienne.
def division(a : int,b : int):
"""
Entrée : a et b sont deux entiers positifs non nuls
Sortie : quotient q et reste r de la division euclidienne de a par b
"""
q = 0
r = a
while r >= b:
q = q + 1
r = r - b
return (q, r)
print(division(22, 3))
Écrire la trace de l’algorithme pour \(a=19\) et \(b=3\).
Trace de l'algorithme
- \(a=19\) et \(b=3\)
- \(q = 0\)
- \(r = a = 19\)
- \(r \geq b\) ? \(19 \geq 3\) ? oui
- \(q = q + 1 = 2\)
- \(r = r - b = 19 - 3 = 16\)
- \(r \geq b\) ? \(16 \geq 3\) ? oui
- \(q = q + 1 = 3\)
- \(r = r - b = 16 - 3 = 13\)
- \(r \geq b\) ? \(13 \geq 3\) ? oui
- \(q = q + 1 = 4\)
- \(r = r - b = 13 - 3 = 10\)
- \(r \geq b\) ? \(10 \geq 3\) ? oui
- \(q = q + 1 = 5\)
- \(r = r - b = 10 - 3 = 7\)
- \(r \geq b\) ? \(7 \geq 3\) ? oui
- \(q = q + 1 = 6\)
- \(r = r - b = 7 - 3 = 4\)
- \(r \geq b\) ? \(4 \geq 3\) ? oui
- \(q = q + 1 = 7\)
- \(r = r - b = 4 - 3 = 1\)
- \(r \geq b\) ? \(1 \geq 3\) ? non : fin de la boucle while
- return \((q = 7, r = 1)\)
Preuve de la correction : l’invariant de boucle⚓︎
Un invariant de boucle est une proposition qui :
- est vérifiée à l’initialisation de la boucle ;
- reste vraie à chaque itération de la boucle.
- après le dernier tour de boucle, la véracité de l'invariant de boucle correspond à ce qui est attendu de l'algorithme (correction de l'algorithme)
Remarque. Cette méthode permet de décrire une proposition qui sera vraie à l’issue de la boucle. Un invariant de boucle bien choisi doit, conjointement à la condition d’arrêt de la boucle, permettre de montrer que l’algorithme est correct, c’est-à-dire qu’il fait bien ce que l’on attend de lui.
Dans notre exemple ci-dessus de la division euclidienne, l’invariant de boucle est \(a=bq+r\). En effet :
- À l’initialisation, \(q=0\) et \(r=a\), ainsi \(bq+r=b\times0+a=a\).
- À chaque tour de boucle, \(q\) devient \(q+1\) et \(r\) devient \(r−b\). Si au début de la boucle on \(a=bq+r\), après les affectations on obtient : \(b(q+1)+(r-b)=bq+b+r-b=bq+r\). Donc \(a=bq+r\) reste vraie à chaque itération de la boucle.
Ainsi, \(a=bq+r\) est notre invariant de boucle. À l’issue de l’algorithme, \(a=bq+r\) et comme la boucle s’est terminée cela signifie aussi que \(r<b\) (condition d’arrêt de la boucle while). C’est ce que l’on souhaitait obtenir. Cet algorithme est donc correct.
Méthode⚓︎
- Choisir et exprimer un invariant judicieux.
- Initialisation : Démontrer qu’il est vérifié avant d’entrer dans la boucle (utiliser les préconditions).
- Conservation : Démontrer que s’il est vérifié au début d’une itération quelconque, il l’est aussi au début de l’itération suivante (utiliser le corps de la boucle).
- Conclusion : Instancier l’invariant en sortie de boucle et en déduire une postcondition (utiliser la négation de la condition du
while).