Algorithmes et programmation

publicité
Algorithmes et programmation
4 janvier 2015
1 Introduction
1.1 Algorithme et programme
Un algorithme est une suite d’opérations élémentaires constituant un schéma de
calcul et de résolution en un temps fini (et acceptable) d’un problème donné.
— Les opérations élémentaires correspondent à une suite d’instructions visant à
tranformer les données pour obtenir le résultat.
— Un algorithme est indépendant de tout langage de programmation. Il est écrit en
pseudo-langage.
Un programme est l’implémentation (ou implantation) d’un algorithme dans un
langage de programmation donné.
Un algorithme n’est pas un programme. Par exemple, dans un algorithme, on ne
précise pas comment sont formatées les variables utilisées (polynôme, tableau...).
1.2 Portée des variables
— Une variable locale est une variable utilisée comme paramètre d’une procédure,
ou qui est affectée à l’intérieur d’une procédure.
Une fois la procédure exécutée, la variable locale a perdu la valeur qu’elle avait
au sein de la procédure.
— On dit que la portée d’une variable locale est limitée au corps de la procédure où
elle est utilisée.
— Une variable globale est une variable utilisée à l’intérieur d’une fonction sans y
être affectée ou qui est déclarée comme globale à l’intérieur d’une procédure.
— On dit que la portée d’une variable globale s’étend à l’ensemble du programme.
1
def f ( ) :
global a
a=a+1
c=2*a
return a+b+c
Dans ce programme, a et b sont globales mais c est locale.
x=1
def plusun ( ) :
x+=1
return x
ERREUR car, x étant affecté à l’intérieur de la procédure, x est considérée comme
variable locale qui n’est donc pas initialisée donc ’x=x+1’ donne une erreur.
Par contre
x=1
def plusun ( ) :
return 1+x
renvoie 2. x est considérée comme variable globale.
def f ( ) :
global x
x=2
def g ( ) :
x=3
Ici, on a deux variables x indépendantes : une globale dans f et une locale dans g.
Dans g, on ne peut plus accéder à la variable globale x car masquée par la variable
locale x.
x=1
f()
g()
print x
affiche 2.
On utilisera les variables globales pour les constantes du problème.
On évitera d’avoir à recourir à l’instruction global .
On évitera de donner des noms de variables identiques aux variables locales et aux
variables globales.
On donnera des noms de variables longs et explicites pour les variables globales, et
des noms courts pour les variables locales.
Pour écrire un algorithme, on doit procéder de la manière suivante :
1. définir la spécification (ie le contrat) formée de :
— La précondition,
— La postcondition,
2
2. écrire l’algorithme
3. Justifier sa correction composée de :
— la correction partielle (sous réserve de terminaison)
— La terminaison .
— Le couple précondition/postcondition représente un contrat : on garantie que
sous réserve que la précondition est vérifiée au début, la postcondition le sera à
la fin.
— Justifier la correction partielle du programme consiste à démontrer que l’algorithme respecte le contrat pour toutes les valeurs pour lesquelles l’algorithme
termine.
Dans le cas d’une boucle ’for’ ou ’while’ :
— définir un invariant de boucle pour une boucle for ou while qui est une
propriété qui est vraie à l’entrée de la boucle et qui, si elle est vraie en début
de tour de boucle, est vraie à la fin du tour. Dans ce cas, à la sortie du dernier
tour de boucle, on obtient le résultat cherché.
— justifier cet invariant de boucle.
— Prouver la correction partielle du programme ie montrer que
(Precond + condition d’arrêt de la boucle ⇒ Postcond ).
Pour une boucle ’while cond’, la condition d’arrêt est ’non cond’
Pour une boucle ’for i in range(n)’, la condition d’arrêt est ’i=n-1’.
La question de la terminaison ne se pose que pour une boucle «tant que condition
».
On la justifie en :
— définissant un variant de boucle qui est une valeur entière, positive et décroissant strictement à chaque tour de boucle ;
— justifiant ce variant de boucle pour en déduire la terminaison (ie condition fausse)
2 Exemples classiques
2.1 Division euclidienne
Calculer quotient et reste d’une division euclidienne en utilisant pour seules opérations arithmétiques l’addition et la soustraction.
2.1.1 Spécifier le problème
Étant donné deux entiers n et d, avec d > 0 et n ≥ 0, on veut retourner l’unique
couple d’entiers (q, r) tel que n = dq + r et 0 ≤ r < d.
3
2.1.2 Écrire un algorithme
1
2
3
4
5
6
Précondition: n ∈ N et d ∈ Z∗
Postcondition: q ∈ N, r ∈ [[0, d[[ et n = dq + r
q ← 0;
r ← a;
tant que r ≥ d faire
r ← r − d;
q ←q+1
fin
2.1.3 Correction partielle d’un algorithme
Ajout d’un invariant :
Précondition: n ∈ N et d ∈ Z \ { 0 }
Postcondition: q ∈ N, r ∈ [[0, d[[ et n = dq + r
1
2
3
4
5
6
7
q ← 0;
r ← n;
tant que r ≥ d faire
invariant n = dq + r;
r ← r − d;
q ←q+1
fin
Justification de l’invariant L’invariant de boucle «tant que» ligne 4 est vérifié car
1. Quand on arrive à la boucle, q = 0 et r = n, donc n = dq + r ;
2. Si l’invariant est vérifié au début d’un tour de boucle alors il est encore vérifié à
la fin de ce même tour. En effet, si on note qk et rk les valeurs de q et r au début
d’un tour de boucle, on a rk+1 = rk − d et qk+1 = qk + 1 donc dqk+1 + rk+1 =
dqk + d + rk − d = dqk + rk = n ce qui justifie l’invariant.
Correction partielle du programme À la sortie de la boucle on a donc n = dq + r et
r < d.
(Il nous manque r ≥ 0 : il aurait suffit de l’ajouter dans l’invariant de boucle).
Conclusion Si la précondition est vérifiée au début de l’exécution de l’algorithme alors
la postcondition est vérifiée à la fin. . . sous réserve que cette exécution se termine !
L’algorithme est donc partiellement correct.
Remarquez que notre algorithme ne termine pas toujours : prendre d = −1 par
exemple !
4
2.1.4 Justifier la terminaison d’un algorithme (impératif)
Risque de non-terminaison : les boucles «tant que».
Renforçons la précondition : on prendra d ∈ N∗ .
Précondition: n ∈ N et d ∈ N∗
Postcondition: q ∈ N, r ∈ [[0, d[[ et n = dq + r
1
2
3
4
5
6
7
8
q ← 0;
r ← a;
tant que r ≥ d faire
invariant n = dq+r;
variant r - d;
r ← r − d;
q ←q+1
fin
Sous la précondition n ∈ N, r et d ont des valeurs entières.
De plus, la condition de boucle est r ≥ d donc dans le corps de la boucle, r − d est
toujours positif.
Enfin, r diminue de d à chaque tour de boucle et d > 0 (précondition d ∈ N∗ ).
Donc r − d est un entier positif diminuant strictement à chaque tour de boucle.
2.1.5 Programme
def diveuclide ( n , d ) :
" " " Retourne un c o u p l e ( q , r ) avec
q n a t u r e l , 0 <= r < d e t n == d * q + r
Pr é c o n d i t i o n : n e n t i e r n a t u r e l , d n a t u r e l non nul . " " "
q = 0
r = a
while r >= d :
#n == d * q + r
# v a r i a n t : r−d
r = r − d
q = q + 1
return q , r
2.2 Recherche du minimum d’un tableau
2.2.1 Spécification
On veut rechercher l’indice du minimum d’un tableau.
Étant donné un tableau de nombres t non nécessairement trié contenant au moins
un élément, retourner un indice i ∈ [[0, len(t)[[ tel que
t[i] =
min
j∈[[0,len(t)[[
5
(t[j])
2.2.2 Algorithme
Précondition: t tableau avec len(t) > 0
Postcondition: t[i] = minj∈[[0,len(t)[[ (t[j])
1
2
3
4
5
6
7
8
9
i ← 0;
m ← t[0];
pour k de 1 (inclus) à len(t) (exclu) faire
invariant m = t[i] = minj∈[[0,k[[ (t[j]);
si t[k] < m alors
i ← k;
m ← t[k];
fin
fin
2.2.3 Justification de l’invariant
Ici, notons, pour k entier non nul, P (k) la proposition
« m = t[i] = minj∈[[0,k[[ (t[j]) ».
1. On a clairement P (1) est vrai quand on arrive à la boucle (ligne 3) puisqu’alors
on a i = 0 et m = t[0], donc m = t[i] = t[0] = minj∈[[0,1[[ (t[j]).
2. Si au début d’un tour de boucle, on a P (k), alors ou bien t[k] < m = minj∈[[0,k[[ (t[j])
alors à la fin du tour, i = k, donc m = t[i] = t[k] = minj∈[[0,k+1[[ (t[j]) ou alors
m ≥ t[k] et alors m = t[i] = minj∈[[0,k+1[[ (t[j]). Dans les deux cas, on a P (k + 1) à
la fin du tour.
Donc on a P (len(t)) à la fin de l’exécution de la boucle, qui est exactement la
postcondition que l’on voulait montrer.
L’algorithme est donc (totalement) correct.
2.2.4 Programme
def iminimum ( t ) :
" " " Retourne un i n d i c e i t e l que t [ i ] s o i t l e minimum de t .
Pr é c o n d i t i o n : t e s t un t a b l e a u non−v i d e de nombres " " "
i=0
m = t [0]
f o r k in range ( 1 , len ( t ) ) :
# t [ i ] == min ( t [ j ] f o r j i n r a n g e ( k ) )
i f t [ k ] <m :
m = t[k]
i = k
return i
6
2.3 Pgcd
2.3.1 Algorithme
Étant donné un entier relatif α, on note D(α) l’ensemble de ses diviseurs. En particulier que D(0) = Z. Étant donnés deux entiers α et β, on note D(α, β) leurs diviseurs
communs : D(α, β) = D(α) ∩ D(β).
Précondition: (a, b) ∈ Z2 avec (a, b) 6= (0, 0)
Postcondition: R0 = a ∧ b
1
2
3
4
5
6
7
8
R0 ← |a|;
R1 ← |b|;
tant que R1 > 0 faire
invariant D(R0 , R1 ) = D(a, b) et R0 ∈ N et R1 ∈ N et (R0 , R1 ) 6= (0, 0);
variant R1 ;
(q, R2 ) ← diveuclide(R0 , R1 );
(R0 , R1 ) ← (R1 , R2 );
fin
2.3.2 Invariant
L’invariant de boucle est vérifié car :
1. D’après la précondition, (a, b) ∈ Z2 . Donc D(R0 , R1 ) = D(|a|) ∩ D(|b|) = D(a, b)
et R0 = |a| ∈ N et R1 = |b| ∈ N et (R1 , R0 ) = (|a| , |b|) 6= (0, 0).
2. Supposons qu’au début d’un tour de boucle l’invariant est vérifié et notons (α, β)
la valeur de (R0 , R1 ) au début du tour, (α0 , β 0 ) sa valeur à la fin du tour et γ et δ
le quotient et le reste dans la division euclidienne de α par β.
On a α = γβ + δ et (α0 , β 0 ) = (β, δ) , donc on a α0 ∈ N et β 0 ∈ N et de plus
D(α0 , β 0 ) = D(β, α − γβ) = D(α, β) = D(a, b). Donc à la fin du tour, on a
D(R0 , R1 ) = D(a, b). Enfin, comme on a effectué ce tour de boucle c’est que la
condition du "while" est vérifiée, donc β > 0, donc α0 > 0, donc (R0 , R1 ) 6= (0, 0).
2.3.3 Correction partielle
En fin d’exécution de la boucle «tant que», la condition de boucle n’est plus vérifiée,
donc on a R1 ≤ 0. De plus on a D(R0 , R1 ) = D(a, b) et R1 ∈ N et R0 ∈ N. Donc on
a R1 = 0 et D(a, b) = D(R0 , 0) = D(R0 ). Donc R0 = a ∧ b. On a donc la correction
partielle.
2.3.4 Terminaison
Comme on l’a vu, R1 est positif au début de chaque tour de boucle. De plus lors d’un
tour de boucle, R1 voit sa valeur β remplacée par un reste dans la division euclidienne
par β, donc décroît strictement.
7
D’où la correction totale de l’algorithme.
2.3.5 Programme
def pgcd ( a , b ) :
" " " Retourne l e pgcd de a e t b .
Pr é c o n d i t i o n : a e t b e n t i e r s r e l a t i f s
non t o u s l e s deux n u l s " " "
R0=abs ( a )
R1=abs ( b )
while R1 >0:
# D( R0 , R1)=D( a , b ) e t R0 , R1 e n t i e r s n a t u r e l s
q , R2=diveuclide ( R0 , R1 )
R0 , R1=R1 , R2
return R0
2.4 Produit matriciel
2.4.1 Algorithme
Étant données deux matrices carrées A et B d’ordre n, il s’agit de définir la matrice
produit A × B définie par
X
∀i, j ∈ [[0, n[[, (AB)i,j =
(A)i,k (B)k,j
k∈[[0,n[[
NB : on prendra les indices de ligne et colonne dans [[0, n[[ (contrairement à l’usage
habituel en mathématiques).
1
2
3
4
5
6
7
8
Précondition: A et B sont deux tableaux bidimensionnels de n2 réels.
Postcondition: C = A × B.
Initialiser un tableau C de dimensions n × n;
pour i de 0 (inclus) à n (exclu) faire
invariant C[i0 , .] vaut la ligne i0 de AB pour tout i0 ∈ [[0, i[[;
pour j de 0 (inclus) à n (exclu) faire
invariant C[i, j 0 ] vaut l’élément (i, j 0 ) de AB pour tout j 0 ∈ [[0, j[[;
C[i, j] ← 0;
pour k de 0 à n (exclu) faire
P
invariant C[i, j] =
A[i, p]B[p, j];
p∈[[0,k[[
C[i, j] ← C[i, j] + A[i, k] × B[k, j]
9
fin
10
fin
11
12
fin
2.4.2 Explications
— L’invariant annoncé pour la boucle la plus interne (celle sur k) est
C[i, j] =
X
p∈[[0,k[[
8
A[i, p]B[p, j]
Il est vrai au début du tour de boucle pour k = 0 car on a initialisé C[i, j] à 0 et
la somme est vide donc nulle. De plus s’il est vrai au début d’un tour de boucle
quelconque (avec k ∈ [[0, n[[), alors on exécute
C[i, j] ← C[i, j] + A[i, k] × B[k, j]
donc à la fin du tour, C[i, j] vaut

C[i, j] = 

X
A[i, p] × B[p, j] + A[i, k] × B[k, j]
p∈[[0,k[[
=
X
A[i, p] × B[p, j]
p∈[[0,k+1[[
L’invariant est donc vérifié, et à la sortie de cette boucle la plus interne, on a
C[i, j] =
X
A[i, p]B[p, j] = (AB)i,j
p∈[[0,n[[
— L’invariant de la boucle «pour j» est vrai au début du tour où j = 0 car alors [[0, j[[
est vide. De plus si au début d’un tour de boucle, C[i, j 0 ] = (AB)i,j 0 pour tout
j 0 ∈ [[0, j[[, alors à la fin du tour du boucle, on a de plus C[i, j] = (AB)i,j , donc on
a bien C[i, j 0 ] = (AB)i,j 0 pour tout j 0 ∈ [[0, j + 1[[.
En particulier à la fin de chaque exécution de la boucle, C[i, j 0 ] = (AB)i,j 0 pour
tout j 0 ∈ [[0, n[[, donc C[i, .] vaut la ligne i de AB.
— De la même façon, l’invariant de la boucle «pour i» est vrai au début du tour où
i = 0 car [[0, i[[ est alors vide. De plus si au début d’un tour de boucle, C[i0 , .] vaut
la ligne i0 de AB pour tout i0 ∈ [[0, i[[, alors à la fin de ce tour, on a de plus C[i, .]
égal à la ligne i de AB, donc C[i0 , ] vaut la ligne i0 de AB pour tout i0 ∈ [[0, i + 1[[.
En particulier, à la fin de l’exécution de la boucle la plus externe, on a C[i0 , .] égal
à la ligne i0 de AB pour tout i0 ∈ [[0, n[[. C est donc égal à AB.
2.4.3 Programme
def prodmat ( A , B ) :
" " " Retourne l e p r o d u i t m a t r i c i e l de A e t B .
Pr é c o n d i t i o n : A e t B s o n t deux m a t r i c e s c a r r é e s
de même t a i l l e n . " " "
n = len ( A [ 0 ] )
C = [ None ] * n
f o r i in range ( n ) :
C [ i ] = [ None ] * n
f o r j in range ( n ) :
C[i , j] = 0
f o r k in range ( n ) :
C [ i , j ] += A [ i , k ] * B [ k , j ]
return C
9
2.5 Exercices
Prouver la correction de l’algorithme suivant de calcul de a.b :
1
2
3
4
5
6
7
8
9
10
Précondition: a et b deux entiers naturels
Postcondition: p = ab
x ← a;
y ← b;
p ← 0;
tant que y > 0 faire
si y impair alors
p←p+x
fin
x ← 2 ∗ x;
y ← E(y/2)
fin
Prouver la correction de l’algorithme suivant de calcul de ap :
1
2
3
4
5
6
7
8
9
10
Précondition: a un nombre et p un entier naturel
Postcondition: r = a ∗ ∗p
r ← 1;
n ← p;
x ← a;
tant que n 6= 0 faire
si n impair alors
r ←r∗x
fin
x ← x ∗ x;
n ← E(n/2)
fin
10
Téléchargement