Sujet 4 :Arbres Binaires de Recherche INSA 2e année MIC Algorithmique & Programmation (semestre 4) 3 avril 2015 Les objectifs de ce TP sont les suivants : comprendre les opérations du type abstrait de données Arbre Binaire de Recherche poursuivre la compréhension des notions de généricité comprendre la notion de clé associée à un élément concevoir et développer un algorithme de recherche acquérir une méthodologie pour eectuer des tests de performance d'algorithmes et des comparaisons entre algorithmes Remarques créer un nouveau répertoire pour ce sujet de TP, les noms des chiers ne doivent pas comporter de majuscules, Ce sujet dure au maximum 2 séances de TP. Contexte On souhaite gérer un ensemble d'informations à l'aide d'un Arbre Binaire de Recherche (ABR). On va créer un paquetage générique d'ABR que l'on testera avec un type d'information spécique. En pratique, l'ABR sera utilisé pour gérer un annuaire similaire à celui considéré au TP précédent. Pour représenter un annuaire, on disposera donc d'une liste ordonnée et d'un ABR. Dans les deux cas, vous allez mettre un point un algorithme de recherche et eectuer des comparaisons de ces deux algorithmes en termes de performances exprimées sur des mesures à dénir lors du TP. 1 1 Annuaire représenté par un ABR On suppose que les éléments de l'ABR seront diérenciés par une totalement les éléments entre eux. clé qui permet d'ordonner 1.1 Création du paquetage générique d'ABR Ce paquetage, arbre_bin_recherche_cle_g, vous est partiellement fourni. Il exporte : un type Un_ABR limité privé trois exceptions : Element_Deja_Present, Element_Non_Present et Arbre_Vide. les primitives : Liberer(A) : libérer la mémoire utilisée pour A. Est_Vide(A) : renvoie vrai si A est vide. Taille(A) : retourne le nombre d'éléments de A. Racine(A) : retourne l'objet de type Element présent à la racine de l'arbre. Une exception est levée si l'arbre est vide. Appartient(C, A) : retourne vrai si l'élément ce cle C est présent dans A. Arbre_to_string(A) : retourne la chaine de caractères composée des éléments de A dans l'ordre en eectuant un parcours inxe d'un arbre (dit parcours GRD). Inserer(E, A) : insérer l'élément E dans l'ABR A en respectant la relation d'ordre. Une exception est levée si l'élément est déjà présent ; A doit rester ordonnée après l'insertion de E. Supprimer(E, A) : supprime l'élément E de l'ABR A. Une exception est levée si l'élément n'est pas présent dans A. Remarque : on pourrait aussi dénir une opération de suppression à partir de la valeur d'une clé. Travail à réaliser Identier tous les paramètres de généricité du paquetage (types et sous-programmes). N'oubliez pas qu'une clé doit être associée à chaque élément et que la relation d'ordre se base sur les valeurs des clés. Faire valider par un enseignant et compléter la spécication du paquetage générique. Développer le corps du paquetage progressivement, en y intégrant un par un les sousprogrammes manquant. Exécuter les tests de chaque sous-programme progressivement (voir le point suivant). Récupérer le programme tester_abr_entier.adb. Ce programme teste les sous-programmes dans le cas d'un ABR contenant des entiers. Ecrire l'instanciation correspondante et lancer les tests au fur et à mesure de vos développements (commenter les tests associés à des sous-programmes non terminés). 1.2 Instanciation : ABR de contacts Récupérer les paquetages contact_cle et pointeurs_de_strings.ads ainsi que les chiers afficher_test.ads et afficher_test.adb. Attention, le paquetage contact_cle a été modié par rapport au paquetage contact du TP précédent et explicite maintenant une clé. Travail à réaliser Créer un programme de test appelé tester_abr_contacts, permettant : d'instancier le paquetage générique d'arbre pour le type Un_Contact ; de lire un annuaire à partir d'un chier texte et de créer l'ABR correspondant (reprendre la procédure de lecture écrite lors du TP précédent ou consulter l'annexe 6.1) ; d'acher l'ABR créé. 2 Donner le graphe de dépendance des diérentes unités utilisées (faire valider par un enseignant). 2 Annuaire représenté par une liste ordonnée Récupérer le paquetage générique liste_ordonnee_cle_g. Il sera utilisé dans la section suivante. 3 Algorithmes de Recherche 3.1 Principes généraux Lors du TP précédent, vous avez développé un algorithme de ltrage sur une liste ordonnée. On parle de ltrage d'un ensemble d'éléments lorsqu'on souhaite récupérer les informations associées à plusieurs éléments vériant tous une même condition de ltrage. Par exemple, on veut pouvoir connaitre tous les contacts ayant une spécialité donnée ou exerçant dans une même ville. Dans le cas général, plusieurs objets de type Element peuvent donc respecter la condition de ltrage. Dans ce TP, on va s'intéresser seulement au développement d'un algorithme de recherche. La recherche d'un élément dans un ensemble permet de récupérer toutes les informations associées à l'élément donné. Par exemple, on cherche toutes les informations (nom, prénom, ville, spécialité, téléphone) sur une personne donnée à partir de son nom (en supposant que chaque personne n'est présente qu'en un seul exemplaire dans l'ensemble et si ce n'est pas le cas en retournant la première occurrence trouvée). Ainsi, le résultat d'une recherche est un unique élément. Un algorithme de recherche (ou de ltrage) peut s'appliquer que les objets soient stockés dans une liste ordonnée ou dans un ABR (ou d'autres structures de données). Pour un même ensemble de contacts, stockés dans une liste ordonnée ou dans un ABR (ou autre), la recherche (ou le ltrage) doit fournir le même résultat. L'ecacité d'un algorithme de recherche est liée à l'utilisation d'une clé pour chaque élément en garantissant l'unicité. En eet, dans ce cas, les recherches peuvent être stoppées dès que l'on est certain que la liste ordonnée ou l'ABR ne contient plus d'éléments pouvant avoir la valeur de clé recherchée. Vous allez développer deux algorithmes de recherche : le premier s'appliquera sur les listes ordonnées et le second sur les ABR. Il vous sera ensuite demander d'évaluer les performances de ces algorithmes an de les comparer. Travail à réaliser Répondre aux questions ci-dessous et faire valider les réponses par un enseignant (ou vérier dans le cours). quelle est la complexité au pire d'un algorithme de recherche d'un élément à partir de sa clé dans un ABR (ordonné par les clés) équilibré ? quelle est la complexité au pire lorsque l'ABR n'est pas équilibré ? quelle est la complexité au pire d'un algorithme de recherche d'un élément à partir de sa clé dans une liste ordonnée par les clés ? quelle est la complexité au pire d'un algorithme de recherche d'un élément à partir d'un champ autre que sa clé dans un ABR (ordonné par les clés) équilibré ? quelle est la complexité au pire d'un algorithme de recherche d'un élément à partir d'un champ autre que sa clé dans une liste ordonnée par les clés ? 3 3.2 Recherche sur un ABR On souhaite développer un annuaire inversé permettant d'obtenir toutes les informations sur un contact à partir de son numéro de téléphone. On note que pour les annuaires considérés, la clé ne peut être que le numéro de téléphone (les noms, prénoms, villes ou spécialités ne sont pas des clés pour les contacts). L'utilisation d'un ABR ordonné par numéro de téléphone pour réaliser un annuaire inversé est donc pertinente. Travail à réaliser Modier le paquetage générique arbre_bin_recherche_cle_g pour y ajouter une fonction de recherche. Créer un programme de test appelé gerer_annuaire_inverse permettant de lire un chier d'annuaire et de créer un ABR ordonné par numéro de téléphone (clé). Vérier, dans ce programme de test, le fonctionnement de la procédure de recherche en générant et en achant le résultat de plusieurs recherches (recherche du premier élément, d'une feuille de l'arbre, ou d'un élément d'un noeud interne). 3.3 Recherche sur une liste ordonnée Le développement d'un algorithme de recherche dans une liste ordonnée est similaire. Travail à réaliser Modier le paquetage générique liste_ordonnee_cle_g pour y ajouter une fonction de recherche. Vérier, dans le programme de test, gerer_annuaire_inverse, le fonctionnement de la procédure de recherche sur des listes ordonnées par numéro de téléphone. Pour cela, à la lecture du chier annuaire, il faut mémoriser les contacts à la fois dans un ABR et dans une liste ordonnée. 4 Tests de performance Nous nous étions limités, jusqu'à ce TP, à vous demander de vérier que les algorithmes développés étaient corrects à partir d'un ensemble de tests que l'on devait dénir. Cependant, les tests de validité (ou de correction) ne susent pas dans de très nombreuses situations. Il est également important de montrer que vos algorithmes sont ecaces en théorie (en donnant leur complexité, cela sera vu en détail en 3MIC) et en pratique. Pour les TPs de 2MIC, nous nous contenterons de mesurer leur ecacité en pratique. On parle alors de tests de performance. 4.1 Méthodologie pour les tests de performance Avant de tester l'ecacité d'un algorithme, il convient tout d'abord de dénir les paramètres que l'on souhaite mesurer et qui paraissent représentatifs de son ecacité. Il est ensuite nécessaire de sélectionner (ou de créer) des jeux de données représentatifs. Enn, il faut recueillir les valeurs des paramètres à mesurer pour chacun des jeux de données et analyser les valeurs obtenues. Pour cela, on peut développer un programme appelant l'algorithme dont on veut évaluer les performances sur chacun des jeux de données ou le faire "à la main". Il est impératif de bien comprendre la distinction entre tests de validité (de correction) et tests de performance (ou d'ecacité). Si ces notions vous paraissent oues, demandez des éclaircissements à vos encadrants. 4 Travail à réaliser Les paramètre de mesure que nous allons utiliser sont le nombre de comparaison entre 2 éléments et le temps de calcul an d'évaluer les performances d'un algorithme de recherche utilisant une liste ordonnée ou un ABR ; Rechercher comment mesurer les temps de calcul en ADA (si vous ne trouvez pas, limitez vous au nombre de comparaison entre 2 élements) ; Utiliser les chiers d'annuaire proposés ; Eectuer les comparaisons et construire des tableaux avec les valeurs des paramètres. 4.2 Comparaison de vos algorithmes Comparer l'ecacité respective de vos algorithmes (recherche dans une liste ordonnée versus recherche dans un ABR) sur des jeux de données identiques et pour un même ensemble de paramètres de mesure. Que pouvez-vous en conclure ? On s'attend à ce que la recherche sur un ABR soit plus ecace à la recherche sur une liste ordonnée. Votre conclusion est-elle cohérente par rapport à ce qui était attendu ? Est-ce vérié pour tous les chiers d'annuaire ? Justier votre réponse et faire valider par un enseignant. 5 Pour aller plus loin Reprendre la démarche de ce TP dans le cas d'un algorithme de ltrage. La principale diculté réside dans la construction (ecace) de l'ensemble des résultats d'un ltrage pour un ABR. 5 6 Annexes 6.1 Lecture de données dans un chier texte Pour lire des données dans un chier texte : ouvrir un chier (open) en mode lecture (in_file). le parcourir ligne par ligne (get_line) jusqu'à la n (end_of_file). fermer le chier (close). Pour gérer les chiers, le langage Ada propose un type prédéni appelé file_type. En supposant que le chier que l'on souhaite lire s'appelle data.txt, le principe général de lecture est fourni dans le listing ci-dessous. 1 2 3 4 5 6 7 8 9 10 11 12 13 F : File_Type ; Lig ne : s t r i n g ( 1 . . 8 0 ) ; Last : N a t u r a l ; C : Un_Contact ; begin −− l e c t u r e Open (F , In_File , " data . t x t " ) ; while not End_Of_File (F) loop Get_Line (F , Ligne , Last ) ; C := C o n v e r t i r ( Lig ne ( 1 . . Last ) ) ; ........ end loop ; C l o s e (F) ; end ; −− l e c t u r e Listing 1 Lecture des lignes d'un chier Remarque : pour la conversion d'une ligne lue en un contact, vous pouvez reprendre le sousprogramme lire_mot réalisé au premier semestre et rappelé ci-dessous. 1 procedure Lire_Mot ( S : S t r i n g ; Deb , Fin : out I n t e g e r ) i s 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 begin −− c h e r c h e l e debut e t l a f i n du p r e m i e r token −− s i aucun token n ' e s t t r o u v e on impose en s o r t i e que Deb > Fin Deb := S ' F i r s t ; Fin := Deb − 1; while Deb <= S ' l a s t and then S ( Deb ) = ' ' loop Deb := Deb+1; −− r e c h e r c h e du debut du 1 e r mot end loop ; i f Deb <= S ' Last then Fin := Deb ; while Fin <= S ' Last and then S ( Fin ) /= ' ' loop Fin := Fin +1; −− r e c h e r c h e de l a f i n du 1 e r mot end loop ; Fin := Fin − 1; end i f ; end Lire_Mot ; Listing 2 Lire un mot dans une chaine La procédure de conversion doit eectuer autant d'appels que nécessaire à la procédure lire_mot et retourner un élément de type Un_Contact ou une exception si une erreur de lecture survient. 6