1 1. File de priorité - implantation par Tas Une le de priorité permet de représenter un ensemble d'éléments avec les méthodes suivantes: accès à l'élément de priorité maximum (accesMax); l'insertion d'un nouvel élément (inserer). Un élément d'une le de priorité a deux accesseurs pour lire sa priorité accesValeur. accesPriorite et sa valeur De plus un mutateur permet de changer la valeur de priorité d'un élément (changePriority). Spécier la classe FilePriorite. Dans toute la suite on implante la le de priorité par un arbre binaire partiellement ordonné: pour tout n÷ud, sa valeur est supérieure à celle de ses ls. 2. On implémente ici avec un arbre binaire naïf: un noeud de l'arbre stocke une valeur et les deux noeuds filsgauche et filsdroit, 1. Dénir les classes Noeud et dont la valeur peut être le noeud-vide. ArbreBinaire. 2. Dénir la classe le de pirorité construite à partir d'ArbreBinaire. Donner les prototypes des méthodes: inserer, accesMax, muterValPriorite mais pas de les implementer. 3. Donner l'implantation de accesMaxPriorite. 4. Ecrire un itérateur qui parcourt tout l'arbre en eectuant un traitement sur chaque valeur qui ne change pas l'arbre. 5. Que pensez-vous du coût (en moyenne) si vous deviez implanter insertion, suppression? 3. 1 et partiellement On considère une représentation optimisée, appelée "tas" (heap) d'un arbre parfait ordonné. On se limite ici à un arbre binaire. Supposons que l'arbre contient 1 ≤ i ≤ n, T [i] n éléments: il est stocké dans un tableau • si • T[1] stocke la racine; • Si ils existent, les ls gauche et droit de Pour • si • si 2 ≤ i ≤ n, i > n/2, T [i] T indicé de T [1] à T [n]: stocke un élément de l'arbre; le père de T [i] est T [i] T [i/2]. sont stockés respectivement aux indices 2i et 2i + 1. est une feuille; n 2 est un entier, alors T [n/2] représente l'unique noeud unaire de l'arbre. Sinon tout noeud est soit un noeud interne binaire, soit une feuille. Ajout dans un arbre parfait partiellement ordonné : • On ajoute l'élément sur la première feuille libre, de manière à ce que l'arbre reste parfait. • On rétablit la propriété partiellement ordonné en échangeant l'élément avec son père si la valeur de ce dernier est inférieure, et en propageant ce type d'échange aussi loin qu'il le faut vers la racine (voir Figure 1). Suppression du maximum : • On remplace l'élément racine, qui contient le maximum, par le dernier élément (dernière feuille) de manière à ce que l'arbre reste parfait. • On rétablit la propriété partiellement ordonné en eectuant si nécessaire des échanges de pères avec le plus grand de leurs ls, en partant de la racine de l'arbre et en descendant (voir gure 2). 1 18 8 11 7 6 9 5 4 8 11 3 15 2 18 18 3 6 2 5 4 7 15 9 8 15 3 6 2 5 4 7 11 9 Figure 1: Ajout de 15 dans l'arbre de la gure précédente. 6 8 15 7 11 9 5 4 15 15 8 6 3 2 4 7 11 9 5 8 11 3 2 4 7 6 9 3 2 5 Figure 2: Suppression du maximum dans l'arbre précédent. 1. Quels sont les coûts de l'insertion et de la suppression (en nombre de comparaisons ou d'échanges): en pire cas ? et en moyenne ? 2. Implanter une le de priorité avec un tas, en utilisant TableauInni pour stocker le tableau. On pourra implanter deux méthodes privées: • taslagmite( int i ): this est un tas sauf en T [i] dont la valeur est supérieure à celle de son père T [i/2]. • taslactite( int i ): this est un tas sauf en à T [i] dont un des ls au moins a une valeur supérieure T [i]. 3. Réorganisation d'un tableau en tas: écrire un constructeur prenant en entrée un tableau et construisant un tas: (a) Version naive: Indication: temps en insérant les éléments un à un dans un nouveau tas: Remarquer que le premier élément est inséré en O(1) quel est le coût ? mais les n/2 feuilles en O(log n). (b) Version ecace: donner un algorithme de cout O(n). Indications: on pourra chercher à inverser le fonctionnement de la version naive, en inserant les n/2 feuilles en O(1) et l'unique racine en O(1). Etant donné deux tas A et B contenant 2i − 1 élements et une nouvelle valeur, remmarquer que l'on peut construire un tas contenant i+1 les 2 −1 éléments (A, B et v ) en eectuant O(i) comparaisons; en dédui expliquer comment naive: en insérant les éléments un à un dans un nouveau tas; (c) le tri par tas fonctionne comme suit: étant donné un tableau, on le réorganise en tas; puis on extrait le maximum pour l'écrire sur la sortie. Quel est le coût de cet algorithme (en nombre de comparaisons)? Quel est son avantage et son inconvénient par rapport au quicksort ? 1 Un arbre est parfait ssi: (i) les feuilles sont sur deux niveaux seulement; (ii) l'avant dernier niveau est complet; (iii) les feuilles du dernier niveau sont groupées le plus à gauche possible. 2 2 Tableau trié avec insertion et recherche en coût logarithmique Une classe comparable contient des objets dont on peut comparer les valeurs (ordre total). La méthode int compareTo(E b) retourne un entier négatif (resp. resp. supérieur) à b. nul, resp. positif ) si this est inférieur (resp. égal, Un dictionnaire est un container d'objets comparables qui supporte les méthodes: • void insertion(E val): insère val dans le dictionnaire; • E recherche(E val) qui lève une exception si l'objet n'est pas présent dans le dictionnaire, et sinon retourne une référence sur un objet de valeur val dans le dictionnaire. NB On ne considère pas la suppression dans un premier temps. La recherche dichotomique dans un tableau trié de n éléments prend un temps logarithmique, mais l'insertion un temps linéaire. Pour diminuer le coût de l'insertion, on représente le tableau de la façon suivante. Pk−1 k = dlog2 (n + 1)e et n = i=0 ni 2i la décompostion de n en base 2 (i.e. ni vaut 0 ou 1). i Les n éléments triés sont stockés dans k tableaux A0 , . . . , Ak−1 . Le tableau Ai est de longueur 2 si ni = 1; il est vide si ni = 0. Chaque tableau Ai est trié; mais il n'y a pas d'ordre entre deux tableaux. Soit Exemple : pour 13 caractères dans l'ordre alphabétique, on a 4 tableaux : A0 = [e] A1 = [, ] A2 = [a, c, i, l] A3 = [b, d, f, g, h, j, k] Pour l'implantation, les T de taille 2k − 1, k sous-tabbelaux donc inférieure à 2n. Ai sont stockés consécutivement dans un tableau dynamique On demande d'écrire les algorithmes en Java. 1. Donner un algorithme de recherche; analyser le coût en pire cas. 2. Analyser le coût en moyenne lors de la recherche d'un élément qui est dans le tableau. 3. Donner un algorithme d'insertion. Analyser le coût en pire cas et le coût amorti. 4. Etudier la destruction d'un élément. 5. On propose l'algorithme de tri suivant, inspiré d'un tri par insertion; il utilise cette structure de données T qui stocke de manière contigüe T = [A0 , A1 , . . .], chaque sous-tableau Ai de taille 2i étant toujours soit vide soit trié : T = vide ; // initialisation de T // Insertion des éléments dans la structure for (int i=0; i < n; i++) T.insert ( x(i) ) ; // Fin de l'insertion; on finalise en fusionnant les tableaux 2 à 2 pour terminer. int k = dlog2 (n + 1)e ; for (int j=1; j < k ; j++ ) merge( T[0 . . . 2j−1 − 1 ], T[ 2j . . . 2j − 1] ) ; T.resize( n) ; // pour réajuster la taille de T return T ; merge( T[0 . . . 2j−1 − 1 ], T[ 2j . . . 2j − 1] ) est la fusion classique de deux j−1 j (non nécessairement pleins) avec m1 ≤ 2 et m2 ≤ 2 . En résultat, les m1 + m2 sont stockés triés dans le sous tableau T [0 . . . m1 + m2 − 1]. L'opération tableaux éléments (a) Quel est le coût de la phase d'insertion ? (b) Quel est le coût de la phase de fusion ? O(n log n). La recheche O(log n), c'est le décalage des éléements dans un tableau 2 de taille n qui entraînent un coût O(n ) pour le tri par insertion naïf. La représentation cidessus permet de conserver un coût total O(n log n) grâce à un tableau de taille raisonnable, inférieure à 2n. Cependant, le tableau T ne restant pas trié tout au long de l'algorithme, il ne Remarque : Ce tri, assez proche d'un tri par insertion, est donc de coût dichotmique dans un tableau trié étant en peut pas être considéré comme un vrai tri par insertion. D'autres représentations d'un tableau, qui utilisent comme ci-dessus des emplacememnts vides pour amortir le coût des décalages nécessaires, permettent d'implanter un tri par insertion dans un tableau de taille 3 O(n) en coût amorti O(n log n). Correction: 1. Recherche: on recherche successivement dans les 1, . . . , 0, log2 n tableaux Ai pour i = k− en commençant par le tableau de plus grande taille; dans chaque tableau Ai avec ni 6= 0, on eectue une recherche dichitomique de coût log2 kAi k = i. Le coût en pire cas est majoré par Plog n i=0 i = O(log2 n). 2. Analyse en moyenne de la recherche d'un élément dans le tableau. On se place dans le pire k cas où n = 2 − 1, i.e. on a ni = 1 pour tout i. On suppose que l'élément recherché est choisi uniformément parmi les n éléments; la probai Ai est alors 2n ≤ 2i−k . D'où le coût cR (n = 2k − 1) en moyenne: bilité que l'élément soit dans le tableau 1 + .cR (2k−1 − 1) 2 1 1 1 ≤ log2 2k−1 + log2 2k−2 + . . . + i log2 2k−i−1 + . . . + k−1 2 2 2 ≤ 2 log2 n. cR (n) ≤ CoûtRechDicho (Ak−1 ) (1) (2) (3) Θ(log2 n) si l'on fait la recherche dans l'ordre : d'abord dans etc jusqu'à trouver l'élément. Le coût en moyenne est donc Ak−1 , puis dans Ak−2 ni = 1 pour tout i = 0 . . . j − 1. L'insertion consiste Aj ; puis à réorganniser la structure de donnée en supprimant les tableaux A0 , ..., Aj−1 pour les déplacer dans Aj . Pour cela, on procède comme suit: on fusionne Aj successivement avec A0 puis A1 , etc jusqu'à Aj−1 . Le coût en comparaison de la fusion de deux tableaux de taille 2i est majoré par 2i+1 ; Pj−1 i+1 le coût de ces fusions successives est donc manjoré par ≤ 2j − 1. i=0 2 On incréménte alors n de 1: ainsi on a ni = 0 pour 0 ≤ i < j et nj = 1; on détruit donc virtuellement les tableaux Ai devenus inutiles. Le pire cas est lorsque j = k − 1 : dans ce cas, la réorganisation est de coût O(n). Mais alors, on a ensuite n0 = 0 et la prochaine insertion se fera avec 0 comparaisons, en coût O(1). Plus précisément, le coût pour n insertions consécutives est le suivant: j tel que nj = 0 et alors à insérer le nouvel élément dans 3. Pour l'insertion: soit • n2 fois, on a j = 0 et l'insertion se fait avec 20 − 1 = 0 comparaisons; • 2n2 fois, on a j = 1 et l'insertion se fait avec 21 − 1 = 1 comparaisons; • 2n3 fois, on a j = 2 et l'insertion se fait avec 22 − 1 = 3 comparaisons; • 2ni fois, on a j = i et l'insertion se fait avec 2i − 1 comparaisons; • ... • 1 fois seulement on a j = log2 n et l'insertion se fait avec n − 1 comparaisons; n On en déduit le cout amorti pour insertions (consécutives): Plog2 n c(n) ≤ j=0 n j 2j+1 .2 n = O(log2 n). Le coût amorti de l'insertion est alors logarithmique, comme le coût moyen de la recherche. 4. Pour analyser la destruction, supposons que l'élément détruit est dans le tableau plus j≤d l'indice tel que nj = 1 et ni = 0 pour Ad . Soit de 0≤i<j Pour détruire l'élément, on procède comme suit. • Étape 1 : Réorganisation de Aj : on enlève le dernier élément y dans Aj et on déplace les 2j − 1 éléments restants de Aj dans les tableaux A0 , . . . , Aj − 1. Le coût est O(2j ). • Étape 2 : Insertion dans Ad : on remplace l'élément x par y et on rétablit l'ordre dans le en permutant l'élément y soit à gauche, soit à droite jusqu'à ce qu'il soit en d place. Le coût est O(2 ). tableau • Ad Finalement, on décrémente n de 1 : ainsi Le coût de la destruction est donc linéaire ni = 1 O(n) pour et nj = 0. en pire cas. Analyse amortie : le coût de la réorganisation du tableau 4 0≤i<j Aj peut être amorti : il est nul lorsque j = 0, Ad trié est de coût linéaire et l'insertion est de coût moyen n/4. Donc, le ie une fois sur deux. Cependant, l'insertion dans le tableau d=k O(n). et n'est pas amortie : une fois sur deux, coût amorti de la destruction est aussi En conclusion, cette structure est bien adaptée à la recherche et l'insertion (coût amorti O(log n)); mais elle n'est pas adaptée à la destruction. Les structures de dictionnaire (chapitre ??) permettent de garantir un temps d'insertion, de recherche et de suppression de coût Θ(log n). i étant de coût amorti O(log i), le coût de la phase d'insertion = [t log t − t]n1 ≡ n log n. Donc O(n log n). j−1 (b) Quel est le coût de la phase de fusion ? Le coût de la fusion merge( T[0 . . . 2 −1 j j j j ], T[ 2 . . . 2 − 1] ) est 2 comparaisons, donc Θ(2 ). La phase de fusion est donc de Plog2 n j coût total j=1 2 ≡ 2n, donc Θ(n). Le coût de cette variante du tri par insertion est donc Θ(n log n). 5. (a) L'insertion de l'élément Rn Pn est i=1 log i ≡ 1 log t 5