Tri à bulles et tri par insertion
Codez ces deux algorithmes de tri quadratiques, comprenez pourquoi ils sont en O(n²) et repérez le cas où le tri par insertion est plus performant que le tri fusion.
Tri à bulles et tri par insertion est une leçon DSA Interview Prep gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage DSA Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DSA Interview Prep comprend 4 leçons au total.
Pourquoi étudier les tris en O(n²) ?
Le tri à bulles et le tri par insertion sont en O(n²) dans le pire cas, ce qui les rend peu adaptés aux entrées volumineuses. Pourtant, tout entretien sérieux sur les algorithmes attend de vous que vous sachiez les implémenter et les analyser. Ils enseignent des concepts fondamentaux — comparaison, échange, tri stable et comportement dans le meilleur cas — qui s’appliquent à des algorithmes plus avancés. Les examinateurs les utilisent pour vérifier que vous savez raisonner sur les invariants de boucle et la notation asymptotique à partir des principes fondamentaux.
# When O(n^2) is acceptable:
# n <= 1000: 10^6 ops, runs in milliseconds
# nearly-sorted data: insertion sort beats merge sort
# constant factor so small (simple ops) that overhead matters
import time
def time_sort(sort_fn, data):
import copy
arr = copy.copy(data)
t = time.perf_counter()
sort_fn(arr)
return time.perf_counter() - t
print('Small n: quadratic sorts are fine')Tri à bulles : faire remonter le maximum
Le tri à bulles parcourt plusieurs fois le tableau et échange les éléments adjacents qui ne sont pas dans le bon ordre. Après chaque passage complet, le plus grand élément non trié « remonte » jusqu’à sa position finale à la fin du tableau. Après n-1 passages, le tableau entier est trié. Son nom vient de la façon dont les éléments les plus grands remontent comme des bulles. C’est l’algorithme de tri le plus simple à décrire, mais il est rarement utilisé en pratique.
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1): # n-1 passes
for j in range(n - 1 - i): # inner loop shrinks
if arr[j] > arr[j+1]: # out of order
arr[j], arr[j+1] = arr[j+1], arr[j] # swap
return arr
arr = [64, 34, 25, 12, 22, 11, 90]
bubble_sort(arr)
print(arr) # [11, 12, 22, 25, 34, 64, 90]Tri à bulles avec arrêt anticipé
Une version optimisée du tri à bulles utilise un indicateur swapped : si un passage interne complet n’effectue aucun échange, le tableau est déjà trié et l’exécution s’arrête immédiatement. Cela donne un meilleur cas en O(n) pour une entrée déjà triée — le seul véritable avantage du tri à bulles. Sans cet indicateur, l’algorithme effectue toujours O(n²) comparaisons. L’optimisation par arrêt anticipé est ce que les examinateurs vérifient lorsqu’ils demandent comment améliorer le tri à bulles.
def bubble_sort_optimised(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped: # already sorted!
print(f'Sorted after pass {i+1}')
break
arr1 = [1, 2, 3, 4, 5] # already sorted
bubble_sort_optimised(arr1) # exits after 1 passAnalyse de la complexité du tri à bulles
La boucle externe du tri à bulles s’exécute n-1 fois. La boucle interne s’exécute n-1-i fois à chaque passage : (n-1) + (n-2) + ... + 1 = n(n-1)/2 ≈ n²/2 comparaisons. Cela donne une complexité de O(n²) en moyenne et dans le pire cas. Avec l’indicateur d’arrêt anticipé, le meilleur cas passe à O(n) pour une entrée triée. La complexité en espace est O(1) — seul l’échange nécessite une variable temporaire. Le tri à bulles est stable : les éléments égaux conservent leur ordre relatif, car seuls les éléments strictement supérieurs sont échangés.
def bubble_sort_counted(arr):
n = len(arr)
swaps = comparisons = 0
for i in range(n-1):
for j in range(n-1-i):
comparisons += 1
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swaps += 1
return comparisons, swaps
arr = [5, 4, 3, 2, 1] # worst case: reversed
c, s = bubble_sort_counted(arr)
print(f'Comparisons: {c}, Swaps: {s}') # 10, 10 for n=5Tri par insertion : construire une main triée
Le tri par insertion imite le tri d’une main de cartes : prenez la carte suivante (l’élément) et insérez-la à la bonne position parmi les cartes déjà triées à gauche. L’invariant est que arr[0:i] est toujours trié. Pour chaque nouvel élément, décalez les éléments plus grands vers la droite afin de lui faire une place. Cet algorithme en place et stable a une complexité de O(n²) dans le pire cas, mais de O(n) dans le meilleur cas pour des données presque triées.
def insertion_sort(arr):
for i in range(1, len(arr)): # start from second element
key = arr[i] # element to insert
j = i - 1
# Shift larger elements to the right
while j >= 0 and arr[j] > key:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key # insert in correct position
return arr
arr = [12, 11, 13, 5, 6]
insertion_sort(arr)
print(arr) # [5, 6, 11, 12, 13]Tri par insertion étape par étape
Suivez le tri par insertion sur [3, 1, 4, 2] : i=1, clé=1, décalez 3 vers la droite → [1, 3, 4, 2]. i=2, clé=4, aucun décalage → tableau inchangé. i=3, clé=2, décalez 4 puis 3 vers la droite → [1, 2, 3, 4]. Chaque élément est comparé à ceux qui se trouvent à sa gauche jusqu’à trouver sa position correcte. La boucle interne while effectue les décalages à l’aide d’affectations, qui sont plus rapides que les échanges puisqu’un décalage nécessite une affectation, contre trois pour un échange.
def insertion_sort_trace(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j+1] = arr[j] # shift right (1 assignment)
j -= 1
arr[j+1] = key
print(f'After inserting {key}: {arr}')
insertion_sort_trace([3, 1, 4, 2])
# After inserting 1: [1, 3, 4, 2]
# After inserting 4: [1, 3, 4, 2] (no change)
# After inserting 2: [1, 2, 3, 4]Tri par insertion sur des données presque triées
L’atout majeur du tri par insertion est sa complexité de O(n + inversions). Une inversion est une paire (i,j) telle que i < j mais arr[i] > arr[j]. Pour les tableaux presque triés qui ne contiennent que quelques inversions, le tri par insertion est extrêmement rapide — parfois plus rapide en pratique que le tri fusion grâce à sa simplicité et à son accès au cache favorable. Le Timsort de Python utilise le tri par insertion sur les petits sous-tableaux, précisément pour cette raison.
# Nearly sorted: only 1 inversion
arr1 = [1, 2, 4, 3, 5] # 4>3 is the only inversion
def count_ops(arr):
arr = arr[:]
ops = 0
for i in range(1, len(arr)):
key = arr[i]; j = i - 1
while j >= 0 and arr[j] > key:
arr[j+1] = arr[j]; j -= 1; ops += 1
arr[j+1] = key
return ops
print(count_ops([1,2,4,3,5])) # 1 op (nearly sorted)
print(count_ops([5,4,3,2,1])) # 10 ops (reversed = worst case)Stabilité du tri
Un algorithme de tri est stable si les éléments égaux conservent leur ordre relatif initial après le tri. Le tri à bulles et le tri par insertion sont tous deux stables : ils n’échangent jamais les éléments égaux. La stabilité est importante lorsque vous triez successivement selon plusieurs clés : triez d’abord selon la clé secondaire, de manière stable, puis selon la clé primaire, également de manière stable, afin de conserver l’ordre lié à la clé secondaire entre les éléments ex æquo. Le tri fusion est lui aussi stable ; le tri par tas et le tri rapide ne le sont généralement pas.
# Stable sort preserves order of equal elements
students = [
('Alice', 85),
('Bob', 92),
('Carol', 85),
('Dave', 78),
]
# Sort by score ascending (stable: Alice before Carol for same score)
students.sort(key=lambda x: x[1])
for s in students:
print(s)
# ('Dave',78) ('Alice',85) ('Carol',85) ('Bob',92)
# Alice still comes before Carol => stableTri par insertion comme recherche binaire
La boucle interne du tri par insertion trouve à la fois la position correcte et décale les éléments. Vous pouvez utiliser une recherche binaire pour trouver la position en O(log i) comparaisons, mais les décalages prennent toujours O(i) : la complexité globale reste donc O(n²). Cette optimisation réduit le nombre de comparaisons, ce qui est utile lorsque les fonctions de comparaison sont coûteuses, mais elle ne réduit pas le nombre total d’opérations. Ce « tri par insertion binaire » apparaît dans Timsort pour les petites tailles de blocs.
import bisect
def binary_insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
# Find insertion point in O(log i)
pos = bisect.bisect_left(arr, key, 0, i)
# Shift elements to make room: still O(i)
arr[pos+1:i+1] = arr[pos:i]
arr[pos] = key
return arr
print(binary_insertion_sort([5, 2, 4, 6, 1, 3]))
# [1, 2, 3, 4, 5, 6]Tri à bulles ou tri par insertion : quand utiliser chacun
Lors des entretiens, énoncez cette comparaison avec assurance : le tri par insertion est strictement meilleur que le tri à bulles — les deux sont en O(n²) dans le pire cas et utilisent O(1) d’espace, mais le tri par insertion effectue moins d’écritures (O(n+k) pour k inversions contre O(n²) pour le tri à bulles), est plus adapté au cache et constitue le choix pratique pour les petites valeurs de n (Timsort l’utilise). Le seul véritable avantage du tri à bulles est sa simplicité pédagogique. En production, utilisez toujours le tri intégré au langage.
# Summary: when to use quadratic sorts
# Use insertion_sort when:
# - n <= 20 (small enough that O(n^2) is fine)
# - data is nearly sorted (few inversions => fast)
# - you need stable sort with O(1) space
# - implementing a hybrid (like Timsort)
# NEVER use bubble_sort in production code
# Python's built-in sort: O(n log n), stable, extremely fast
arr = [5, 2, 8, 1, 9]
print(sorted(arr)) # [1, 2, 5, 8, 9]
arr.sort()
print(arr) # [1, 2, 5, 8, 9]Compter les inversions comme métrique
Le nombre d’inversions dans un tableau est égal au nombre de paires (i,j) telles que i < j mais arr[i] > arr[j]. Le tri par insertion effectue exactement autant de décalages qu’il y a d’inversions — une observation utile. Compter efficacement les inversions, en O(n log n), nécessite un tri fusion modifié. Les examinateurs demandent parfois, pour approfondir une discussion sur le tri : « Dans quelle mesure votre algorithme tient-il compte des inversions ? »
# Count inversions: naive O(n^2)
def count_inversions_naive(arr):
count = 0
for i in range(len(arr)):
for j in range(i+1, len(arr)):
if arr[i] > arr[j]:
count += 1
return count
print(count_inversions_naive([3, 1, 2])) # 2: (3,1) and (3,2)
print(count_inversions_naive([1, 2, 3])) # 0: already sorted
print(count_inversions_naive([3, 2, 1])) # 3: all pairs invertedVérification rapide
Vérifiez votre compréhension des concepts de Structures de données & algorithmes — préparation aux entretiens de programmation présentés dans cette leçon.
Bilan de la leçon
Dans cette leçon, vous avez appris que : le tri à bulles effectue n-1 passages, chacun faisant remonter le maximum actuel jusqu’à sa position finale, avec une complexité de O(n²) dans le pire cas mais de O(n) dans le meilleur cas grâce à l’indicateur d’arrêt anticipé ; le tri par insertion décale les éléments vers la droite pour insérer la clé actuelle à la bonne position dans la partie triée, en O(n + inversions), ce qui le rend optimal pour les données presque triées ; et les deux algorithmes sont stables, utilisent O(1) d’espace et ont une complexité de O(n²) dans le pire cas — mais le tri par insertion est strictement préférable au tri à bulles dans toutes les situations pratiques. Nous allons maintenant implémenter le tri fusion à partir de zéro.
Questions Fréquemment Posées
La leçon « Tri à bulles et tri par insertion » est-elle gratuite ?
Oui — le texte complet de « Tri à bulles et tri par insertion » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours DSA Interview Prep, passe à CoddyKit PRO. Le cours DSA Interview Prep comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Tri à bulles et tri par insertion » ?
Codez ces deux algorithmes de tri quadratiques, comprenez pourquoi ils sont en O(n²) et repérez le cas où le tri par insertion est plus performant que le tri fusion. Tu pratiques DSA Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer DSA Interview Prep ?
Aucune expérience préalable n'est requise. DSA Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.
Combien de temps prend la leçon « Tri à bulles et tri par insertion » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon DSA Interview Prep ?
Oui. Chaque leçon DSA Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Tri à bulles et tri par insertion
- Tri fusion : diviser, trier, fusionner
- Tri rapide et sélection du pivot
- Tris sans comparaison et sort() de Python