VII. Algorithmes avec des vecteurs▲
Rappel : on appelle vecteur un tableau en une dimension.
VII-1. Recherche séquentielle d'un élément▲
Prenons par exemple un tableau d'entiers.
On déclare le type vec_t suivant :
CONST
VMax = 1000
;
TYPE
vec_t = array
[1
..VMax] of
integer
;
Soit v un vec_t de vn éléments (1<=vn<=VMax).
On veut écrire une fonction booléenne qui dit si un entier x se trouve dans le tableau v.
VII-1-1. Dans un vecteur non trié▲
On parcourt le tableau et on s'arrête dès que x est trouvé ou que la fin du tableau est atteinte.
Première implémentation
FUNCTION
cherche1 (v : vec_t; vn, x : integer
) : boolean
;
VAR
i : integer
;
BEGIN
i := 1
;
while
(i <= vn) and
(v[i] <> x) do
i := i+1
;
cherche1 := i <= vn;
END
;
On sort du while dans deux situations :
- Si i > vn, on a parcouru tout le tableau sans trouver x.
- Sinon i <= vn et v[i] = x : on a trouvé x.
On ne peut donc pas écrire comme résultat cherche1 := v[i] = x puisque ipeut sortir du tableau ; c'est pourquoi on écrit cherche1 := i <= vn.
Il y a un problème : dans le while, l'expression (i <= vn) and (v[i] <> x)est toujours complètement évaluée, même si le premier terme est faux.
En éffet, le and ne signifie pas « et sinon», comme dans d'autres langages.
La conséquence est que si x n'est pas dans v, à la dernière itération on évaluera (vn+1 <= vn) and (v[vn+1] <> x)
et on sortira du vecteur !
Implémentation à éviter absolument.
Deuxième implémentation avec un booléen
On va décomposer le test dans le while
FUNCTION
cherche2 (v : vec_t; vn, x : integer
) : boolean
;
VAR
i : integer
;
continuer : boolean
;
BEGIN
i := 1
; continuer := true
;
while
continuer do
if
i > vn
then
continuer := false
else
if
v[i] = x
then
continuer := false
else
i := i+1
;
cherche2 := i <= vn;
END
;
Troisième implémentation
FUNCTION
cherche3 (v : vec_t; vn, x : integer
) : boolean
;
VAR
i : integer
;
BEGIN
i := 1
;
while
(i < vn) and
(v[i] <> x) do
{ i < n strict }
i := i+1
;
cherche3 := v[i] = x;
END
;
On sort toujours du while avec i <= vn, donc on ne sort jamais du tableau.
Par contre pour le résultat, il faut changer le test, et on a le droit d'écrire
cherche3 := v[i] = x .
Le coût : Le vecteur v étant non trié, il faut
- vn itérations six n'appartient pas à v
- vn/2 itérations en moyenne si x appartient à v.
VII-1-2. Dans un vecteur trié▲
Lorsqu'on parle de vecteur trié, on suppose toujours que les vecteurs sont triés
par ordre croissant : pour tout i; v[i] <= v[i + 1].
On parcourt le tableau et on s'arrête dès que :
- x est trouvé
- ou la fin du tableau est atteinte
- ou v[i] > x : ça veut dire que tous les éléments qui suivent seront plus grands que x, inutile de continuer.
On peut adapter facilement la méthode 3 :
FUNCTION
cherche4 (v : vec_t; vn, x : integer
) : boolean
;
VAR
i : integer
;
BEGIN
i := 1
;
while
(i < vn) and
(v[i] < x) do
{ v[i] < x strict }
i := i+1
;
cherche4 := v[i] = x;
END
;
Le coût Le vecteur v étant trié, il faut en moyenne vn/2 itérations, que x appartienne ou non à v.
VII-2. La dichotomie▲
Prenons l'exemple du correcteur orthographique dans un traitement de texte, utilisé, pour corriger une lettre.
Le dictionnaire du correcteur contient tous les mots de la langue, orthographiés de toutes les façons possibles, soit par exemple 1 million d'entrées.
Avec la recherche séquentielle sur un dictionnaire trié, il faudra donc en moyenne
500 000 itérations pour trouver un mot !
Mettons que le texte à corriger fasse 2000 mots. Il faudra donc 2000*500 000 = 1 milliard d'itérations pour corriger le texte !
Sachant qu'en plus, la comparaison de deux mots est nettement plus lente que la comparaison de 2 entiers, la correction va durer plusieurs jours ! ! C'est tout à fait inacceptable.
Pour accélérer les choses, on va s'inspirer du jeu des 1000 francs.
VII-2-1. Le jeu des 1000 francs▲
Jeu1 L'ordinateur choisit un prix secret entre 1 et 1000 F, et le joueur doit le deviner en un nombre minimum de coups.
PROCEDURE
jeu1 (secret : integer
);
VAR
n, essai : integer
;
continuer : boolean
;
BEGIN
continuer := true
; n := 1
;
while
continuer do
begin
write
('Essai '
, n, ' : '
); readln (essai);
if
essai < secret then
writeln ('+'
)
else
if
essai > secret then
writeln ('-'
)
else
begin
writeln ('Gagné en '
, n, ' coups'
);
continuer := false
;
end
;
n := n+1
;
end
;
END
;
Ce qui nous intéresse c'est la stratégie du joueur : admettons que secret = 326.
Essai | Réponse | Intervalle possible |
500 | - | 1-500 |
250 | + | 250-500 |
375 | - | 250-375 |
312 | + | 312-375 |
343 | - | 312-343 |
328 | - | 312-328 |
321 | + | 321-328 |
324 | + | 324-328 |
326 | Gagné |
La solution est trouvée en seulement 9 coups !
C'est le principe de la dichotomie : on a un intervalle de possibilités, et à chaque
Itération on réduit de moitié la taille de cet intervalle.
De la sorte, le nombre maximum d'itération est log2(vn), c'est à dire 10 pour vn =1000, de 20 pour vn = 1 million.
Jeu2 La réciproque : l'utilisateur choisit un prix secret entre 1 et 1000 F, et l'ordinateur doit le deviner en un nombre minimum de coups.
Le programme va donc gérer un intervalle de possibilités, c'est à dire un début et une fin, proposer le milieu de l'intervalle, puis changer l'intervalle en fonction de la réponse.
La recherche dichotomique consiste à faire la même chose sur les indices, et est
donc très performante : sur l'exemple du correcteur orthographique, il faudra 20
Itérations pour trouver un mot, donc 40 000 itérations pour corriger tout le texte, ce qui est quasiment instantané.
VII-2-2. Recherche dichotomique▲
La dichotomie se fait toujours sur un vecteur trié.
Cela consiste à considérer une certaine plage de recherche inf.. sup sur le vecteur, plage que l'on réduit d'un facteur 2 à chaque itération.
Au départ, la plage de recherche est tout le vecteur.
A une itération donnée, on a une plage [inf..sup] et son milieu est m := (inf + sup) div 2.
On a donc la subdivision : [inf..m-1], [m], [m+1..sup].
- Soit v[m] = x et on a fini.
- Soit v[m] < x, donc x 62 [inf..m] et la nouvelle plage sera [m+1..sup].
- Soit v[m] > x, donc x 62 [m..sup] et la nouvelle plage sera [inf..m-1].
On implémente l'algorithme avec un booléen trouve.
FUNCTION
cherche5 (v : vec_t; vn, x : integer
) : boolean
;
VAR
inf, sup, m : integer
;
trouve : boolean
;
BEGIN
trouve := false
;
inf := 1
; sup := vn;
while
(inf <= sup) and
not
trouve do
begin
m := (inf + sup) div
2
;
if
v[m] = x
then
trouve := true
else
if
v[m] < x
then
inf := m+1
else
sup := m-1
;
end
;
cherche5 := trouve;
END
;
Remarque Le fait de prendre m-1 ou m+1 n'est pas une simple optimisation, mais est essentiel pour que l'algorithme se termine.
Le coût Il faut
- log2 vn itérations si x n'appartient pas à v.
- log2 vn itérations au plus si x appartient à v.
On peut améliorer l'efficacité dans le cas où x n'appartient pas à v :
BEGIN
trouve := false
;
if
(v[1
] <= x) and
(x <= v[vn]) then
begin
inf := 1
; sup := vn;
while
{...}
end
;
cherche6 := trouve;
END
;
VII-3. Tri d'un vecteur▲
Soit v[1..vn] un vecteur non trié. Nous voulons construire un vecteur w[1..vn] qui contienne les même éléments que v, et qui soit trié.
Il existe de très nombreuses méthodes de tris, qui sont plus ou moins faciles à implémenter, et dont certaines sont nettement plus efficaces que d'autres.
Dans certaines méthodes on peut se passer du second vecteur w, en travaillant directement sur v où seront permutés des éléments.
VII-3-1. Tri par remplacement▲
La méthode consiste à sélectionner des minimums successifs dans v, et à les ranger au fur et à mesure dans w.
Au départ on recherche quel est le max.
A chaque pas :
- on cherche min(v)
- on le met au bout de w
- on le remplace dans v par max(v)
Exemple Trier dans l'ordre alphabétique les lettres du mot 'ETABLES'.
Le max est la lettre 'T'.
i | v | Indice du min dans v | W trié | V après remplacement |
1 | E T A B L E S | 3 | A | E T T B L E S |
2 | E T A B L E S | 4 | AB | E T T T L E S |
3 | E T A B L E S | 1 | ABE | T T T T L E S |
4 | T T T T L E S | 6 | A B E E | T T T T L T S |
5 | T T T T L T S | 5 | A B E E L | T T T T T T S |
6 | T T T T T T S | 3 | A B E E L S | T T T T T T T |
7 | T T T T T T T | fini | A B E E L S T |
FUNCTION
maximum (v : vec_t; vn : integer
) : char
;
VAR
i : integer
; m : char
;
BEGIN
m := v[1
];
for
i := 2
to
vn do
if
v[i] > m then
m := v[i]:
maximum := m;
END
;
FUNCTION
ind_min (v : vec_t; vn : integer
) : integer
;
VAR
i, j : integer
;
BEGIN
j := 1
;
for
i := 2
to
vn do
if
v[i] < v[j] then
j := i:
ind_min := j;
END
;
PROCEDURE
tri_remplacement ( v : vec_t;
vn : integer
;
var
w : vec_t );
VAR
max : char
;
i, j : integer
;
BEGIN
{ recherche du max }
max := maximum (v,vn);
58
Algorithmes et programmation en Pascal
Edouard Thiel
{ pas a pas }
for
i := 1
to
vn-1
do
begin
j := ind_min (v,vn);
w[i] := v[j];
v[j] := max;
end
;
{ on met le max dans la derniere case }
w[vn] := max;
END
;
Le coût
Les performances sont faibles : il y a environ vn*vn comparaisons, et 2,5vn affectations en moyenne.
Par exemple si vn= 1000, on aura 1 000 000 de comparaisons et 2500 affectations.
VII-3-2. Tri par permutation▲
Dans la méthode précédente, la place occupée est double ; on peut éviter de créer un nouveau vecteur en travaillant directement sur v.
Principe on est à l'étape i.
- Supposons déjà trié v[1..i-1], et non traité v[i..vn].
- On cherche le min dans v[i..vn]
- On le permute avec v[i].
- Maintenant v[1..i] est trié.
Exemple Trier les lettres du mot 'ETABLES'.
i | V trié / non traité | Indice du min | Lettres à permuter | V après permutation |
1 | / E T A B L E S | 3 | E et A | A / T E B L E S |
2 | A / T E B L E S | 4 | T et B | A B / E T L E S |
3 | A B / E T L E S | 3 | Non | A B E / T L E S |
4 | A B E / T L E S | 6 | T et E | A B E E / L T S |
5 | A B E E / L T S | 5 | Non | A B E E L / T S |
6 | A B E E L / TS | 7 | T et S | A B E E L S / T |
7 | A B E E L S / T | fini | A B E E L S T / |
Le coût
Les performances sont meilleures que le tri par remplacement : il y a environ
Vn*vn/ 2 comparaisons, et vn / 2 permutations en moyenne.
Par exemple si vn= 1000, on aura 500 000 comparaisons et 1500 affectations.
VII-3-3. Tri à bulles▲
C'est une variante du tri par permutation, un peu moins efficace ; il y a une version optimisée qui est un peu meilleure que le tri par permutation.
Principe On est à l'étape i.
- Supposons déjà trié v[1..i-1], et non traité v[i..vn].
- On parcourt v[i..vn] en descendant et, chaque fois que deux éléments consécutifs ne sont pas dans l'ordre, on les permute.
- En fin de parcours le min de v[i..vn] se retrouve dans v[i].
- Maintenant v[1..i] est trié.
Le nom du « tri à bulles» vient de ce que à chaque étape i, les éléments les plus «légers » remontent vers la surface, sont transportés à gauche.
On constate aussi qu'à chaque étape, l'ordre général est accru.
Tri à bulle optimisé
Si lors d'une étape i, aucune permutation n'a lieu, c'est que [i..vn] est déjà dans l'ordre, et le tri est fini.
Booléen apermute ou encore mieux, indice dp de dernière permutation.
VII-3-4. Tri par comptage▲
Cette méthode consiste à construire un vecteur d'indices ind, où l'on calcule la position que devrait avoir chaque élément pour que le vecteur soit trié.
Exemple Résultat sur le tri des lettres du mot 'ETABLES'.
I | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
v | E | T | A | B | L | E | S |
Ind | 3 | 7 | 1 | 2 | 5 | 4 | 6 |
w | A | B | E | E | L | S | T |
PROCEDURE
tri_comptage ( v : vec_t;
vn : integer
;
var
w : vec_t);
VAR
i, k : integer
;
ind : array
[1
..VMax] of
integer
;
BEGIN
{ init }
for
i := 1
to
vn do
ind[i] := 1
;
{ construit ind }
for
i := 1
to
vn-1
do
for
k := i+1
to
vn do
if
v[k] < v[i]
then
ind[i] := ind[i]+1
else
ind[k] := ind[k]+1
;
60
Algorithmes et programmation en Pascal
Edouard Thiel
{ construit w }
for
i := 1
to
vn do
w[ind[i]] := v[i];
END
;
Le coût
La place occupée est importante (3 vecteurs).
Le nombre de comparaisons est constant (vn*(vn-1)/2). C'est du même ordre que le tri par permutation ou à bulles non optimisé.
Il y a très peu d'affectations (vn) ; cela est très intéressant si les éléments de vn sont « lourds », par exemple des strings ou des records triés sur un champ.
VII-4. Mise à jour d'un vecteur▲
Soit v[1..vn] un vecteur, avec 1 <=vn <= VMax.
On regarde comment insérer ou supprimer un élément de v, en conservant l'ordre ou non des éléments.
VII-4-1. Insertion dans un vecteur non trié▲
Pour insérer un élément x en queue de v, il suffit de faire
if
vn < VMax then
begin
vn := vn+1
; v[vn] := x; end
;
Si on veut insérer x à une autre position, c'est que l'on considère que le vecteur est trié avec un certain ordre (croissant, décroissant, d'apparition, etc.).
VII-4-2. Insertion dans un vecteur trié▲
On veut insérer x à la position i dans v, avec 1 <= i <= vn.
On doit décaler v[i..vn] vers la droite :
if
vn < VMax then
begin
vn := vn+1
;
for
j := vn downto
i+1
do
{ en descendant }
v[j] := v[j-1
];
v[i] := x;
end
;
VII-4-3. Suppression dans un vecteur non trié▲
On veut supprimer l'élément v[i], avec 1 <= i <= vn.
Si l'ordre des éléments dans v est indifférent, on place l'élément de queue dans le trou.
v[i] := v[vn]; { si i = vn, ca ne fait rien }
vn := vn-1
;
VII-4-4. Suppression dans un vecteur trié▲
On décale v[i+1..vn] vers la gauche :
décale v[i+1
..vn] vers la gauche :
for
j := i to
vn-1
do
{ en montant }
v[j] := v[j+1
];
vn := vn-1
;
VII-5. Tri par insertion▲
Voici un algorithme de tri basé sur l'insertion dans un vecteur trié.
Principe On est à l'étape i.
- Supposons déjà trié v[1..i-1], et non traité v[i..vn].
- On pose x = v[i]. La case i est disponible.
- On cherche k tel que v[k-1]<=x<=v[k].
- On insère x à la position k dans v[1..i-1], ce qui oblige à décaler d'abord v[k..i-1] vers v[k+1..i] ; le trou en i est bouché.
- Maintenant v[1..i] est trié et v[i+1..vn] est non traité.
Exemple Trier les lettres du mot 'ETABLES'.
Au départ, on considère que v[1..1] est trié ; on va donc insérer les éléments suivants, de 2 à vn.
i | V trié / non trié | Lettre à insérer | V après insertion |
2 | E / T A B L ES | T | E T / A B L E S |
3 | E T / A B L E S | A | A E T / B L E S |
4 | A E T / B L E S | B | A B E T / L E S |
5 | A B E T / L E S | L | A B E L T / E S |
6 | A B E L T / E S | E | A B E E L T / S |
7 | A B E E L T / S | S | A B E E L S T / |
Implémentation du tri
PROCEDURE
tri_insertion ( var
v : vec_t; vn : integer
);
VAR
i : integer
;
BEGIN
for
i := 2
to
vn do
insérer_trié (v, i);
END
;
Implémentation de l'insertion
PROCEDURE
insérer_trié ( var
v : vec_t; i : integer
);
VAR
j, k : integer
;
x : typeélement;
BEGIN
{ élément à insérer }
x := v[i];
{ recherche position d'insertion de x }
k := posit_ins (v, i, x);
{ décalage : en descendant }
for
j := i downto
k+1
do
v[j] := v[j-1
];
{ insertion }
v[k] := x;
END
;
Optimisation de la recherche du point d'insertion
La recherche du point d'insertion k peut se faire séquentiellement ; mais on a tout intérêt à employer une recherche dichotomique, bien plus efficace.
FUNCTION
posit_ins ( var
v : vec_t;
i : integer
;
x : typeélement) : integer
;
VAR
inf, sup, m : integer
;
BEGIN
{ le cas des extrémités }
if
x < v[1
] then
posit_ins := 1
else
if
v[i-1
] <= x then
posit_ins := i
else
begin
{ init dichotomie : les cas 1 et i sont déjà traités }
inf := 2
; sup := i-1
;
{ recherche position m tel que v[m-1] <= x < v[m] }
{ : variante de la dichotomie habituelle, }
{ sans le booléen trouve }
while
inf < sup do
begin
m := (inf + sup) div
2
;
if
v[m] <= x
then
inf := m+1
else
sup := m;
end
;
posit_ins := sup;
end
;
END
;
Le coût
Méthode nettement plus efficace que les autres pour vn grand :
La recherche dichotomique sur v[1..i] est en log2 i. L'insertion dans v[1..i]
coute en moyenne i=2. Le coût total est donc la somme sur i de 2 à vn de (log2 i + i/2).
Par exemple avec vn= 1000, le coût est de 10 000, contre 500 000 pour les autres tris.