Nous utilisons le tri à bulles pour illustrer une propriété importante des tris: la stabilité.
Cette propriété s'intéresse à ce qu'il se passe lorsque l'algorithme de tri rencontre deux éléments qu'il considère comme de même valeur.
TAB = [ ("Aubert","Beatrice"),
("Caron","Alain"),
("Bonnet","Christine"),
("Bonnet","Anne"),
("Aubert","Alexandre"),
("Caron","Catherine"),
("Bonnet","Benoit"),
("Aubert","Denis"),
("Aubert","Carole"),
("Caron","Brigitte") ]
On peut envisager de trier cette liste par
Pour le rendre plus générique - capable de trier selon divers critères - réécrivons notre tri à bulles avec une fonction de comparaison plus_petit
passée en paramètre
def tri_a_bulles(T,plus_petit):
N = len(T)
for k in range(N,1,-1):
for i in range(0,k-1):
if plus_petit( T[i+1], T[i] ):
T[i],T[i+1] = T[i+1],T[i]
Pour trier la liste par ordre alphabétique des prénoms, il suffit d'écrire une fonction de comparaison sur le deuxième élément du tuple
def compare_prenoms(a,b):
return a[1] < b[1]
T1 = TAB.copy()
tri_a_bulles(T1,compare_prenoms)
afficher(T1)
Nom | Prénom -----------+----------- Caron | Alain Aubert | Alexandre Bonnet | Anne Aubert | Beatrice Bonnet | Benoit Caron | Brigitte Aubert | Carole Caron | Catherine Bonnet | Christine Aubert | Denis
Pour trier selon l'ordre alphabétique des noms, il suffit d'utiliser la fonction de comparaison
def compare_noms(a,b):
return a[0] < b[0]
T2 = TAB.copy()
tri_a_bulles(T2,compare_noms)
afficher(T2)
Nom | Prénom -----------+----------- Aubert | Beatrice Aubert | Alexandre Aubert | Denis Aubert | Carole Bonnet | Christine Bonnet | Anne Bonnet | Benoit Caron | Alain Caron | Catherine Caron | Brigitte
Appliquons le tri par nom de famille à la liste T1, précédemment triée par prénom.
T3 = T1.copy()
tri_a_bulles(T3,compare_noms)
afficher(T3)
Nom | Prénom -----------+----------- Aubert | Alexandre Aubert | Beatrice Aubert | Carole Aubert | Denis Bonnet | Anne Bonnet | Benoit Bonnet | Christine Caron | Alain Caron | Brigitte Caron | Catherine
Pour chaque nom de famille, les personnes sont triées par ordre alphabétique des prénoms
Un algorithme de tri est stable s'il ne modifie pas l'ordre initial des clés identiques.
Dans l'algoritme du tri à bulle, remplaçons if plus_petit( T[i+1], T[i] )
par if not plus_petit( T[i], T[i+1] ):
Cela revient à remplacer $T_{i+1} < T_i$ par $\neg( T_i < T_{i+1} )$, i.e. $T_{i+1} \leq T_{i}$.
def tri_instable(T,plus_petit):
N = len(T)
for k in range(N,1,-1):
for i in range(0,k-1):
if not plus_petit( T[i], T[i+1] ):
T[i],T[i+1] = T[i+1],T[i]
Ce changement modifie le traitement des éléments égaux.
Avec T[i] > T[i+1]
, les éléments égaux restent en place.
Avec T[i] >= T[i+1]
, les éléments égaux sont échangés.
Cela rend le tri instable. Trions de nouveau par nom de famille la liste T1 triée par prénom.
T4 = T1.copy()
tri_instable(T4,compare_noms)
afficher(T4)
Nom | Prénom -----------+----------- Aubert | Denis Aubert | Beatrice Aubert | Alexandre Aubert | Carole Bonnet | Christine Bonnet | Benoit Bonnet | Anne Caron | Catherine Caron | Brigitte Caron | Alain
Il est également possible de déstabiliser la fonction tri_a_bulles
sans la modifier mais en lui fournissant en paramètre une mauvaise fonction de comparaison.
def compare_noms_instable(a,b):
return a[0] <= b[0]
T5 = T1.copy()
tri_a_bulles(T5,compare_noms_instable)
afficher(T5)
Nom | Prénom -----------+----------- Aubert | Denis Aubert | Beatrice Aubert | Alexandre Aubert | Carole Bonnet | Christine Bonnet | Benoit Bonnet | Anne Caron | Catherine Caron | Brigitte Caron | Alain
De même, il est toujours possible de stabiliser un algorithme de tri instable en procédant en trois étapes.
def ajoute_indice(T):
return [ (nom, prenom, indice) for indice, ( nom, prenom ) in enumerate(T)]
T6 = ajoute_indice(T1)
print(T6)
[('Caron', 'Alain', 0), ('Aubert', 'Alexandre', 1), ('Bonnet', 'Anne', 2), ('Aubert', 'Beatrice', 3), ('Bonnet', 'Benoit', 4), ('Caron', 'Brigitte', 5), ('Aubert', 'Carole', 6), ('Caron', 'Catherine', 7), ('Bonnet', 'Christine', 8), ('Aubert', 'Denis', 9)]
def compare_noms_indices(a,b):
(nom_a, prenom_a, indice_a) = a;
(nom_b, prenom_b, indice_b) = b;
if(nom_a < nom_b):
return True
elif nom_a == nom_b:
return indice_a < indice_b
else:
return False
tri_instable(T6,compare_noms_indices)
print(T6)
[('Aubert', 'Alexandre', 1), ('Aubert', 'Beatrice', 3), ('Aubert', 'Carole', 6), ('Aubert', 'Denis', 9), ('Bonnet', 'Anne', 2), ('Bonnet', 'Benoit', 4), ('Bonnet', 'Christine', 8), ('Caron', 'Alain', 0), ('Caron', 'Brigitte', 5), ('Caron', 'Catherine', 7)]
def retire_indice(T):
return [ (nom, prenom) for (nom, prenom, indice) in T ]
T6 = retire_indice(T6)
afficher(T6)
Nom | Prénom -----------+----------- Aubert | Alexandre Aubert | Beatrice Aubert | Carole Aubert | Denis Bonnet | Anne Bonnet | Benoit Bonnet | Christine Caron | Alain Caron | Brigitte Caron | Catherine
Pour analyser la stabilité des tris suivants, nous utiliserons une technique plus visuelle. Elle consiste à trier une liste de 50 nombres réels aléatoires entre 0 et 7 avec trois critères de tris distincts qui comparent
def comp(a,b):
return a<b
def comp_int(a,b):
return int(a) < int(b)
def comp_frac(a,b):
return (a-int(a)) < (b - int(b))
Visualisons le tri de 50 nombres aléatoires
import numpy as np
import matplotlib.pyplot as plt
def comp_frac(a,b):
return (a-int(a)) < (b - int(b))
T = np.random.uniform(0,7,50)
tri_a_bulles(T,comp_frac)
plt.stem(T,markerfmt=',',linefmt='black',basefmt='black')
plt.show()
def comp_int(a,b):
return int(a) < int(b)
T = np.random.uniform(0,7,50)
tri_a_bulles(T,comp_int)
plt.stem(T,markerfmt=',',linefmt='black',basefmt='black')
plt.show()
T = np.random.uniform(0,7,50)
tri_a_bulles(T,comp_frac)
tri_a_bulles(T,comp_int)
plt.stem(T,markerfmt=',',linefmt='black',basefmt='black')
plt.show()
T = np.random.uniform(0,7,50)
tri_instable(T,comp_frac)
tri_instable(T,comp_int)
plt.stem(T,markerfmt=',',linefmt='black',basefmt='black')
plt.show()
© Olivier Cuisenaire, 2018 |