Aller au contenu

Tri par sélection

Principe du tri par sélection

Tri par sélection en résumé

  • On cherche la valeur minimale et on la place au début du tableau.
  • On recommence à partir de la deuxième valeur, et on la place en deuxième position par échange.
  • Ainsi de suite
Activité 1 - Déroulé « à la main »
  • On considère le tableau \([3,\,4,\,1,\,2]\).

  • Lors de la première itération de l'algorithme, on cherche la valeur minimale à partir de l'indice \(0\). C'est le \(1\) placé à l'indice \(2\). On échange donc les éléments d'indices \(0\) et \(2\). Le tableau devient \([1,\,4,\,3,\,2]\).

  • Lors de l'itération suivante on cherche le minimum à partir de l'indice \(1\) : c'est le \(2\) situé à l'indice \(3\). Après échange, le tableau devient \([1,\,2,\,3,\,4]\).

  • On recommence encore une fois la recherche à partir de l'indice \(2\) : le minimum est à l'indice \(2\), on l'échange avec lui-même.

  • Lorsqu'il ne reste plus qu'un élément à trier (le dernier), il est inutile de chercher le minimum car il est obligatoirement bien placé (en dernière position).

Vous pouvez observer le déroulé de cet algorithme ci-dessous :

Tableau aléatoire Tableau croissant Tableau décroissant

Trier votre tableau

On considère le tableau \([6,\,1,\,4,\,5,\,2,\,3]\).

  • Lors de la première itération on échange les valeurs d'indices \(1\) et \(6\)
  • Après deux itérations, le tableau est \([1,\,2,\,4,\,5,\,6,\,3]\)
  • La valeur \(4\) ne changera de position qu'une seule fois durant l'algorithme
  • Au total il y a eu \(5\) échanges de valeurs
  • ❌ Lors de la première itération on échange les valeurs d'indices \(0\) et \(1\). \(1\) et \(6\) sont leurs valeurs !
  • ✅ La première itération échange \(6\) et \(1\), la deuxième \(6\) et \(2\). Ce tableau est donc correct
  • ❌ La valeur \(4\) sera échangée deux fois : avec le \(3\) lors de la troisième itération puis avec le \(5\) lors de la quatrième. Elle sera alors bien placée
  • ✅ On échange au total : \(6\) et \(1\), \(6\) et \(2\), \(4\) et \(3\), \(5\) et \(4\), \(6\) et \(5\). Il y a donc bien 5 échanges
Activité 2 - Tri par sélection en Python

L'une des étapes essentielles du tri par sélection est donc de déterminer l'indice de la valeur minimale à partir d'un certain indice i. Cette recherche ayant lieu à plusieurs reprises, nous allons utiliser une fonction pour la réaliser.

Compléter la fonction indice_minimum_depuis prenant en argument un tableau ainsi qu'un indice i et renvoyant l'indice de la valeur minimale parmi les éléments situés après celui d'indice i (inclus).

On garantit que i est un indice valide.

Si plusieurs valeurs sont égales au minimum, on renverra l'indice de la première d'entre elles.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013rAt=èflb)D1 2e/vygj.:,]TswRnuph5aS(oq3ùLk_iî4+6;Pé[cdm050#0o0d0H0R0h0z0m0!0h0H0z0z0e010d0R0E010406050z0D0$0$0H0b0r040I0K0h0D0`0K0C0m020H0$0E0W0m0B0o140b0L0D0o0z050p111315170 0E04051C1v1F0p1C0 0#0R0q0/0;0?0^0F0R0s0F0h1T0F0d0}050*0i0h0o1O0=0@011S1U1W1U0d1$1(1!0d0b1D0d0F0/1a0z0E0H0C0^0n011*1Q010g0,0o0C1i0o1!2022271,2a1(2d0$2f040a0m0X0b0K0E0K0z0R1d1f0(1~0b0b0o0!2A1v2h0C1D0p1|2M1_1{1`1#0#2j0^1W0C2c2x1!1L1N0:1+2W0R2Y0C0K2$1!0E2F1D2K2M2@10211f2(282-0b140h0}0l2J2{0~2`2i2}1,2 310}0n3522372K2V013c0H32040M3g2L0 3j3a0^3m3o0T3r3i2{3k3x0}0G3A3t3C3v3l0K303n0}0V3A1G2=1v2$2P0#1{2U3K0!2.2p0%1M1D2;0o2?363R3#0(3-391P1,0P0}0(0g3A0m382|3@3w0g0}2+1L0!0o0Q0$2+0R0$120Q0(0E0D0R1u1w3.3u41010|040J3R4n2)3l0}1?0o0H0D4t3J4o4q0w3}3 3D444C3?4v4q0j0v3H0m4S3~4u2~44494b4H4V1,0K0}0e4!4D4v0C4K4l3h064T4U4+283_040g3M4*4M4W040t4}404v0K0A440C523D0i0}0b220s0o4L53284q4s4/2L4I3K4-040R593K4%040U5t4o4a335h3k4F5y4,5b042m5C3K5k5K4o5q4y4A5N4N0}0j4P4R4=4T5o4o4_0R3|5m044?4~3b4x0H1%4z4B5)5!5T040Z5S4 515?4#0^4q0x5F285v020h0d0W635-045Q5=2_5 4p0}5`5~4@6b0R4Y0C5s6k5,600}0x4Q5)4;5Y6z5@4 6n4a6p6a0^5v4)5)5+5i6b5}2@6y4S6B3^5c0)0D0b586K6S3w4X6E6q6P1v3:3,3S6,0p3V1v0d3X6;2S2N5/1(2M3V1B3=6M0^2F0$0Q0g0H0P480F0M0}1n1p1r1t0m6w2_1I371C0k220.1(0/0=0m0K0N0m7p6%4d0$0m0H0E215d0S0d0m0E1b2y0o6W0.4{4j0w7s1f4A5d2z7H0D7A0D0?0R0m2F0d2c0R0b7v0H0m0(6X0R0f2F7.002c1_1)0o0h1(0z0u0m0O1)4{0C2H0R1e2Y2o0C7G0s0Y0C0Y5d0h7.1)0h003n0s3M2z0F2o7Y7U7G0Y0d0Y0m2y4a0;7*1)0J144j0m0,0m0C8m6W0+7G7I7Z0m8w8y057-0F2F0g1R1=0E0z0v0p0p0g0b0u0A0R0P0{0o1L0H0u3M0s0p8.8:0p0c0h8o0b8q2o4f481_0R030y920Q0z4y1!1o049d6`5g0p9g1v0H040j1G376/0)0+0-04.

On donne les fonctions echange et indice_minimum_depuis.

Compléter la fonction tri_selection prenant en argument un tableau et le triant en place à l'aide du tri par sélection.

On pourra utiliser les fonctions echange et indice_minimum_depuis.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013rt=flcb)1 2e/vyg:,swnuph(aSo3k_i4P-dm050K0m0c0A0G0f0t0k0g0f0A0t0t0d010c0G0x010406050t0w0L0L0A0b0p040B0C0f0w0$0C0v050n0-0/0;0?0+0x0405160 190n160+0K0G0o0U0W0Y0!0y0G0q0y0f1n0y0c0)050P0h0f0m1i0X0Z011m1o1q1o0c1w1y1u0c0b170c0y0U0_0t0x0A0v0!0l011A1k010e0R0m0v0A0L0m1u1T1V1!1C1%1y1*1,0)0a0k0I0b0C0x0C0t0G0|0v0k0N1R0b0b0m0g240 1/0v170n1P2h1M1O1N1v0K1;0!1q0v1)211u1f1h0V1B2r0G2t0v0C2x1u0x2a172f2h2L0,1U252z1#2E0b0:0f0)0j2e2P0*2O1:2R1C2T2V0)0l2Z1V2#2f2q012*0A2W040D2.2g0+2;2(0!2@2_0H2|2h2I0m2h2x2k0K1O2p30010g2F1-173a182J2$2g35053h0N2K2P2=0E0)0N0e350k3o2=0v0e0)1M0G0F0t0m1y2c0G0}3q2 1j1C0(040z3R3v3f0v3H0A1x0m0A0w3Y2%3T0!3V0i0r35060k3^3C3S2A013x040e0C0b3B3D3!0)0G433{1#0C0u460~102!3`3Z3.2?0h0)0b1V0q0m3,2Q4j3V3X4f2/444j0v4l041@4r2=4u4E45041J3)3+4w3p493U0)0i484i3|0C0)0J4T3-3|0L0G2X4H4t4R3=4N0*3_4/4h4!2S460F4$0v474-4;4s4V0)0d4Z4~4?042C1f0g0m4^2C0G0L0.0F0N0x0w0G0t4)3|4G4-4y3|3#4J3%1y3*5l1#3V0s523E465w4Q044S4-3@4:3^5p542b0y4o4q5o4P3/0)4v2N5S2?3$3(5v5R4U5x0)5z4|5L2)5C5$4=5E5*2L4}5B555a4`5D5T5F3?0 3s381a3n0n3l2i3c0 2l695t62651g2#650O0Q0S04.

Le code précédent est court et lisible.

Toutefois le plus souvent le tri par sélection est rédigé en une seule fonction. Terminons notre étude par cette rédaction.

Compléter la fonction tri_selection prenant en argument un tableau et le triant en place à l'aide du tri par sélection.

On n'utilisera pas les fonctions echange et indice_minimum_depuis.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013rt=7flb)1 2e/vygj:,-]swnuph5aS(o3k_i4+6P;[cdm050S0m0c0D0K0g0w0k0R0g0D0w0w0d010c0K0A010406050w0z0T0T0D0b0p040E0G0g0z0.0G0y050n0^0`0|0~0?0A04051e171h0n1e0?0S0K0o0$0(0*0,0B0K0q0B0g1v0B0c0;050X0h0g0m1q0)0+011u1w1y1w0c1E1G1C0c0b1f0c0B0$110w0A0D0y0,0l011I1s010f0Z0m0y0D0T0m1C1#1%1,1K1/1G1=1@0;0a0k0O0b0G0A0G0w0K140y0k0V1Z0b0b0m0R2c171`0y1f0n1X2p1U1W1V1D0S1|0,1y0y1;291C1n1p0%1J2z0K2B0y0G2F1C0A2i1f2n2p2T0@1$2d2H1-2M0b0{0g0;0j2m2X0=2W1{2Z1K2#2%0;0l2+1%2-2n2y012=0D2(040H2_2o0?2|2:0,2 310L342{2X2}3a0;0C3d363f382~0G2$300;0N3k2.2Y1r2;3p2?040e3d1i2R172F2s0S1W2x3n0R2N1^1f3H1g3F2V182,053N0V2S3m3x0,0I0;0V0f3d0k3v3g0f0;1U0K0J0w0m1G2k0K153D373$010:040F3~3#2I2~3;0D1F0m0D0z452/40420i0s3k0k4m3-3 473(040f3p3,3.3n0y0;0K4v4p1-0G0x4z163V2`4o462!0h0;0b1%0q0m4f3w4742444I2o4w400y4N041 4T2}4W4*4x494b4d4-4h0;0i4B4L1K0G0;0u4_4g470T0K2)4=4V4@4k4Y0=4n5b4K502!4z0J520y4A595d4U4D0;0d4 5n2;4z4l5c4m4!4q0;4t0b5r3g0;0r5D3n4E4G5H4#4%4P0y4R551-4,595y5f045k2T5m2}4|040M5L5153042*5U4C1K420t5)4M0;4)5.4`0,5T2V5/394/1G4;5`5e5:4@4j5v5w5b5V1K4r0K3+5l6c60041R4c4e645s5|0;0Q5R5t045G6o4+0;0v5?4{0;020g0c0P6B6j6l635~5{416r6t6j3?5i5Y3W5 6O040v582T066a6%5!4.5X5h2K6U4J6i015$5q6h6W4y6v695c6:6_6K6n6M656q046s6x6*6.4Z6W420v5=6@6N6~4a62706V6N4275716p486+6T6Q6X6A7e726;5p6I7q6 7t7m7t6_6S6-7D6z7d5Z6}616m7J747F5u764?6Y3u0n3Y0m2p2Q7!3G1o3I2s2v2q7h7%0n3H0?7:0W0Y0!04.

Tester ci-dessous. Que se passe-t-il ?

Réponse

La fonction n'a pas de return, c'est une procédure. Elle renvoie donc None

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Tester ci-dessous. Que se passe-t-il ?

Réponse

La liste de départ a été modifiée ...

C'est ce qu'on appelle un effet de bord. La fonction a modifié "en place" la liste.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Activité 3 - Étude de la complexité du tri par sélection

Nous allons étudier le tri de [9, 5, 8, -2, 6, 4]. Nous avons mis en vert la partie triée de la liste. La dernière colonne donne le nombre de comparaisons effectuées par la fonction rechercher_position_du_min.
Par exemple :
- A l'étape 0, il faut 5 comparaisons pour trouver le minimum -2 :
On compare 9 à 5, puis 5 à 8, puis 5 à -2, puis -2 à 6, puis -2 à 4 . - A l'étape 1, il faut 4 comparaisons pour trouver le minimum 4 :
On compare 5 à 8, puis 5 à 9, puis 5 à 6, puis 5 à 4.



Numéro d'étape liste nombre de comparaisons
0 [9, 5, 8, -2, 6, 4] 5
1 [-2, 5, 8, 9, 6, 4] 4
2 [-2, 4, 8, 9, 6, 5] 3
3 [-2, 4, 5, 9, 6, 8] 2
4 [-2, 4, 5, 6, 9, 8] 1
5 [-2, 4, 5, 6, 8, 9] 0

Pour une liste de taille 6, il faut donc 5 + 4 + 3 + 2 + 1 = 15 comparaisons.

Dans le cas général, on va compter le nombre de comparaisons : if tableau[j] < tableau[i_mini] : de la fonction tri_selection.
Nous désirons trier un tableau de taille n

Dans la fonction tri_selection, ivarie de 0 à n-2

Dans la boucle on va devoir faire (n-1)-i comparaisons

Au total :

indice de boucle dans triSel nombre de comparaisons
0 n - 1
1 n - 2
2 n - 3
... ...
n - 3 2
n - 2 1

(nous n'avons pas écrit la dernière ligne du tableau de l'exemple car elle ne nécessite aucune comparaison).

Le nombre total de comparaisons est donc :

\(C(n) = 1 + 2 + 3 + ... + n-1\)

Si vous faites la spécialité maths, vous savez que \(C(n)=\dfrac{(n-1)n}{2}\)

👉 Pour trier une liste de taille il faut donc \(C(n)=\dfrac{(n-1)n}{2}\) comparaisons.
Si \(n\) devient très grand, on a \(n-1 \approx n\) et il faut environ \(\dfrac{n^2}{2}\) comparaison.
On dit que la complexité de l'algorithme de tri par sélection est en : \(O(n^2)\)

\(\mu\) : Un peu de maths (facultatif...)

Nous avons vu que \(1 + 2 + 3 + ... + n-1 = \dfrac{(n-1)n}{2}\)

Ce n'est pas difficile à comprendre :

Il suffit d'écrire en dessous de \(C(n)\), le même \(C(n)\) en partant de la fin, puis d'additionner les deux lignes :

C(n) = 1 + 2 + 3 + ... + n-3 + n-2 + n-1
C(n) = n-1 + n-2 + n-3 + ... + 3 + 2 + 1
2C(n) = n + n + n + ... + n + n + n

La dernière ligne du tableau contient \(n-1\) fois le terme \(n\).
On a donc \(2 \times C(n)=(n-1)n\) , et donc \(C(n)=\dfrac{(n-1)n}{2}\)

Activité 4 - Mesures du temps de calcul
Quel est le "pire des cas?"
Solution

Il n' y a pas de "pire des cas". ou plus exactement, on est toujours dans le pire des cas ! Si la liste de départ est complètement triée, à chaque tour de boucle, il faudra quand même itérer sur le reste de la liste pour chercher l'indice du minimum ... Le fait de mesurer plusieurs fois le tri de la liste triée n'est donc pas gênant.

Vérifions sur une liste de taille 100000 ...

Recopier le script ci-dessous dans votre éditeur Python (pas en ligne)

Il faut attendre un peu, on trie une "grosse" liste ...

from timeit import timeit

def tri_selection(tableau):
    for i in range(len(tableau) - 1):
        i_mini = i
        for j in range(i + 1, len(tableau)):
            if tableau[j] < tableau[i_mini]:
                i_mini = j
        tableau[i], tableau[i_mini] = tableau[i_mini], tableau[i]

liste_croiss = [i for i in range(10000)]
liste_decroiss = [i for i in range(10000,0,-1)]
t_croiss = timeit("tri_selection(liste_croiss)", number = 1, globals = globals())
t_decroi = timeit("tri_selection(liste_decroiss)", number = 1, globals = globals())
print("t_croiss = ",t_croiss)
print("t_decroi = ",t_decroi)
regardons plus précisément

Recopier le script ci-dessous dans votre éditeur Python (pas en ligne). Que se passe-t-il ?

from timeit import timeit

def tri_selection(tableau):
    for i in range(len(tableau) - 1):
        i_mini = i
        for j in range(i + 1, len(tableau)):
            if tableau[j] < tableau[i_mini]:
                i_mini = j
        tableau[i], tableau[i_mini] = tableau[i_mini], tableau[i]

liste_tailles = [300, 900, 2700]
liste_ref = [i for i in range(100)]
tref = timeit("tri_selection(liste_ref)", number = 20, globals = globals())
print("n = 100 : tref = ",tref)
for n in liste_tailles :
    print("n =", n, end = '')
    # Création de la liste triée de taille n : [0, 1, ...,n-1]
    liste_cr = [i for i in range(n)]
    # Temps d'éxécution du tri
    temps = timeit("tri_selection(liste_cr)", number = 20, globals = globals())
    # Affichage
    print('\t-> temps = ', round(temps, 2), '\t x', round(temps/tref, 2) )
    # Nouveau temps de référence avant le tour de boucle suivant
    tref = temps
Solution

Lorsque la taille de la liste et multipliée par 3, la temps est multiplié par 9. Or \(3^2 = 9\)

Cela correspond bien à une complexité quadratique

Activité 5 - Visualisation du temps de calcul du tri par sélection

Notebook

Activité 6 - Correction de l'algorithme de tri par sélection

Démontrer, par récurrence, la correction de l'algorithme de tri par sélection.

Rappel sur les invariant de boucle

On appelle invariant d’une boucle une propriété qui, si elle est vraie avant l’exécution d’une itération, le demeure après l’exécution de l’itération.

Un invariant de boucle bien choisi permet de prouver qu’une boucle produit le résultat attendu (correction).

Réponse

Après la ième itération (boucle en i de l’algorithme fourni) les i premiers éléments sont triés.

a. Vérifions sur l'exemple du tri de : [9, 5, 8, -2, 6, 4]

Numéro d'étape partie gauche de la liste reste de la liste
liste de départ [] [9, 5, 8, -2, 6, 4]
1 [-2] [5, 8, 9, 6, 4]
2 [-2, 4] [8, 9, 6, 5]
3 [-2, 4, 5] [9, 6, 8]
4 [-2, 4, 5, 6] [9, 8]
5 [-2, 4, 5, 6, 8] [9]

b. Raisonnement « par récurrence »

  • Après la première itération, le 1er élément étant seul est forcément « trié ».
  • Supposons qu’après la ième itération les i premiers éléments soient triés. Par construction le ième élément est plus petit ou égal que tous ceux qui suivent (sinon ce serait un autre qui aurait été le minimum, et donc mis à le place du ième élément).
  • A l’itération suivante on met au rang i+1 un élément de la liste restante, donc supérieur ou égal au ième élément. La liste des i+1 premiers éléments est donc triée.

On prouve ainsi de proche en proche que pour n’importe quel entier i, après la ième itération, les i premiers éléments sont triés. Cela prouve la correction de l’algorithme de tri par sélection, car après n itérations une liste de longueur n est donc triée.

A retenir
  • La complexité du tri par sélection est quadratique : \(O(n^2)\)

  • L’algorithme de tri par sélection se termine car il a un nombre limité de tours de boucles, du fait qu'il ne comporte que des boucles Pour qui sont des boucles bornées.

Le tri par sélection en python
def tri_selection(tableau):
    for i in range(len(tableau) - 1):
        i_mini = i
        for j in range(i + 1, len(tableau)):
            if tableau[j] < tableau[i_mini]:
                i_mini = j
        tableau[i], tableau[i_mini] = tableau[i_mini], tableau[i]