0 XP
?
Algorithmes Avancés/Algorithmes de tri
Avancé60 min40 XP

Objectifs de cette leçon

  • Implémenter les tris (bulles, fusion, rapide)
  • Comparer la complexité de chaque algorithme de tri
  • Choisir le bon tri en fonction du contexte

Algorithmes de tri 🔍

Trier des données est l'un des problèmes les plus étudiés en informatique. Il existe des dizaines d'algorithmes, chacun avec des forces et faiblesses différentes. Comprendre ces algorithmes, c'est comprendre la complexité algorithmique en pratique.


1. Bubble Sort (Tri à bulles) — O(n²) 🫧

Principe : On parcourt le tableau en comparant les éléments adjacents. Si deux éléments sont dans le mauvais ordre, on les échange. Les plus grands éléments "remontent" vers la fin comme des bulles dans un verre.

Exécution pas à pas

pseudo
1/12
🧩
FONCTION tri_bulles(tableau)
⚙️FONCTION tri_bulles(tableau)Le programme exécute cette action, puis passe à la suivante.

On définit une fonction appelée "tri_bulles(tableau)". Cette fonction contient des instructions qui seront exécutées quand on l'appelle.

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

Pas à pas sur [5, 3, 8, 1] :

  1. 5>3 → échange → [3, 5, 8, 1] — 5 remonte
  2. 5<8 → rien → [3, 5, 8, 1]
  3. 8>1 → échange → [3, 5, 1, 8] — 8 a remonté jusqu'à la fin !
  4. Deuxième tour : 3<5, 5>1 → échange → [3, 1, 5, 8]
  5. Dernier tour : 3>1 → échange → [1, 3, 5, 8] ✅

Complexité : O(n²) dans tous les cas. Simple à comprendre, lent en pratique.


2. Insertion Sort (Tri par insertion) — O(n²) 📥

Principe : On construit le tableau trié un élément à la fois. On prend chaque élément et on l'insère à sa bonne place parmi les éléments déjà triés (comme quand on trie des cartes à jouer).

Exécution pas à pas

pseudo
1/13
🧩
FONCTION tri_insertion(tableau)
⚙️FONCTION tri_insertion(tableau)Le programme exécute cette action, puis passe à la suivante.

On définit une fonction appelée "tri_insertion(tableau)". Cette fonction contient des instructions qui seront exécutées quand on l'appelle.

def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]    # L'élément à insérer
        j = i - 1
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]  # Décalage
            j -= 1
        arr[j + 1] = key  # Insertion
    return arr

Pas à pas sur [5, 3, 8, 1] :

  1. i=1, clé=3. 5>3 → décale → [5, 5, 8, 1] → insère 3 → [3, 5, 8, 1]
  2. i=2, clé=8. 5<8 → rien → [3, 5, 8, 1]
  3. i=3, clé=1. 8>1, 5>1, 3>1 → décale → [3, 5, 8, 8] → [3, 5, 5, 8] → [3, 3, 5, 8] → insère 1 → [1, 3, 5, 8] ✅

💡 Performant sur les petits tableaux et les tableaux presque triés (O(n) au meilleur cas).


3. Merge Sort (Tri fusion) — O(n log n) 🧩

Principe : Diviser pour régner (divide and conquer).

  1. Diviser le tableau en deux moitiés
  2. Trier récursivement chaque moitié
  3. Fusionner les deux moitiés triées

Exécution pas à pas

pseudo
1/31
🧩
FONCTION tri_fusion(tableau)
⚙️FONCTION tri_fusion(tableau)Le programme exécute cette action, puis passe à la suivante.

On définit une fonction appelée "tri_fusion(tableau)". Cette fonction contient des instructions qui seront exécutées quand on l'appelle.

def tri_fusion(tab):
    if len(tab) <= 1:
        return tab
    milieu = len(tab) // 2
    gauche = tri_fusion(tab[:milieu])
    droite = tri_fusion(tab[milieu:])
    return fusionner(gauche, droite)

def fusionner(g, d):
    resultat = []
    i = j = 0
    while i < len(g) and j < len(d):
        if g[i] <= d[j]:
            resultat.append(g[i])
            i += 1
        else:
            resultat.append(d[j])
            j += 1
    resultat.extend(g[i:])
    resultat.extend(d[j:])
    return resultat

tab = [8, 3, 5, 1, 9, 2]
print(tri_fusion(tab))  # [1, 2, 3, 5, 8, 9]

Visualisation : [8, 3, 5, 1, 9, 2]

  1. Diviser → [8, 3, 5] et [1, 9, 2]
  2. Diviser → [8] [3, 5] et [1, 9] [2]
  3. Trier → [8] [3, 5] et [1, 9] [2]
  4. Fusionner → [3, 5, 8] et [1, 2, 9]
  5. Fusionner → [1, 2, 3, 5, 8, 9] ✅

Toujours O(n log n) — même complexité dans le meilleur et le pire cas. Stable et prévisible.


4. Quick Sort (Tri rapide) — O(n log n) en moyenne ⚡

Principe : Choisir un pivot, partitionner le tableau autour de ce pivot, puis trier récursivement chaque partition.

Exécution pas à pas

pseudo
1/12
🧩
FONCTION tri_rapide(tableau)
⚙️FONCTION tri_rapide(tableau)Le programme exécute cette action, puis passe à la suivante.

On définit une fonction appelée "tri_rapide(tableau)". Cette fonction contient des instructions qui seront exécutées quand on l'appelle.

def tri_rapide(tab):
    if len(tab) <= 1:
        return tab
    pivot = tab[len(tab) // 2]
    gauche = [x for x in tab if x < pivot]
    milieu = [x for x in tab if x == pivot]
    droite = [x for x in tab if x > pivot]
    return tri_rapide(gauche) + milieu + tri_rapide(droite)

tab = [8, 3, 5, 1, 9, 2]
print(tri_rapide(tab))  # [1, 2, 3, 5, 8, 9]

💡 Rapide en pratique mais O(n²) dans le pire cas (pivot mal choisi). C'est le tri le plus utilisé dans les langages.


5. Comparaison des tris ⚖️

AlgorithmeMeilleurMoyenPireMémoireStable ?
BubbleO(n)O(n²)O(n²)O(1)
InsertionO(n)O(n²)O(n²)O(1)
MergeO(n log n)O(n log n)O(n log n)O(n)
QuickO(n log n)O(n log n)O(n²)O(log n)

Quand utiliser quoi ?

  • Insertion : petits tableaux (< 50 éléments) ou presque triés
  • Merge : quand la stabilité et la prévisibilité comptent
  • Quick : usage général, grands tableaux
  • Bubble : à éviter en pratique (pédagogique uniquement)

6. Recherche dichotomique (Binary Search) — O(log n) 🎯

Une fois le tableau trié, on peut chercher très rapidement :

Exécution pas à pas

pseudo
1/19
🧩
FONCTION recherche_dichotomique(tableau, cible)
⚙️FONCTION recherche_dichotomique(tableau, cible)Le programme exécute cette action, puis passe à la suivante.

On définit une fonction appelée "recherche_dichotomique(tableau, cible)". Cette fonction contient des instructions qui seront exécutées quand on l'appelle.

def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

tab = [1, 3, 5, 7, 9, 11, 13]
print(binary_search(tab, 7))  # 3

💡 Avec 1 million d'éléments triés, la recherche dichotomique trouve la réponse en ~20 étapes (log₂ 1 000 000 ≈ 20). Une recherche linéaire en prendrait 500 000 en moyenne !


Exercices pour toi 🎯

  1. Implémente Bubble Sort avec une optimisation : arrête-toi si aucune permutation n'a eu lieu pendant un tour
  2. Merge Sort récursif : trace les appels récursifs pour [4, 2, 7, 1]
  3. Binary Search : trouve le point d'insertion d'une valeur dans un tableau trié
  4. Compare les temps d'exécution des 4 tris sur [100, 1000, 10000] éléments

Exécution pas à pas

n > 1n = 1Pivot
0/7

État de la mémoire

2 variables
Variable
Valeur
Type
Addr.
tableau
[1,2,3,5,8,9]
list
0x4000
comparaisons
O(n log n)
str
0x4010
Aucune variable active
tableau0x4000
[1,2,3,5,8,9]
list
comparaisons0x4010
O(n log n)
str

⚡ Simulateur — Tri à bulles

Étape 1/21
📝 Pseudo-code
1FONCTION tri_bulles(tableau)
2 n ← taille(tableau)
3 POUR i ALLANT DE 0 À n-1 FAIRE
4 POUR j ALLANT DE 0 À n-i-2 FAIRE
5 SI tableau[j] > tableau[j+1] ALORS
6 échanger(tableau[j], tableau[j+1])
7 FIN SI
8 FIN POUR
9 FIN POUR
10 RETOURNER tableau
11FIN FONCTION
📊 Visualisation du tri4 éléments
5
0
3
1
8
2
1
3
Comparaison Échange En place
💡 Bubble Sort
  • Compare chaque paire d'éléments adjacents
  • Les échange s'ils sont dans le mauvais ordre
  • Le plus grand élément « remonte » à sa place à chaque passe
  • Complexité : O(n²) dans le pire cas

Bubble Sort : [5, 3, 8, 1].