NOM : Classe : Devoir Surveillé informatique MP, PC, PSI L’utilisation des calculatrices n’est pas autorisée pour cette épreuve. Le langage de programmation choisi est Python. L’espace laissé pour les réponses est suffisant (sauf si vous utilisez ces feuilles comme brouillon, ce qui est fortement déconseillé). Comparaison de deux méthodes de tri L’emploi de la méthode .sort() et de la fonction max prédéfinies dans Python n’est pas autorisé. Partie I/ Le tri par insertion On considère un tableau T de valeurs numériques représenté par une liste de valeurs numériques non triée. On définit une méthode intuitive pour trier la liste par ordre croissant : le tri insertion dont le pseudo-code est donné ci-dessous. procédure tri_insertion(tableau T) pour i de la deuxième valeur à la dernière valeur du tableau x ← T[i] j←i tant que j > 0 et T[j - 1] > x T[j] ← T[j – 1] j←j–1 fin tant que T[j] ← x fin pour fin procédure 1- Proposer un script écrit en langage Python qui effectue le tri par insertion d’un tableau de valeurs. 1/8 2- On dit que ce tri est « en place ». Que signifie ce terme et en quoi est-ce un aspect positif de cette méthode de tri ? 3- Donner, en justifiant brièvement, la complexité en temps de cet algorithme dans le meilleur et dans le pire des cas en fonction de n = len(T). 4- Quelle est la meilleure performance que l’on puisse attendre en termes de complexité temporelle pour un tri par comparaison ? À ce titre, le tri par insertion est-il performant ? Partie II/ Le tri par tas Le but de cette partie est l’écriture d’un algorithme de tri de tableaux basé sur la notion de tas. Les questions se suivent logiquement, mais beaucoup sont indépendantes. Les fonctions définies dans les différentes questions peuvent être utilisées dans les questions suivantes même si celles-ci n’ont pas été écrites complètement. A- Des fonctions élémentaires pour se déplacer dans un tableau indice T(indice) 0 5 1 2 2 6 3 0 4 1 5 9 6 1 7 5 (a) Vision tabulaire (b) Vision arborescente (arbre binaire) Figure 1 – Deux vues différentes d’un même tableau La figure 1 montre qu’un même tableau peut être dessiné avec des cases contigües, ou bien avec des cases dispersées dans une arborescence. Avec la vue contigüe, on utilise généralement une variable i qui parcourt les indices du tableau. 2/8 Précisons maintenant un peu plus les termes désignant les différents composants d’un arbre binaire. Tout d’abord, chaque élément d’un arbre se nomme un nœud. Les nœuds sont reliés les uns aux autres par des relations d’ordre ou de hiérarchie. Ainsi on dira qu’un nœud possède un père, c’est-à-dire un nœud qui lui est supérieur dans cette hiérarchie. Il possède éventuellement un ou deux fils. Il existe un nœud qui n’a pas de père, on l’appelle alors la racine de l’arbre. Un nœud qui n’a pas de fils est appelé feuille ou nœud externe. Tout autre nœud de l’arbre sera appelé nœud interne. Voici donc un schéma qui résume les différents composants d’un arbre : (a) Structure d’un arbre binaire (b) Arbre binaire complet Figure 2 – Généralités sur les arbres Le nombre de niveaux total, autrement dit la distance entre la feuille la plus éloignée et la racine, est appelé hauteur de l’arbre. Le niveau d’un nœud est appelé profondeur. Sur la figure 2b, l’arbre a une hauteur égale à 2. On appellera arbre binaire complet tout arbre qui est localement complet, dont chaque nœud interne possède deux fils et dont toutes les feuilles ont la même profondeur. Dans ce type d’arbre, on peut exprimer le nombre de nœuds n de l’arbre en fonction de la hauteur h : n = 2h+1 -1. Avec la vue arborescente, on peut évidemment utiliser une variable i qui parcourt les indices du tableau, mais on utilise également trois fonctions qui permettent de suivre les liens bidirectionnels (réels ou virtuels) de l’arborescence : - gauche(indice) représente les liens pointillés du haut vers le bas de l’arborescence. Par exemple, dans la figure 1b, gauche(1)=3, gauche(4)=9 et gauche(2)=5. - droite(indice) représente les liens en trait plein du haut vers le bas de l’arborescence. Par exemple, dans la figure 1b, droite(1)=4, droite(3)=8 et droite(0)=2. - pere(indice) représente les liens du bas vers le haut de l’arborescence. Par exemple, dans la figure 1b, pere(4)=1, pere(7)=3 et pere(2)=0. Par contre pere(0) n’est pas défini, et sa valeur (null,-1,0,…) importe peu car jamais utilisée dans cet exercice. Voici un programme Python possible pour la fonction gauche(indice), de complexité temporelle en O(1) : def gauche (indice) : '' '' '' Retourne le lien à gauche vers le bas de l'arborescence '' '' '' return 2*indice+1 5- Écrire un programme Python droite(indice) qui retourne un entier d tel qu’il existe un lien en trait plein du haut vers le bas reliant les indices i à d. La complexité en temps doit être en O(1). 3/8 6- Écrire un programme Python pere(indice) qui retourne un entier p tel qu’il existe un lien du bas vers le haut reliant les indices i à p. La complexité en temps doit être en O(1). B- Construction d’un tas à partir d’un tableau. Un tas est un tableau d’entiers tel que pour tous les indices i strictement positifs, la valeur de T[i] est inférieure ou égale à celle de T[pere(i)]. Le but de cette partie de l’exercice est d’effectuer la transformation représentée par la figure 3. (a) Vue arborescente du tableau initial (b) Tas obtenu par construction Figure 3 – Construction d’un tas 7- Écrire un programme Python estunTas(T) qui retourne True si le tableau T est un tas, False sinon. La complexité en temps doit être en O(n) avec n = len(T). 4/8 8- Pour un tableau T d’éléments T[i], on définit une valeur limite telle que 0 i limite et limite longueur(T). Définir une fonction maximum(T,i,limite) qui retourne l’indice (inférieur à limite) de la plus grande des trois valeurs T[i], T[gauche(i)] et T[droite(i)]. Si on note iMax la valeur retournée par la fonction maximum(T,i,limite), iMax a donc les propriétés suivantes : iMax limite iMax i, gauche(i ), droite(i ) T [iMax] T [i ] gauche(i ) limite T [iMax] T [ gauche(i )] droite(i ) limite T [iMax] T [droite(i )] En cas de valeurs égales, le plus petit indice est retourné. Par exemple sur la figure 3a, maximum(T,0,8) = 2, maximum(T,2,8) = 5, maximum(T,3,8) = 7 et maximum(T,3,7) = 3. La complexité en temps doit être en O(1). 9- Soit l’algorithme récursif écrit en langage Python suivant : def entasserRecursif (T,i,limite) : iMax = maximum(T,i,limite) if iMax!= i : echange (T,i,iMax ) entasserRecursif (T,iMax,limite) avec : def echange (T, i, j) : aux = T[i] T[i] = T[j] T[j] = aux Justifier pourquoi cet algorithme est récursif. 10- Compléter l’arborescence avec les valeurs du tableau après l’appel entasserRecursif(T,0,8). (a) Avant entasser (b) Après entasser Figure 4 – Entasser(T,0,8) 5/8 11- Proposer un algorithme non récursif écrit en langage Python que l’on nommera entasser(T,i,limite) équivalent à l’algorithme récursif entasserRecursif(T,i,limite). 12- Donner les complexités en temps dans le meilleur et dans le pire des cas de l’algorithme entasser(T,i,limite) en fonction de n = len(T). 13- L’algorithme entasser(T,i,limite) échange des valeurs du tableau de haut en bas, en suivant une branche de l’arborescence. Cela a pour effet de faire descendre des petites valeurs, et de faire monter les grandes valeurs. Il est donc possible de construire un tas, en itérant cet algorithme sur les indices décroissants du tableau. En utilisant entasserRecursif(T,i,limite) ou entasser(T,i,limite), proposer un algorithme construireTas(T), écrit en langage Python, qui transforme un tableau en un tas. 14- Donner la complexité en temps dans le meilleur et dans le pire des cas de l’algorithme construireTas(T) en fonction de n = len(T). 6/8 15- Tri d’un tas. Le but de cette partie de l’exercice est d’effectuer la transformation représentée par la figure 5. (a) Tas initial (b) Vue arborescente du tableau trié Figure 5 – Tri d’un tas a) Dans un tas, la valeur maximale est à la racine de l’arborescence, donc en T[0]. Dans le tableau trié, cette valeur doit être en T[len(T)-1]. Il suffit donc d’échanger ces deux valeurs pour progresser vers la solution. Une fois cet échange fait, si l’on exclut la dernière valeur du tableau, le tas est peu changé. En fait entasser(T,0,len(T)-1) va créer un nouveau tas pour les valeurs du tableau dont les indices sont inférieurs à limite = len(T)-1. Il suffit donc d’itérer ces deux étapes (échange, entasser) pour trier un tas. Écrire un algorithme trierTas(T) en langage Python qui transforme un tas en un tableau trié en ordre croissant. b) Donner la complexité en temps dans le meilleur et dans le pire des cas de l’algorithme trierTas(T) en fonction de n = len(T). c) Écrire un algorithme triParTas(T) en langage Python qui trie un tableau d’entiers T en construisant d’abord un tas, puis en le triant. 7/8 d) Donner la complexité en temps dans le meilleur et dans le pire des cas l’algorithme triParTas(T) en fonction de n = len(T). Commenter. FIN DU SUJET 8/8