Vous êtes sur la page 1sur 11

Memento INF1042

Semestre 2 Année académique 2019-2020


Professeur Maurice TCHUENTE

Section 1: Boucles et application aux algorithmes de recherche

1.1. Les Boucles

Définition 1.1 Une boucle est une construction algorithmique qui permet d’exécuter de
manière répétitive une instruction ou une suite d’instructions.

Il existe trois formes de boucle :


 La boucle Pour
 La boucle Jusqu’à … faire
 La boucle Tantque … faire
 La boucle Faire … jusqu’à

Boucle Pour

Pour Indice := Valeur_Initiale à Valeur_Finale faire


<Instruction>

Ceci s’applique lorsqu’on connaît à l’avance le nombre d’exécutions et les éléments qui
seront traités. L’instruction est exécutée pour les valeurs de l’indice allant de
Valeur_Initiale à Valeur_Finale

Boucle Tantque … faire

Tantque <Condition_de_Continuation> faire


<Instruction>

Le contrôle se fait par la condition de continuation et avant exécution de l’instruction.


Tant que cette condition est vraie on exécute l’instruction.

Boucle Jusqu’à … faire

Jusqu’à <Condition de Sortie> faire <Instruction>

Le contrôle se fait par la condition de continuation et avant exécution de l’instruction.


Tant que cette condition n’est pas vraie on exécute l’instruction.

Boucle Faire … jusqu’à

Faire <Instruction> Jusqu’à <Condition de Sortie>

Le contrôle se fait par la condition de sortie et après chaque exécution de l’instruction.


Lorsque cette condition est vraie on s’arrête. Pour cette forme de boucle, l’instruction est
donc exécutée au moins une fois.
Exercice 1.1
Montrer comment on peut utiliser la forme

Jusqu’à <Condition de Sortie> faire <Instruction>

Pour réaliser les trois autres formes de boucles

Dans la suite nous privilégions la forme

Jusqu’à <Condition de Sortie> faire <Instruction>

car elle a l’avantage d’indiquer clairement la condition de sortie de la boucle.

1.2. Technique de conception des algorithmes basés sur les boucles

Pour concevoir un algorithme basé sur une boucle pour la résolution d’un problème, il
est conseillé de commencer par bricoler sur un petit exemple en respectant la structure
suivante :

1. Identifier le paramètre permettant de décrire la famille d’instructions à exécuter


2. Déterminer la valeur initiale v1 du paramètre
3. Identifier les actions permettant d’initialiser les traitements pour résoudre le
problème
4. Action avec paramètre v1 valeur initiale du paramètre 
5. Action avec paramètre v2 ;

6. Action avec paramètre vi ; valeur générique du paramètre 

7. Action avec paramètre v_final ; valeur du paramètre correspondant à la
condition de sortie 
8. Actions permettant de finaliser les traitements pour résoudre le problème posé.

On peut ensuite mettre cet algorithme sous la forme suivante :

La forme générique d’un algorithme basé sur une boucle est donc la suivante

<Initialisations : préparent les traitements, donnent la valeur initiale au paramètre p>


Jusqu’à <Condition de Sortie(p) > faire
début
<Instructions comportant un traitement et la progression du paramètre p>
fin ;
<Actions qui finalisent les traitements>

1.2.1 Application à la recherche séquentielle de val dans un vecteur V[1..n]

On désire déterminer le plus petit indice i correspondant à une occurrence de val dans un
vecteur V[1..n]. Une variable booléenne trouvé, indiquera si une telle occurrence existe.
L’algorithme est le suivant :

trouve := faux ; i := 1 ; { Initialisations}


jusqu’à trouve ou (i > n) faire
si V[i] = val alors trouve := vrai sinon i := i+1.

Dans cet algorithme, la condition de sortie comporte un test sur trouve et un test
sur i. On peut économiser l’un de ces tests en utilisant une sentinelle comme
indiqué ci-dessous, où on désire déterminer le plus grand indice i correspondant à une
occurrence de val dans un vecteur V[1..n]. Une variable booléenne trouvé, indiquera si une
telle occurrence existe.

L’idée est de placer la valeur cherchée val dans la position où l’on se retrouvera
si elle n’est pas dans V[1..n], c-à-d en position 0. On est alors certain de trouver val
 soit dans une position i > 0, ce qui correspond à trouve = vrai,
 soit en position i = 0, ce qui correspond à trouve = faux.

Recherche séquentielle avec sentinelle de val dans un vecteur V[1..n]

V[0] := val ; i := n ; { Initialisations }


jusqu’à V[i] = val faire
i := i-1 ;
trouve := i > 0.

Exercice 1.2
1.2.a Montrer que la comparaison lexicographique entre deux vecteurs V[i..j] et W[i..j]
peut se ramener à un problème de recherche que l’on précisera
1.2.b Donner l’algorithme correspondant à la question 1.2.a.
1.2.3 Donner un algorithme qui cherche dans un vecteur W[1..n], un sous-vecteur W[i..i+n-1]
égal à V[1..n].

Exercice 1.3
Donner un algorithme qui fusionne deux vecteurs triés U[1..n] et V[1..m] en un vecteur trié
W[1..n+m].
Indication : Une solution simple comporte trois boucles

Exercice 1.4
Donner un algorithme qui imprime ligne par ligne, les éléments d'une matrice triangulaire
inférieure d’ordre n.

Exercice 1.5 (on suppose que n est impair)


1.5.1 Donner un algorithme très simple qui calcule le minimum et le maximum d’un vecteur
V[1..n].
1.5.2 Quel est le nombre de comparaisons effectuées par l’algorithme de la question 5.1 ?
On considère maintenant un algorithme qui calcule le maximum et le minimum de V[1..n] en
traitant successivement les couples (V[2i], V[2i+1]), pour i = 1, … , n div 2.
1.5.3 Montrer que si on connaît le maximum et le minimum de V[1..2i-1], alors on peut
calculer en 3 opérations, le minimum et le maximum de V[1..2i+1].
1.5.4 Quel est le nombre de comparaisons effectuées par l’algorithme de la question 5.3 ?

Exercice 1.6 Elimination de Gauss


Dans l’algorithme de Gauss pour résoudre un système linéaire Ax = b, on commence par
transformer ce système linéaire en un système triangulaire équivalent, par combinaison de
lignes. Ensuite on résout le système triangulaire supérieur. Dans la suite on suppose, pour
simplifier les notations, que b correspond à la (n+1)ème colonne de A.
1.6.1 Compléter l’algorithme d’élimination ci-dessous en supposant qu’il n’y a pas de pivot
nul
Pour k := … à … faire
Elimination de ak+1,k, … , ank en utilisant akk 
Pour i := … à … faire
Elimination de ai,k
début

Pour j := … à … faire
fin ;

1.6.2 Compléter l’algorithme ci-dessous pour la résolution d’un système triangulaire obtenu à
la question précédente.
xn := … ;
Pour i := … à … faire
Calcul de xi 
début
xi := … ;
Pour j := … à … faire xi := xi - … ;
xi := … ;
fin
Problème A (Tri d’un vecteur)
On considère un vecteur V[1..n].
A.1 Compléter la procédure Select_Max ci-dessous, qui sélectionne le maximum de V[1..i] et
le met en position i par permutation avec V[i].
A.2 Compléter la procédure Tri_Select_Max ci-dessous, qui trie un vecteur V[1..n] dans
l’ordre croissant.

On se propose d’analyser le nombre d’opérations (comparaisons, affectations) effectuées par


cet algorithme

A.4 Montrer que


 le nombre de comparaisons effectuées par Select_Max est i-1
 le nombre de comparaisons effectuées par Tri_Select_Max est n(n-1)/2.

On suppose dans la suite que V[1..n] est un vecteur d’enregistrements représentant les
résultats d’examen, et de type indiqué ci-dessous
Record
Nom : char(10)
Note : integer
end ;
Au départ, les enregistrements sont triés par ordre alphabétique des noms. Après saisie des
notes, les résultats doivent être triés par ordre de mérite.

Un algorithme de tri est dit stable s’il préserve l’ordre des éléments ex-aequo. Autrement dit,
à partir d’un fichier trié par ordre alphabétique, deux étudiants ayant la même note doivent
après le tri, apparaître dans l’ordre alphabétique.

A.5 Donner un exemple montrant que Tri_Select_Max n’est pas stable

A.6 Compléter la procédure Comp_Echange_Max ci-dessous, qui effectue des comparaisons-


échanges entre V[1] et V[2], puis entre V[2] et V[3], … , puis entre V[i-1] et V[i], pour
placer le maximum de V[1..i] en position i.

A.7 Déduire un algorithme de tri du vecteur V[1..n]

A.8 Montrer que, si dans la question précédente, la dernière comparaison-échange concerne


V[j] et V[j+1], alors à la fin de la procédure on a V[j+1]  V[j+2]  …  V[i].

A.9 En tenant compte de la question A.8, compléter la procédure TRI_COMP_ECH ci-


dessous pour le tri efficace du vecteur V[1..n], c-à-d dans lequel les boucles ne portent pas
systématiquement sur V[1..n], V[1..n-1], … V[1..i], … V[1..2]

A.10 Montrer que le tri par comparaisons-échanges est stable

Les algorithmes vus jusqu’ici construisent au fur et à mesure des sous-vecteurs triés V[i..n] de
plus en plus grands, c-à-c correspondant 0 des valeurs décroissantes de i.

On s’intéresse maintenant au tri par insertion qui construit au fur et à mesure des sous-
vecteurs triés V[1..i] de plus en plus grands.

A.11 Compléter la procédure Insertion ci-dessous qui, à partir d’un sous-vecteur trié V[1..i-1],
crée un vecteur trié V[1..i], par insertion de V[i, en faisant des comparaisons-échanges V[i-
1]V[i], V[i-2] V[i], V[i-3] V[i], …. BV noter la présence de la position sentinelle 0.

A.12 Déduire une procédure Tri_Insertion pour le tri d’un vecteur V[1..n].

A.13 Dire pourquoi Tri_Insertion est stable

On se propose maintenant d’analyser les performances des algorithmes de tri qui effectuent
des comparaisons uniquement entre des éléments consécutifs V[i] V[i+1].

A.14 Montrer que dans un tel algorithme, deux éléments initialement situés en positions i, j
avec i < j, et qui à la fin se retrouvent respectivement dans les positions r, s, r > s, ont
forcément fait l’objet d’une comparaison-échange au cours de l’exécution.

A.15 Déduire que ces algorithmes sont stables


Dans un vecteur V[1..n], on appelle inversion, un couple (V[i], V[j]) tel que i < j et V[i] >
V[j].
A.16 Montrer que dans de tels algorithmes, les échanges correspondent exactement aux
inversions, et déduire que le nombre de comparaisons-échanges est égal au nombre
d’inversions + O(n).

Nous nous intéressons maintenant au nombre moyen d’inversions d’une permutation. A cet
effet, on considère l’ensemble des vecteurs correspondant aux permutations de n éléments
distincts notés e1, e2, … , en, muni de la loi uniforme. Pour une permutation particulière, on
note Ni le nombre d’inversions ayant pour élément de gauche ei.

A.17 Montrer que Ni  [0, i-1]

A.18 Montrer que sur l’ensemble des permutations, Ni est une variable aléatoire uniforme, c-
à-d la probabilité qu’une permutation ait k inversions, k  [0, i-1], est 1/i.
Indication : Compter le nombre de permutations telles que Ni = k, et diviser par le nombre
total des permutations.

A.19 Déduire que le nombre moyen d’inversions d’une permutation d’ordre n est n(n-1)/2.

Ceci montre qu’un algorithme qui effectue des comparaisons uniquement entre des éléments
consécutifs V[i] V[i+1], a une performance en moyenne qui est en O(n2).

Pour faire mieux, il faut donc effectuer des comparaisons-échanges entre éléments non
consécutifs. Pour exploiter cette idée on décide de décomposer V en sous-vecteurs ayant des
éléments espacés de k ainsi qu’il suit :
 V[1], V[1+k], V[1+2k], … ,
 V[2], V[2+k], V[2+2k], … ,
 …
 V[k-1], V[k-1+k], V[k-1+2k], … ,
 V[k], V[2k], V[3k], … ,

On considère alors un algorithme avec deux étapes :


1. Une première étape au cours de laquelle les k sous-suites ci-dessus sont triées par
comparaisons-échanges.
A l’issue de cette étape, on espère avoir fait baisser considérablement le nombre
d’inversions, en échangeant des éléments distants
2. Une deuxième étape au cours de laquelle on applique par exemple le tri par insertion

A.20 Ecrire la procédure correspondant à l’étape 1.

A.21 Expérimenter cet algorithme par exemple pour n = 10000, ainsi qu’il suit :
1. Considérer plusieurs valeurs de k (10, 20, 30, … 100)
2. Pour chaque valeur de k, exécuter l’algorithme sur 10000 permutations tirées au hasard
et déterminer le nombre moyen de comparaisons-échanges par permutation
3. Tracer la courbe obtenue pour le nombre de comparaisons-échanges, en fonction de k

A.22 Retrouver dans la littérature, des travaux sur la mise en œuvre de l’algorithme ci-dessus.
On s’intéresse maintenant au tri d’une file séquentielle qu’on peut assimiler à une suite
mathématique. A cet effet on utilisera le répertoire d’instructions du tableau ci-dessous.

Tâche à réaliser Instruction Commentaire


Ouvrir la file F Ouvrir (F) Place en tout début de file
Lire l’élément suivant et le mettre dans Lire (F, Var_cour) Entraîne le passage au prochain élément de
la variable Var_cour. la file
Prolonger F en y ajoutant x Ecrire (F, x) L’élément x se retrouve en fin de file
Fermer la file F Fermer (F) Obligatoire après la dernière opération
Test pour savoir si on est en fin de file Eof (F) Le résultat est vrai ou faux

A.23 Donner une procédure Extraction_Monotonies ci-dessous qui, partant d’une file F = (a1,
… , an), crée deux files F1 et F2 contenant alternativement les monotonies (sous-suites
monotones croissantes maximales) de F ainsi qu’il suit :
 première monotonie dans F1
 deuxième dans F2
 troisième dans F1
 quatrième dans F2
 …
 monotonie de rang 2i-1 dans F1
 monotonie de rang 2i dans F2
 …

A.24 Donner une procédure Fusion_Monotonies qui à partir de deux files F1 et F2, crée une
file F par fusion des monotonies de même rang dans F1 et F2 ainsi qu’il suit :
 fusionner la 1ère monotonie de F1 et la 1ère monotonie de F2 avec insertion dans F
 fusionner la 2ème monotonie de F1 et la 2ème monotonie de F2 avec insertion dans F
 …
 fusionner la ième monotonie de F1 et la ième monotonie de F2 avec insertion dans F
 …

Dès que l’une des files F1 ou F2 est épuisée, le reste de l’autre file est recopié dans F.

A.25 Déduire une procédure Tri_Fusion_Monotonies pour le tri de F, qui procède ainsi qu’il
suit :
<Initialisations>
Jusqu’à <F est triée> faire
début
Extraction_monotonies (F, F1, F2) ;
Fusion_Monotonies (F1, F2, F)
fin

A.26 Montrer que le temps d’exécution de cet algorithme est O(n log2 n).

On considère maintenant le tri de vecteurs dont les éléments appartiennent à un type énuméré,
par exemple a, b, c, … , z, pouvant être utilisé pour indicer un vecteur.

A.27 Donner une procédure de calcul des fréquences Freq[a..z] des éléments de V[1..n].

A.28 Déduire une procédure de calcul des fréquences cumulées Freq_Cumul[a..b].

A.29 Déduire une procédure Tri_Freq de tri stable, pour V[1..n].

A.30 L’algorithme de la question précédente est-il stable ?


A.31 Dire comment procéder pour minimiser les mouvements dans le cas où les éléments
d’un vecteur à trier V[1..n] sont volumineux et donc coûtent cher lorsqu’on veut les échanger.
1.3. Technique de conception des boucles avec preuve de correction

Les boucles sont les constructions algorithmiques les plus délicates, et il est important de
s’assurer qu’elles sont correctes. Ceci nécessite de montrer que la boucle a les deux propriétés
suivantes :
 Terminaison : La boucle doit se terminer, c-à-d produire un résultat en temps fini
quelles que soient les données fournies en entrée.
 Correction partielle : Lorsque la boucle s’arrête, le résultat produit doit être la
solution cherchée quelles que soient les données fournies en entrée.

Autrement dit une boucle est correcte si, quelles que soient les données fournies en entrée,
elle s’arrête et fournit la solution cherchée. On résume cela par la formule suivante :

Correction totale = Terminaison + Correction partielle

A noter qu’il existe des problèmes pour lesquels il n’existe que des algorithmes partiellement
corrects, c-à-d dont on n’a pas démontré la terminaison. Le lecteur est invité à en rechercher
un dans la littérature scientifique.

Pour montrer qu’une boucle se termine, on utilise un variant.

Définition 1.2 Un variant est une quantité positive qui décroit strictement à chaque passage
dans la boucle. Un variant est donc une expression impliquant des variables qui interviennent
dans l’exécution de la boucle.

Pour montrer qu’une boucle est partiellement correcte on utilise un invariant.

Définition 1.3 Un invariant de boucle est une propriété qui :


 est vérifiée à l’entrée de la boucle
 si elle est vérifiée avant une itération, alors elle est vraie à la fin de cette itération

En pratique, la solution du problème résolu par une boucle

Jusqu’à <Condition_d’arrêt> faire <Instructions>

est basée sur une propriété P qui doit être vérifiée après exécution de la boucle et qui
correspond à l’équation :

P = <Invariant> et <Condition_d’arrêt>

La forme générique d’un fragment de programme construit autour d’une boucle s’écrit alors
comme suit :
<Initialisation qui garantit l’invariant>
{<Enoncé de l‘invariant>}
{<Enoncé du variant>}
Jusqu’à <Condition d’arrêt> faire
début
<Actions qui font progresser le calcul, réduisent le variant et
maintiennent l’invariant >
fin ;
{<Propriété cherchée> = <Invariant> et <Condition d’arrêt>}
<Action qui finalise la tâche à réaliser >

Exemple 1.1 Recherche dichotomique dans un vecteur trié par ordre croissant

Problème à résoudre : Recherche de la première occurrence de gauche à droite, d'une valeur


val dans un vecteur V[min..max] {min  max}, trié dans l’ordre croissant.
Principe : Découper le vecteur V en deux parties de même longueur. Poursuivre la recherche
dans la partie de gauche si val est inférieur ou égal à l'élément du milieu, ou dans la partie de
droite sinon.
i := min ; j := max ;
Invariant : Si elle existe, alors la première occurrence de val de gauche à droite est
dans V[i..j] 
Variant : j-i
jusqu’à i = j faire
début
m := (i + j) div 2 ; {i  m < j}
si val  V[m] alors
j := m {maintient l’invariant et fait diminuer strictement le variant }
sinon
i := m+1 {maintient l’invariant et fait diminuer strictement le variant }
fin ;
{Propriété finale recherchée : si val existe dans V, alors la première occurrence de
gauche à droite est en position i}
trouve := V[i] = val ; {Action qui finalise le traitement}
Exemple 1.3 Division euclidienne par soustractions successives
On considère l’algorithme ci-dessous pour la division euclidienne de n par m.
Données : deux entiers naturels n et m
Résultat : le quotient q et le reste r de la division euclidienne de n par m
r := n ;
q := 0 ;
Jusqu’à r < m faire
début
r := r - m ;
q := q + 1
fin ;
Montrer que n = qm + r est un invariant, r est un variant, et déduire la preuve de validité.

Exemple 1.4 Algorithme d’Euclide pour le calcul du pgcd de deux nombres positifs.
La solution est basé sur les propriétés suivantes :
 Si x > 0 alors pgcd (x, 0) = x
 Si x > 0 et y > 0, x alors pgcd(x, y) = pgcd (y, r) où r est le reste de la division de x par
y.
On précisera les éléments suivants : invariant, variant, condition de sortie, condition
finale.

Exercice 1.5 Drapeau tricolore de E. D. Dijktra

On considère le tri d’un vecteur V[1..n] dont les éléments ont trois valeurs possibles : vert,
rouge et jaune. On considère un algorithme basé sur l’invariant suivant : chacun des indices i,
j et k indique respectivement la position où placer le prochain élément vert, rouge ou jaune.
1.5.1 Faire un dessin pour bien visualiser cet invariant
1.5.2 Quelle est à chaque instant, la partie du vecteur qui est non triée ?
1.5.3 Compléter l’algorithme ci-dessous
1. i := … ; j := n ; k := … ;
2. Jusqu’à … faire
Si V[i] = vert alors i := … sinon
Si V[i] = rouge alors début Echanger (V[i], V[j]) ; j := … fin sinon
Si V[i] = jaune alors
Permutation circulaire de V[i], V[k] et V[j], V[j] 
début
Echanger (V[i], V[j]) ;
Echanger (V[j], V[k]) ;
j := j-1
fin ;

Exercice 1.6

Dire ce que fait le programme suivant et le prouver :


i := 1 j := n
Jusqu’à i  j faire
si V[i]  V[j] alors i := i+1
sinon j := j-1
résultat := V[i] ;

Vous aimerez peut-être aussi