0Pricing
DSA Interview Prep · Leçon

Tris sans comparaison et sort() de Python

Explorez le tri par comptage et le tri radix pour les tableaux d’entiers, et comprenez le fonctionnement interne de Timsort de Python lors des appels au tri intégré.

Tris sans comparaison et sort() de Python est une leçon DSA Interview Prep gratuite sur CoddyKit. Ceci est la leçon 4 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.

La borne inférieure O(n log n) pour les comparaisons

Tout algorithme de tri qui détermine l'ordre uniquement au moyen de comparaisons entre éléments nécessite au moins Ω(n log n) comparaisons dans le pire cas. Cela se démontre par l'argument de l'arbre de décision : trier n éléments nécessite de distinguer les n! ordres possibles. Un arbre de décision binaire (chaque nœud correspondant à une comparaison) doit comporter au moins log₂(n!) ≈ n log₂(n) niveaux. Pour contourner cette borne, nous avons besoin d'informations supplémentaires sur les éléments, par exemple qu'il s'agit d'entiers bornés.

import math

for n in [5, 10, 100, 1000]:
    lower_bound = n * math.log2(n)
    factorial_log = sum(math.log2(i) for i in range(1, n+1))
    print(f'n={n}: n*log2(n)={lower_bound:.1f}, log2(n!)={factorial_log:.1f}')

# n log n is a tight bound on comparison-based sorting

Tri par comptage : trier par fréquence

Le tri par comptage fonctionne en comptant la fréquence de chaque valeur, puis en reconstruisant le tableau trié à partir de ces comptes. Il faut connaître à l'avance l'intervalle [0, k) des valeurs. Complexité temporelle : O(n + k) ; complexité spatiale : O(k). Lorsque k est petit par rapport à n (par exemple pour trier des âges compris entre 0 et 120 ou des chiffres isolés), le tri par comptage surpasse tous les tris par comparaison. Lorsque k est grand, le coût en espace O(k) le rend peu pratique.

def counting_sort(arr, k=None):
    if not arr: return []
    if k is None: k = max(arr) + 1
    count = [0] * k
    for n in arr:
        count[n] += 1
    result = []
    for val, freq in enumerate(count):
        result.extend([val] * freq)
    return result

arr = [4, 2, 2, 8, 3, 3, 1]
print(counting_sort(arr))  # [1, 2, 2, 3, 3, 4, 8]
# O(n + k) where k = 9 (max value + 1)

Tri par comptage stable avec comptes cumulés

Pour un tri par comptage stable (important lors du tri d’objets selon une clé), calculez les comptes cumulés afin que cum[v] donne la position de départ de la valeur v dans la sortie. Parcourez le tableau d’entrée de droite à gauche, en plaçant chaque élément à la position cum[key] - 1, puis décrémentez cette position. Vous obtenez ainsi un tri stable : les éléments ayant la même clé apparaissent dans le même ordre relatif que dans l’entrée.

def counting_sort_stable(arr, k):
    count = [0] * k
    for n in arr: count[n] += 1
    # Cumulative counts: count[v] = first position for value v
    for i in range(1, k): count[i] += count[i-1]
    output = [0] * len(arr)
    # Fill from right to maintain stability
    for n in reversed(arr):
        count[n] -= 1
        output[count[n]] = n
    return output

print(counting_sort_stable([4,2,2,8,3,3,1], 9))
# [1, 2, 2, 3, 3, 4, 8]

Tri par base : trier chiffre par chiffre

Le tri par base trie les entiers chiffre par chiffre, du chiffre le moins significatif (LSD) au plus significatif (MSD), en utilisant un tri stable (comme le tri par comptage) à chaque position. Après d passes (une par chiffre), le tableau est entièrement trié. Complexité temporelle : O(d × (n + k)), où d = nombre de chiffres et k = base (généralement 10). Pour n entiers bornés par W, d = log_k(W), soit O(n log_k(W)) au total.

def radix_sort(arr):
    if not arr: return []
    max_val = max(arr)
    exp = 1  # current digit position (1, 10, 100, ...)
    while max_val // exp > 0:
        arr = counting_sort_by_digit(arr, exp)
        exp *= 10
    return arr

def counting_sort_by_digit(arr, exp):
    n = len(arr)
    output = [0] * n
    count = [0] * 10
    for n_ in arr: count[(n_ // exp) % 10] += 1
    for i in range(1, 10): count[i] += count[i-1]
    for n_ in reversed(arr):
        d = (n_ // exp) % 10
        count[d] -= 1
        output[count[d]] = n_
    return output

print(radix_sort([170, 45, 75, 90, 802, 24, 2, 66]))
# [2, 24, 45, 66, 75, 90, 170, 802]

Tri par compartiments : répartir dans des compartiments

Le tri par compartiments répartit les éléments dans un nombre fixe de compartiments selon leur intervalle de valeurs, trie chaque compartiment (avec un tri par insertion pour les petits compartiments), puis les concatène. Pour des données uniformément réparties dans [0, 1), n compartiments donnent un temps moyen en O(n). Complexité temporelle : O(n + k) en moyenne, O(n²) dans le pire cas (tous les éléments dans un seul compartiment). Cette méthode est particulièrement utile lorsque la distribution des données est connue et approximativement uniforme.

def bucket_sort(arr):
    if not arr: return []
    n = len(arr)
    min_v, max_v = min(arr), max(arr)
    if min_v == max_v: return arr[:]
    buckets = [[] for _ in range(n)]
    # Map each value to a bucket index
    for v in arr:
        idx = int((v - min_v) / (max_v - min_v + 1e-9) * n)
        idx = min(idx, n - 1)
        buckets[idx].append(v)
    result = []
    for bucket in buckets:
        bucket.sort()  # insertion sort for small buckets
        result.extend(bucket)
    return result

print(bucket_sort([0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21]))
# sorted list

Le Timsort de Python en détail

Les fonctions sorted() et list.sort() de Python utilisent Timsort, conçu par Tim Peters en 2002. Timsort est un hybride du tri fusion et du tri par insertion. Il recherche les « séquences naturelles » (des sous-séquences déjà triées) et utilise le tri par insertion pour construire des séquences allant jusqu’à 64 éléments. Il fusionne ensuite les séquences avec le tri fusion, en appliquant plusieurs optimisations : le saut par blocs (pour ignorer plusieurs éléments d’un coup lorsqu’une séquence est dominante) et l’empilement des longueurs de séquences.

# Timsort properties:
# - Stable
# - O(n log n) worst case
# - O(n) best case (data already sorted)
# - O(n) auxiliary space
# - Highly optimised for real-world data with runs

import time

# Nearly sorted data: Timsort is extremely fast
nearly_sorted = list(range(10000))
nearly_sorted[-1] = 0  # one mis-placed element

t = time.perf_counter()
not_used = sorted(nearly_sorted)
elapsed = time.perf_counter() - t
print(f'Timsort on nearly-sorted n=10000: {elapsed*1000:.3f} ms')

Le tri Python avec sort() ou sorted() : principales différences

list.sort() trie sur place, renvoie None et ne fonctionne que sur les listes. sorted(iterable) fonctionne avec tout objet itérable (tuples, générateurs, dictionnaires) et renvoie une nouvelle liste. Les deux acceptent les paramètres key et reverse. Une erreur courante consiste à affecter le résultat de lst.sort() à une variable, puis à se demander pourquoi cette variable vaut None. Utilisez toujours sorted() lorsque vous avez besoin de la version triée tout en conservant l’original.

nums = [3, 1, 4, 1, 5, 9]

# in-place: returns None
result = nums.sort()
print(result)  # None  (common bug!)
print(nums)    # [1, 1, 3, 4, 5, 9]  (modified)

nums2 = [3, 1, 4, 1, 5, 9]
# out-of-place: returns new list
result2 = sorted(nums2)
print(result2)  # [1, 1, 3, 4, 5, 9]
print(nums2)    # [3, 1, 4, 1, 5, 9]  (unchanged)

Clés de tri personnalisées en entretien

Le tri de Python accepte une fonction key évaluée une seule fois par élément (contrairement au comparateur de C, appelé pour chaque paire). Parmi les clés de tri courantes en entretien, on trouve len pour la longueur d’une chaîne, lambda x: -x pour un ordre décroissant, lambda x: (x[1], x[0]) pour un tri selon plusieurs clés, et str.lower pour ignorer la casse. Le tri de Python est garanti stable, ce qui permet aux tris selon plusieurs clés de fonctionner correctement.

# Sort by length, then alphabetically
words = ['banana', 'fig', 'apple', 'date', 'kiwi']
print(sorted(words, key=lambda w: (len(w), w)))
# ['fig', 'date', 'kiwi', 'apple', 'banana']

# Sort integers as strings (largest concatenation first)
nums = [3, 30, 34, 5, 9]
print(sorted(map(str, nums), key=lambda a: a*10, reverse=True))
# ['9', '5', '34', '3', '30']  => '9534330'

# Descending sort
print(sorted([3,1,4,1,5], reverse=True))  # [5,4,3,1,1]

Quand utiliser chaque tri en entretien

Choisissez le tri adapté au contexte :

  • Utilisez sorted()/list.sort() : choix par défaut pour tous les problèmes d’entretien — Timsort est optimal
  • Tri par comptage : lorsque les valeurs sont de petits entiers bornés (de 0 à k, avec k petit)
  • Tri par base : lorsque vous triez beaucoup d’entiers dont la largeur en bits ou le nombre de chiffres est connu
  • Tri par compartiments : lorsque les données sont des nombres flottants uniformément répartis dans un intervalle connu
  • Implémentez un tri fusion : lorsqu’on vous demande de coder à partir de zéro un tri stable en O(n log n)

# Problem: sort array of 0s, 1s, 2s efficiently
# Counting sort: O(n), O(1) space  (k=3 is tiny)

def sort_012(arr):
    count = [0, 0, 0]
    for n in arr:
        count[n] += 1
    i = 0
    for val in range(3):
        for _ in range(count[val]):
            arr[i] = val; i += 1

arr = [2, 0, 2, 1, 1, 0]
sort_012(arr)
print(arr)  # [0, 0, 1, 1, 2, 2]

Trier sans tri : les k meilleurs éléments avec un tas

De nombreux problèmes d’entretien demandent des résultats « semblables à un tri » sans exiger un tri complet. Pour trouver les k meilleurs éléments, un tas min de taille k s’exécute en O(n log k), ce qui est plus rapide que O(n log n) lorsque k << n. Pour trouver le k-ième plus grand élément, la sélection rapide s’exécute en O(n) en moyenne. Pour trouver la médiane, l’approche à deux tas s’exécute en O(log n) par insertion. Ces approches de tri partiel sont à connaître, car elles constituent des solutions plus rapides qu’un tri complet.

import heapq

# Top-k with heap: O(n log k)
def top_k(nums, k):
    return heapq.nlargest(k, nums)  # uses heap of size k internally

print(top_k([3,2,1,5,6,4], 2))    # [6, 5]

# kth largest: quickselect O(n) average
import random
def kth_largest(nums, k):
    def _select(lo, hi, target):
        if lo >= hi: return nums[lo]
        rand_i = random.randint(lo, hi)
        nums[rand_i], nums[hi] = nums[hi], nums[rand_i]
        pivot = nums[hi]; i = lo - 1
        for j in range(lo, hi):
            if nums[j] >= pivot: i+=1; nums[i],nums[j]=nums[j],nums[i]
        nums[i+1],nums[hi]=nums[hi],nums[i+1]
        p = i + 1
        if p == target: return nums[p]
        return _select(lo, p-1, target) if target < p else _select(p+1, hi, target)
    return _select(0, len(nums)-1, k-1)

print(kth_largest([3,2,1,5,6,4], 2))  # 5

Stabilité du tri selon plusieurs clés

La stabilité permet d’effectuer correctement un tri selon plusieurs clés : triez d’abord selon la clé secondaire (de manière stable), puis selon la clé principale (également de manière stable). L’ordre secondaire est conservé pour les éléments ex æquo selon la clé principale. Cette technique est utilisée dans les bases de données (ORDER BY col1, col2) et dans le tri par base (chaque passage sur un chiffre doit être stable pour que l’algorithme global soit correct). Le tri de Python est toujours stable, ce qui rend ce modèle fiable.

data = [
    ('Alice', 'Math',    90),
    ('Bob',   'Science', 85),
    ('Carol', 'Math',    90),
    ('Dave',  'Science', 90),
]
# Sort by score DESC, then by subject ASC (for ties)
# Step 1: sort by subject (secondary)
data.sort(key=lambda x: x[1])
# Step 2: sort by score DESC (primary, stable)
data.sort(key=lambda x: x[2], reverse=True)
for row in data:
    print(row)
# All score=90 rows: Math before Science (preserved from step 1)

Vérification rapide

Vérifiez votre compréhension des concepts de Structures de données et algorithmes — préparation aux entretiens de programmation abordés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que : les tris par comparaison sont bornés inférieurement par O(n log n) — dépasser cette borne nécessite des informations ne reposant pas sur les comparaisons, comme des entiers bornés ; le tri par comptage atteint O(n + k) en comptant les fréquences, le tri par base traite les chiffres avec une complexité totale de O(d × (n + k)), et le tri par compartiments exploite une distribution uniforme pour atteindre O(n) en moyenne ; et le Timsort de Python est le choix pratique par défaut : stable, en O(n log n) dans le pire cas, en O(n) dans le meilleur cas et plus rapide que toute solution codée manuellement pour des données réelles. La prochaine étape consiste à maîtriser la recherche binaire classique.

Questions Fréquemment Posées

La leçon « Tris sans comparaison et sort() de Python » est-elle gratuite ?

Oui — le texte complet de « Tris sans comparaison et sort() de Python » 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 « Tris sans comparaison et sort() de Python » ?

Explorez le tri par comptage et le tri radix pour les tableaux d’entiers, et comprenez le fonctionnement interne de Timsort de Python lors des appels au tri intégré. 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 4 sur 4.

Combien de temps prend la leçon « Tris sans comparaison et sort() de Python » ?

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

  1. Tri à bulles et tri par insertion
  2. Tri fusion : diviser, trier, fusionner
  3. Tri rapide et sélection du pivot
  4. Tris sans comparaison et sort() de Python
← Retour à DSA Interview Prep