Exercice 1 : Algorithmes de tri
Problème : Étude comparative et complexité des algorithmes de tri en Python
Section titled “Problème : Étude comparative et complexité des algorithmes de tri en Python”Ce problème propose d’étudier de manière rigoureuse plusieurs algorithmes de tri en Python, en mettant l’accent sur leur complexité algorithmique, leur stabilité, ainsi que sur les limites pratiques liées à l’architecture des machines et à l’implémentation du langage.
Énoncé
Section titled “Énoncé”Dans tout le problème, on s’intéresse au tri d’un tableau (représenté par une list en Python) de éléments appartenant à un ensemble muni d’un ordre total . On suppose que la comparaison de deux éléments s’effectue en temps .
On rappelle qu’en Python, les objets de type list sont des tableaux dynamiques de pointeurs vers des objets en mémoire.
Questions
Section titled “Questions”Partie I : Algorithmes itératifs et récursifs fondamentaux
Section titled “Partie I : Algorithmes itératifs et récursifs fondamentaux”- [Tri par insertion] Écrire une fonction Python
tri_insertion(T)qui trie le tableauTen place en utilisant l’algorithme du tri par insertion. - Déterminer la complexité en temps dans le pire des cas et dans le meilleur des cas de ce tri par insertion. Cet algorithme est-il stable ? (Justifier brièvement).
- [Tri fusion] Rappeler le principe du tri fusion (diviser pour régner). Écrire une fonction
fusion(A, B)qui fusionne deux listes triéesAetBen une nouvelle liste triée, puis la fonctiontri_fusion(T)qui renvoie une nouvelle liste triée. - Établir la relation de récurrence vérifiée par le nombre d’opérations du tri fusion, puis en déduire par le théorème maître (ou par dépliement) sa complexité temporelle dans le pire des cas.
Partie II : Analyse critique des limites du modèle
Section titled “Partie II : Analyse critique des limites du modèle”- [Limites de la ré recursion en Python] Que se passe-t-il si l’on applique la fonction
tri_fusionsur un tableau de taille ? Quelle est la cause technique de cette limite en Python ? - [Analyse de complexité spatiale et modèle machine]
- Quelle est la complexité spatiale (mémoire additionnelle) de
tri_insertion? - Quelle est celle de
tri_fusion? - Analyse critique : Le modèle RAM (Random Access Machine) suppose que l’accès à n’importe quelle case mémoire coûte . Discuter de la validité de cette hypothèse pour de très grands tableaux sur une architecture matérielle moderne (notion de cache CPU, défauts de cache).
- Quelle est la complexité spatiale (mémoire additionnelle) de
Pistes et Indications
Section titled “Pistes et Indications”- Q1 : Utiliser une boucle
whileinterne pour faire “remonter” l’élément courant vers la gauche. - Q3 : Pour la fusion, deux indices parcourant
AetBsimultanément suffisent. - Q5 : Penser à la limite de profondeur de la pile d’appels (
sys.getrecursionlimit()). - Q6 : Considérer la localité des références en mémoire (principe de localité spatiale).
Corrigé pas à pas
Section titled “Corrigé pas à pas”Partie I
Section titled “Partie I”1. Tri par insertion
Section titled “1. Tri par insertion”def tri_insertion(T): n = len(T) for i in range(1, n): cle = T[i] j = i - 1 while j >= 0 and T[j] > cle: T[j + 1] = T[j] j -= 1 T[j + 1] = cle2. Complexité et stabilité du tri par insertion
Section titled “2. Complexité et stabilité du tri par insertion”- Meilleur des cas (tableau déjà trié) : La condition
T[j] > cleest immédiatement fausse. La bouclewhilene s’exécute jamais. La complexité est en . - Pire des cas (tableau trié en ordre inverse) : Pour chaque , on parcourt tous les éléments précédents. Le nombre d’opérations est , soit une complexité en .
- Stabilité : L’algorithme est stable. La condition
T[j] > cle(avec un ) fait qu’on ne décale pas les éléments égaux, préservant ainsi l’ordre initial des éléments équivalents.
3. Tri fusion
Section titled “3. Tri fusion”def fusion(A, B): res = [] i, j = 0, 0 na, nb = len(A), len(B) while i < na and j < nb: if A[i] <= B[j]: res.append(A[i]) i += 1 else: res.append(B[j]) j += 1 res.extend(A[i:]) res.extend(B[j:]) return res
def tri_fusion(T): n = len(T) if n <= 1: return list(T) milieu = n // 2 gauche = tri_fusion(T[:milieu]) droite = tri_fusion(T[milieu:]) return fusion(gauche, droite)4. Complexité du tri fusion
Section titled “4. Complexité du tri fusion”La relation de récurrence pour le nombre d’opérations est : Par application du théorème maître (cas où , donc ), la complexité temporelle est dans dans tous les cas.
Partie II : Analyse critique et limites du modèle
Section titled “Partie II : Analyse critique et limites du modèle”5. Limites de la récursion en Python
Section titled “5. Limites de la récursion en Python”Pour , l’appel à tri_fusion va engendrer une profondeur de récursion de l’ordre de appels. Bien que cette profondeur semble faible, la création de sous-listes par slicing (T[:milieu]) à chaque étape duplique les références en mémoire et consomme énormément d’espace.
Plus fondamentalement, si l’on concevait un tri récursif naïf sans limite de taille de données mais avec une profondeur de pile importante, Python lèverait une exception de type RecursionError: maximum recursion depth exceeded. Par défaut, la limite de la pile d’appels est fixée à en Python pour éviter les plantages de la machine par débordement de pile (stack overflow).
6. Analyse de complexité spatiale et modèle machine
Section titled “6. Analyse de complexité spatiale et modèle machine”- Complexité spatiale de
tri_insertion: en mémoire auxiliaire, car le tri est effectué en place. - Complexité spatiale de
tri_fusion: ou selon l’optimisation, car les fusions successives et les tranches (slices) recréent de nouvelles listes allouées en mémoire.
Analyse critique des limites du modèle RAM : Le modèle RAM classique postule un coût uniforme pour l’accès à n’importe quelle position mémoire. En pratique, sur les architectures matérielles modernes, ce modèle est mis en défaut par la hiérarchie mémoire (registres, caches L1/L2/L3, RAM).
- Un algorithme comme le tri fusion réalise des accès mémoire dispersés lors de la fusion de grands tableaux, ce qui provoque de nombreux défauts de cache (cache misses), ralentissant considérablement l’exécution par rapport au coût théorique.
- À l’inverse, le tri par insertion, bien que , possède une excellente localité spatiale (les éléments accédés sont contigus en mémoire), ce qui le rend paradoxalement plus rapide que le tri fusion pour des tableaux de très petite taille (c’est pourquoi les bibliothèques standard comme Timsort combinent tri par fusion et tri par insertion pour les petits sous-tableaux).