Algorithmes de tris PC* La problématique de tri d'un ensemble d'ob jets pouvant être ordonnés est une question fondamentale dans le traitement des données. De nombreux algorithmes portant sur de tels ensembles sont facilités si on dispose d'une relation d'ordre et si on est par exemple capable de ranger les éléments de l'ensemble dans l'ordre croissant. Cet ordre peut être l'ordre naturel sur l'ensemble des réels, sur l'ensemble des entiers écrits dans une base b quelconque ou encore l'ordre lexicographique pour trier des chaînes de caractère. Dans la suite on va présenter quelques algorithmes usuels de tri et on les appliquera à des listes d'entier. On comparera les performances de ces algorithmes et leur facilité d'implémentation. I : Tri par insertion C'est la manière naturelle de trier par exemple un tas de copie par ordre de note ou par ordre alphabétique. On prend la copie du haut du tas, puis la deuxième que l'on place dans l'ordre choisi, puis la troisième que l'on insère à sa bonne place etc. Ce tri est lent mais on voit évoluer en haut un tas trié et en dessous un tas non encore trié. Le travail s'achève avec la dernière copie. 1. Un exemple Nous utiliserons cette méthode en triant les éléments d'une liste en plaçant le plus petit terme à gauche et en commençant avec le premier élément à gauche de la liste. (a) Utiliser cette méthode pour trier pas à pas la liste L = [7, 2, 5, 8, 4, 6, 2, 3, 6]. (b) Même étude avec les deux listes L1 = [1, 3, 5, 7, 9, 12] et L2 = [9, 7, 6, 4, 2, 1]. Que constatez-vous ? 2. Mise en place de l'algorithme (a) Écrire une procédure Tri_insert qui partant d'une liste a la retourne triée. On remarquera qu'il n'y pas de copie de liste mais seulement des échanges d'éléments deux à deux. On pourra imbriquer deux boucles. (b) Mettre en évidence dans la boucle principale une structure qui dépend de l'indice i mais qui reste inchangée à chaque étape. Cette structure est appelée invariant de boucle. (c) Mettre en évidence un invariant de la boucle secondaire. I.P.T. Tris 1 PC* Algorithmes de tris 3. Performance de l'algorithme (a) Donner un majorant du nombre d'opérations élémentaires nécessaires pour trier une liste de n éléments. (b) Préciser le nombre d'opérations dans le pire des cas et dans le meilleur des cas. (c) Quelle complexité obtient-on pour cet algorithme ? (d) On peut estimer qu'en moyenne, pour insérer un nouvel élément sur dans une liste triée de de p éléments, on eectue p/2 échanges. Donner une estimation moyenne d'une nombre d'opérations nécessaires au tri. II : Tri rapide quicksort L'idée essentielle de cette deuxième méthode est de diviser le problème en deux sous-problèmes de tri de taille plus petite. Pour qu'au nal le tas soit eectivement trié il sut de découper le paquet en deux parties A et B avec ∀a ∈ A, ∀b ∈ B, a ≤ b. Pour trier un jeu de cartes on peut ainsi repérer une carte pivot qui permettra de diviser le paquet en deux sous paquets ayant cette propriété puis on recommence sur chacun des sous-paquets crées. Pour obtenir les deux sous-paquets on procède à des comparaisons et à des échanges. Cette méthode semble intuitivement plus rapide. 1. Exemple Trier par cette méthode la liste de cartes (de même couleur) L = [3, 1, 6, 7, D, 9, 5, 8, 10, R, V, 2, 4]. Comptabiliser le nombre d'échanges. 2. Séparer en deux listes (a) Écrire une procédure Echange(a,i,j) qui échange les termes a[i] et a[j] de la liste. (b) Écrire une procédure Partition(a,g,d) a étant une liste, g, d deux indices avec 0 ≤ g < d ≤ len(a) qui prend a[g] comme pivot et qui modie la partie [a[g], . . . , a[d]] en une partie de même longueur mais du type [éléments plus petits que a[g]], a[g], [éléments plus grands que a[g]]. (c) Écrire une procédure Tri_rapide qui pourra faire appel à ellemême (procédure récursive). I.P.T. Tris 2 PC* Algorithmes de tris 3. Performance de l'algorithme On note C(n) le coût du tri rapide pour un tableau de taille n. (a) Montrer que dans le pire des cas C(n) = n − 1 + C(n − 1). (b) Montrer que dans le meilleur des cas C(n) = n − 1 + 2C(n/2). En déduire alors que C(n) ∼ n ln(n). 4. Amélioration de l'algorithme Pour se placer dans le meilleur des cas on propose un choix aléatoire du pivot puis on diminue les appels récursifs par l'utilisation d'une boucle. (a) Partition_2(a,g,d) Python def Partition_2(a,g,d): assert g<d echange(a,g,random.randint(g,d-1)) Sto=a[g] k=g for i in range (g+1,d): if a[ i ]<Sto: k=k+1 echange(a,i ,k) if g!=k: echange(a,g,k) return k (b) Tri_rapide_rec2(a,g,d) Python def Tri_rapide_rec2(a,g,d) while g<d-1: m=Partition_2(a,g,d) if m-g <d-m-1: Tri_rapide_rec2(a,g,m) g=m+1 else : Tri_rapide_rec2(a,m+1,d) d=m I.P.T. Tris 3 Algorithmes de tris PC* Python (c) Tri_rapide2(a) def Tri_rapide2(a): Tri_rapide_rec2(a,0,len(a)) Attention : avant l'exécution de ces fonctions il est nécessaire de charger le module random sous le nom random. On écrira : import random as random III : Tri par fusion On utilise encore un principe de division en deux paquets. Mais on trie chacun des deux paquets puis on fusionne les deux paquets triés. 1. Écrire un algorithme qui fusionne deux paquets supposés déjà triés. 2. Donner une écriture récursive du tri fusion.Évaluer la complexité de l'algorithme. Étudier l'exemple d'écriture : Python def Fusionner(a,b): i=j=0 n1,n2,n=len(a),len(b),len(a)+len(b) L=[] while i< n1 and j< n2: if a[ i ]<=b[j]: L.append(a[i]) i=i+1 else : L.append(b[j]) j=j+1 if i==n1: while j <n2: L.append(b[j]) j=j+1 else : while i<n1: L.append(a[i]) i=i+1 return (L) I.P.T. Tris 4 Algorithmes de tris PC* 3. Et pour le programme principal : def Tri_fusion(a): N=len(a) if N <=1: Python return a else : m=N//2 a1=Tri_fusion(a[0:m]) a2=Tri_fusion(a[m:N]) return Fusionner(a1,a2) IV : Médiane d'une série statistique Soit L = [x0 , . . . , xn−1 ] une liste de n réels. Notons L′ = [x′0 , . . . , x′n−1 ] la liste obtenue en triant les éléments de L par ordre croissant. Distinguons deux cas : 1. Si n est un entier impair, n = 2p + 1, la médiane de L est l'élément α = x′p . Il y a p éléments de L plus petits que α et p éléments de L plus grands que α. 2. Si n est un entier pair, on conviendra qu'il y a deux valeurs médianes, x′p−1 et x′p . Les procédures de tri déjà exposées permettent donc d'obtenir un calcul de la médiane. Ecrire une procédure de calcul de médiane d'une série statistique de n éléments avec un coût en O(n ln(n)). Il est possible d'obtenir la médiane avec un algorithme en O(n). Voici par exemple les étapes d'un tel algorithme : 1. Écrire une fonction d'argument un tableau de 5 éléments et qui en retourne la médiane (idem avec au plus 5 éléments). 2. Dénir une fonction qui partant d'un tableau de n éléments le divise en sous-tableaux de 5 éléments (au plus) et retourne la liste des médianes de chacun des sous-tableaux obtenus. 3. Déterminer récursivement la médiane des médianes :A. 4. Trier la liste à partir du pivot A obtenu : à savoir des éléments inférieurs à A,A, des éléments supérieurs à A. I.P.T. Tris 5 PC* Algorithmes de tris 5. Recommencer si nécessaire. Eectuer à la main cette procédure pour le tableau suivant : L = [12, 5, 45, 7, 84, 13, 24, 56, 8, 9, 14, 17, 42, 30, 24, 50, 9, 13, 22, 44, 17, 8, 45] On remarquera que dans le cas général c'est la même procédure qui conduit à rechercher la médiane ou la valeur du k-ème élément d'une liste. On pourra obtenir par exemple : def valeur_indice(a,k): if k>= len(a): raise ValueError('position demandée extérieure à la liste ' ) elif len(a)<=5: return valeur_indice_petit(a,k) else : Sto=[] q=len(a)//5 r=len(a)-5*q for i in range(0,q): Sto.append(valeur_indice_petit(a[5*i:5*i+5],2)) if r>0: Sto.append(valeur_indice_petit(a[5*q:5*q+r],r//2)) Python Pseudo=valeur_indice(Sto,len(Sto)//2) K,inf ,sup=Premier_tri(a,Pseudo) if K==k: return Pseudo elif K>k: return valeur_indice(inf,k) else : return valeur_indice(sup,k-K) I.P.T. Tris 6