INGESUP - Paris Algorithmique – 2 Structures de données TAD – P.O.O. Pointeurs – Récursivité Polycopié en cours de réalisation !!! Bertrand LIAUDET SOMMAIRE SOMMAIRE 1 0 - INTRODUCTION 4 1. Structure de données 4 2. Contenu pédagogique 4 1 - LES ENREGISTREMENTS (TYPE STRUCTURE) 5 1. 5 Présentation Usages 5 Structures complexes Renommer un type 5 6 2 - LES TABLEAUX 7 1. Présentation Le tableau Le taille du tableau Le nombre d’éléments du tableau Imbrication de tableaux et d’enregistrements 7 7 7 7 7 2. Méthodes de recherche Recherche dans un tableau non trié Recherche dans un tableau trié : la recherche dichotomique 7 7 7 3. Méthodes de tri Tri à bulles Tri par extraction (élémentaire, heapsort) Tri par insertion Autres méthodes 7 7 7 8 8 ALGORITHMIQUE - Cours 02 - page 1/28 - Bertrand LIAUDET 4. Exercices sur les tableaux Tableaux à une dimension Matrice 9 8 8 3 - LES POINTEURS 10 1. Rappels sur la variable Présentation Formalisme NTVA Déclaration et usage 10 10 10 10 2. Pointeurs et opérateur & et * Le type pointeur Opérateur & Opérateur * Exemple allocation, réservation de mémoire libération de la mémoire 11 11 11 11 11 12 12 3. Tableau de pointeurs 13 4. Exercices sur les pointeurs 13 4 - LES LISTES 14 1. Présentation 14 2. Liste simplement chaînées 14 3. Variantes Liste doublement chaînée Liste circulaire Liste avec compteur de maillons 14 14 14 14 4. Exercices sur les listes 14 5 - RECURSIVITE 16 1. Présentation Principe Exemple : somme des N premiers nombres Intérêt et limite de la récursivité 16 16 16 16 2. Exercices Exercice 1 Exercice 2 Exercice 3 Exercice 4 Exercice 5 17 17 17 17 17 17 6 - LES ARBRES 18 1. Modèle logique des arbres 18 2. ABOH (arbre planaire) Modèle logique Modèle physique des données – Version dynamique Algorithmes 18 18 18 19 3. Arbres équilibrés (AVL) 20 4. Arbres B (B trees) Présentation 20 20 ALGORITHMIQUE - Cours 02 - page 2/28 - Bertrand LIAUDET Principes spécifiques Exemple d’arbre B d’ordre 1 21 21 5. Arbres BB 21 6. Arbres SBB (bicolores). 21 7. Exercices sur les ABOH 22 7 - LES GRAPHES 23 1. 23 Présentation 8 - HACHAGE 24 1. 24 Présentation 9 - LES TAD : VERS LA P.O.O. 25 1. Présentation générale des TAD Présentation Principaux TAD 25 25 25 2. Les chaînes de caractères 25 3. Les piles 26 4. Les files 27 5. Les collections 27 6. Les fichiers 27 10 - USAGES TECHNIQUES DES POINTEURS 28 1. Gestion d’une pile LIFO avec une liste chaînée 28 2. Gestion d’une file FIFO avec une liste chaînée 28 3. Pointeurs et tableaux Tableaux de pointeurs Tableau et chaîne de caractères Tableaux et pointeurs 28 28 28 28 4. Pointeurs et chaînes de caractères 28 5. Pointeurs et mode de passage des paramètres 28 ALGORITHMIQUE - Cours 02 - page 3/28 - Bertrand LIAUDET 0 - INTRODUCTION 1. Structure de données Dans la première partie du cours d’algorithmique, on a travaillé uniquement sur les types simples : entier, réel, caractère et booléen. Le type chaîne de caractères a été abordé sans plus. Dans ce cours, on va présenter différentes sortes de structures de données. Une structure de données est un mode particulier d’organisation des données. On peut aussi créer des types abstraits de données, TAD, qui se caractérise par un type particulier et par un jeu de primitives (procédures et fonctions) permettant d’exploiter le type sans avoir à connaître les détails de son implémentation : ils servent d’introduction à la notion de « CLASSE » de la programmation objet. Pour travailler sur les structures de données, on sera amener à aborder le type « pointeur » et la récursivité. 2. Contenu pédagogique 1. Enregistrements : enregistrement de types de base, enregistrement d’enregistrement. 2. Tableaux : tableau à 1, 2, n dimensions, tableau d’enregistrements, enregistrement de tableaux. 3. Pointeurs. 4. Listes : liste simplement chaînée, liste doublement chaînée, liste circulaire. 5. Récursivité. 6. Arbres : arbres binaires, arbres équilibrés (AVL), arbres B (B tree), arbres bicolores (SBB), arbres planaires. 7. Graphes : graphes non orientés, graphes orientés, graphes valués. 8. Hachage. 9. TAD : chaînes de caractères, piles, files, collection, fichiers ALGORITHMIQUE - Cours 02 - page 4/28 - Bertrand LIAUDET 1 - LES ENREGISTREMENTS (TYPE STRUCTURE) 1. Présentation Un enregistrement (ou type structuré) est un type de donnée permettant d’avoir dans une même variable plusieurs informations pouvant être de type différent. On parle d’enregistrement ou de structure. Usages Exemple traité On veut définir un type de donnée correspondant à un élève. Un élève a un nom, un prénom et une note. Définition du type struct typEleve nom : chaîne prénom : chaîne note : entier fin commentaires On utilise le mot-clé « struct » pour déclarer le nouveau type. typEleve est le nom du type créé. Il pourra s’utiliser comme n’importe quel type simple. « nom », « prénom » et « note » sont trois champs typés. Déclaration de variables e1, e2, e3 : typEleve Utilisation des variables e1.nom « toto » e1.prenom « olivier » e1.note 12 e2.note e1.note // affectation d’un champs e3 e1 // affectation globale de l’enregistrement Structures complexes Exemple traité On veut définir un type de donnée correspondant à un élève. Un élève a un nom, un prénom. Il a un note par matière, et il y a plusieurs matières Définition des types struct typMatiere nomMatiere : chaîne ALGORITHMIQUE - Cours 02 - page 5/28 - Bertrand LIAUDET note : entier fin struct typEleve nom : chaîne prénom : chaîne tabMatières[NMAX] : typMatiere fin Déclaration de variables e1, e2 : typEleve Utilisation des variables e1.nom « toto » e1.prenom « olivier » e1.tabMatiere[1].nomMatiere « algo » e1.tabMatiere[1].note 12 e1.tabMatiere[2].nomMatiere « langage C » e1.tabMatiere[2].note 15 e2.tabMatiere[1].nomMatiere e1.tabMatiere[1].nomMatiere e2.tabMatiere[1].note e1.tabMatiere[1].note e2.tabMatiere[2] e1.tabMatiere[2] e2.tabMatiere[2].note e1.tabMatiere[2] e3 e1 Renommer un type Il peut être pratique de pouvoir renommer un type. On utilisera la syntaxe suivante : Syntaxe type classe as tableau[50] typEleve commentaires On utilise le mot-clé « type » pour renommer un type et le mot-clé « as » pour séparer l’ancien nom du nouveau nom. « classe » est le nouveau nom. tableau[50] typEleve est le type qu’on veut renommer. Usage Le type « classe » s’utilise comme n’importe quel type : cl1, cl2 : classe ALGORITHMIQUE - Cours 02 - page 6/28 - Bertrand LIAUDET 2 - LES TABLEAUX 1. Présentation Le tableau Un tableau est un nouveau type de variable qui permet de regrouper dans une seule variable plusieurs valeurs de même type. Soit « tab » un tableau d’entiers : Pour accéder à un élément du tableau, on écrit par exemple : tab[2]. Le premier élément du tableau c’est tab[1] (tab[0] en langage C et dans les langages dérivés du C) tab[1] c’est une variable comme une autre, ici de type entier. Le taille du tableau Le nombre de valeurs maximum que peut recevoir un tableau est posé au moment de la définition du tableau. Ce nombre, c’est la taille du tableau. C’est un paramètre constitutif du tableau. Le nombre d’éléments du tableau Un tableau n’est pas forcément entièrement rempli. Il faut donc distinguer entre la taille et le nombre d’éléments du tableau à un moment donné. Ce nombre est nécessaire pour pouvoir parcourir tous les éléments du tableau. Imbrication de tableaux et d’enregistrements Tableau d’enregistrement Enregistrement de tableaux 2. Méthodes de recherche Recherche dans un tableau non trié Pour rechercher un élément dans un tableau non trié, il faut parcourir tout le tableau. Si on a N éléments dans le tableau, la recherche se fera en au maximum N tests, N/2 tests en moyenne. Recherche dans un tableau trié : la recherche dichotomique Pour rechercher un élément dans un tableau trié, on fait une recherche dichotomique. Si on a N éléments dans le tableau, la recherche se fera en au maximum log2(N), soit environ 20 pour N = un million. 3. Méthodes de tri Tri à bulles Le principe est de réorganiser (inverser) les couples non classés tant qu’il en existe. Tri par extraction (élémentaire, heapsort) ALGORITHMIQUE - Cours 02 - page 7/28 - Bertrand LIAUDET L’opération de base consiste à rechercher l’extremum dans la partie non triée et à le permuter avec l’élément frontière, puis à déplacer la frontière d’une position. Tri par insertion L’opération de base consiste à prendre l’élément frontière dans la partie non triée et à l’insérer à sa place dans la partie triée, puis à déplacer la frontière d’une position. Autres méthodes Il existe de nombreuses autres méthodes qui ne sont pas abordées ici. 4. Exercices sur les tableaux Tableaux à une dimension Exercice 1 Ecrire une fonction qui calcule la somme des éléments d’un tableau. Exercice 2 Ecrire une fonction qui détermine la valeur la plus grande d’un tableau. On renverra le maximum puis la position du maximum dans le tableau. Exercice 3 Ecrire une fonction qui décale les éléments d’un tableau vers le bas (le i-ième passe en (i-1)ième, le premier passe en dernier (Nième). Exercice 4 Ecrire une fonction qui décale les éléments d’un tableau vers le haut (le i-ième passe en (i+1)ième, le dernier (Nième) passe en premier. Exercice 5 Ecrire une fonction qui inverse un tableau : le premier élément est permuté avec le dernier, le deuxième avec l’avant dernier, etc. Exercice 6 Ecrire une fonction qui recherche la première occurrence d’une valeur dans un tableau. Exercice 7 Ecrire une fonction qui recherche la première occurrence d’une valeur dans un tableau trié en utilisant la méthode de recherche dichotomique. Exercice 8 Ecrire une fonction qui tri un tableau en utilisant la méthode de tri par extraction. Le principe de ce tri est d’aller chercher le plus petit élément du tableau pour le mettre en premier, puis de recommencer l’opération en partant du second élément du tableau, puis du troisième, etc. Exercice 10 Ecrire une fonction qui supprime une valeur dans un tableau trié. ALGORITHMIQUE - Cours 02 - page 8/28 - Bertrand LIAUDET Exercice 11 Ecrire une fonction qui ajoute une valeur dans un tableau trié. Exercice 12 Ecrire une fonction qui élimine les doublons d’un tableau d’entiers. On considérera d’abord le tableau comme étant trié puis on traitera le cas général. Matrice Exercice 1 Ecrire une fonction qui calcule la moyenne des sommes des lignes et la moyenne des sommes des colonnes d’une matrice. Exercice 2 Ecrire une fonction qui calcule la moyenne par ligne et par colonne dans une matrice. Exercice 3 Ecrire une fonction qui multiplie deux matrices. Avec A matrice n lignes, m colonnes. B matrice m lignes, p colonnes. C matrice n lignes, p colonnes. Ci,j=somme(k=0, m) Ai,k * Bk,j. Exercice 4 Ecrire une fonction qui trie une matrice par ordre croissant selon une colonne définie. ALGORITHMIQUE - Cours 02 - page 9/28 - Bertrand LIAUDET 3 - LES POINTEURS 1. Rappels sur la variable Présentation Une variable est caractérisée par : un nom : n un type : int une valeur (ou contenu) : 3 une adresse : ad_n un contenant : le carré dessiné (il est à l’adresse ad_n et sa taille est donné par son type) un sens (ou signification) : par exemple, le nombre d'éléments d'un tableau. n, int 3 ad_n Le nom de la variable c'est "n". Le nom fait référence à (on peut dire c'est) soit la valeur, soit le contenant. Si le nom "n" est utilisé en entrée (c'est-à-dire à droite de l'affectation, en entrée d'une fonction ou plus généralement en entrée de toute expression), "n" c'est la valeur de "n". Si le nom "n" est utilisé en sortie (c'est-à-dire à gauche de l'affectation, en sortie d'une fonction ou plus généralement en sortie de toute expression), "n" c'est le contenant de n. On parle aussi de "lvalue" ("left value", "valeur à gauche" de l'affectation, ou "valeur_g" 1). Formalisme NTVA On peut représenter une variable sous la forme d'un quadruplet : (nom, type, val, ad) C'est le formalisme NTVA : (Nom, Type, Val, Adresse) nom type Val ad Exemple : (a, entier, 12, ad_A) A entier 12 adA Déclaration et usage 1 Quand on déclare une variable, on crée un contenant qui a un nom, un type et une adresse. La valeur est inconnue. Formalisme NTVA : (a, entier, ?, adA) Quand on affecte une valeur à une variable, on donne une valeur au contenant. K&R91 p. 196, §A5. ALGORITHMIQUE - Cours 02 - page 10/28 - Bertrand LIAUDET 2. Initialiser une variable, c'est donner pour la première fois une valeur au contenant de la variable. A noter que avant l'initialisation, la variable a quand même une valeur. En effet les bits constituant la variable sont dans un certain état (0 ou 1). Cet état, interprété selon le type, aboutit forcément à une valeur. Donc la valeur d'initialisation c'est la deuxième valeur que prend la variable. Pointeurs et opérateur & et * Le type pointeur Un pointeur est une variable qui contient comme valeur une adresse. Une adresse est de type « pointeur ». Un type pointeur est spécialisé en fonction du type de variable auquel il correspond. On l’écrit : « type * ». Par exemple : « entier * », « réel * », « TypEleve * », etc Opérateur & L’opérateur unaire & appliqué à une variable produit une valeur : l’adresse de la variable à laquelle il s’applique. La valeur de &a c’est l’adresse de a. Opérateur * L’opérateur unaire * appliquée à une valeur de type adresse produit une variable (et non pas une valeur). Cette variable, c’est celle dont l’adresse correspond à la valeur de type adresse à laquelle on applique l’opérateur. Exemple Soit n un entier et pt_n un pointeur d'entier. On écrit ça : n entier pt_n entier * pt_n est une variable avec ses caractéristiques : elle a une adresse, comme toute les variables et son type est "pointeur d'entier" : int *. pt_n entier * ad_pt_n n, entier ad_n Pour affecter à pt_n la valeur de l'adresse de n, on écrira : pt_n &n Schématiquement cela donne : pt_n entier * n, entier ad_n ad_pt_n ad_n On dit que pt_n est un pointeur d'entier, comme on dit que n est un entier. Définition *pt_n c'est la variable dont l'adresse est donnée par la valeur de "pt_n" (dans notre exemple c'est n). On dit aussi : * pt_n c'est la variable pointée par pt_n. ALGORITHMIQUE - Cours 02 - page 11/28 - Bertrand LIAUDET *pt_n peut s’utiliser comme n’importe quelle variable. On peut écrire : *pt_n 5 Schématiquement cela donne : pt_n entier * n, entier ad_n 5 ad_pt_n ad_n allocation, réservation de mémoire new(pt) avant : (pt, type*, ?, adpt) après : (pt, type*, ad, adpt) ---> (*pt, type, ?, ad) Quand on alloue dynamiquement une variable, on crée une variable qui n'a pas de nom. Notez que pt est en sortie bien sur puisque sa valeur a été modifiée (elle passe de ? à ad). pt est aussi en entrée car pour faire l'allocation, j'ai besoin de connaître le type de pt (type*), ce qui me permet de déterminer le type de la variable créer et donc la taille de l'espace mémoire de cette variable. Est en entrée une variable dont on utilise une des caractéristiques suivantes : la valeur ou le type. Pour les algorithmes de haut niveau, seule l'utilisation de la valeur compte. libération de la mémoire free (pt) avant : (pt, type*, ad, adpt) ---> (*pt, type, ?, ad) après : (pt, type*, ad, adpt) La variable pointée par pt est libérée (elle n’existe plus). Notez que la valeur de pt n'a pas été modifiée par la libération : pt n'est qu'en entrée. ALGORITHMIQUE - Cours 02 - page 12/28 - Bertrand LIAUDET 3. Tableau de pointeurs Un tableau peut contenir des pointeurs L’intérêt d’un tableau de pointeur, du point de vue de l’optimisation, c’est de limiter les recopies à la recopie de pointeurs (pour éviter de recopier toute une structure, par exemple). 4. Exercices sur les pointeurs Exercice 1 Décrire l’évolution de chaque variable après chaque instruction. int x, n, *pt_n; pt_n = &n; *pt_n = 5; n = 7; x = *pt_n; Exercice 2 Quel est le résultat des instructions suivantes : int n, * ad1; * ad1 = 3; Exercice 3 Quel est le résultat des instructions suivantes : int n, * ad1; ad1 = &n; * ad1 = 3; Exercice 4 Soit les tois instructions suivantes : int *ad1; int n=10; ad1 = &n Que valent les expressions suivantes après les deux instructions précédentes. Faire des schémas. ad1 &*ad1 *&ad1 n *&n &*n ALGORITHMIQUE - Cours 02 - page 13/28 - Bertrand LIAUDET 4 - LES LISTES 1. Présentation Présentation générale : la chaîne, le maillon Structure d’un maillon. Variable liste : la tête de liste. Liste simplement chaînées Liste doublement chaînées Listes circulaires 2. Liste simplement chaînées Structure d’un élément de liste Afficher les éléments de la liste. Ajouter un élément dans la liste Supprimer un élément dans la liste 3. Variantes Liste doublement chaînée Liste circulaire Liste avec compteur de maillons 4. Exercices sur les listes Exercice 1 Ecrire un algorithme qui permet de trouver le maximum dans une liste de réels. Exercice 2 Ecrire un algorithme qui permet d’ajouter un élément en queue de liste. Exercice 3 Ecrire un algorithme qui permet d’ajouter un élément dans une liste triée. Exercice 4 Ecrire un algorithme qui permet de supprimer toutes les occurrences d’un élément dans une liste non triée. ALGORITHMIQUE - Cours 02 - page 14/28 - Bertrand LIAUDET Exercice 5 Ecrire un algorithme qui permet de supprimer tous les doublons dans une liste non triée. Exercice 6 Ecrire un algorithme qui inverse une liste chaînée (sans recopier ses éléments). Exercice 7 Ecrire un algorithme qui fusionne deux listes d’entiers triés en une seule liste triée. Les deux listes initiales seront détruites. On évitera de recopier les éléments. Exercice 8 Ecrire un algorithme qui éclate une listes d’entiers en deux listes : une liste dont les valeurs seront supérieures à une limite donnée, et une liste dont les valeurs seront inférieures. La liste initiale sera détruite. On évitera de recopier les éléments. Exercice 9 Le « problème de Joseph ». Soit une liste contenant dans l’ordre les n premiers entiers de N. Ecrire un algorithme qui affiche et supprime les nœuds de k en k. jusqu’à ce que la liste soit vide. Exemple : n = 8 et k = 3. On affiche : 3, 6, 1, 5, 2, 8, 4, 7. ALGORITHMIQUE - Cours 02 - page 15/28 - Bertrand LIAUDET 5 - RECURSIVITE 1. Présentation Principe Une procédure (ou une fonction) est récursive quand elle s’appelle elle-même. La récursivité est l’équivalent d’une boucle sans fin : le problème est de déterminer une condition de sortie. Le problème algorithmique se ramène souvent à l’expression d’une suite mathématique. Exemple : somme des N premiers nombres Approche itérative S = 1+2+3 ... + N-1 + N. On boucle N fois et on somme tous les nombres. Le problème est d’initialiser S : ici à 0. Algorithme Somme(N) : entier // somme des N premiers nombres S=0 ; Pour i de 1 à N S=S+i FinPour Return S Fin Approche récursive : formule de suite S(0) = 0 : c’est la condition de sortie S(N) = S(N-1) + 1 : c’est la formule récursive Algorithme Somme(N) : entier // somme des N premiers nombres Si N=0 return N ; Return S(N-1) + 1; Fin Remarque Il n’est pas toujours évident de trouver une formulation sous la forme d’une suite. Il faut toutefois chercher la condition de sortie et la formule récursive. Intérêt et limite de la récursivité Intérêt L’intérêt de la récursivité est qu’elle facilite considérablement l’écriture de certains algorithmes, particulièrement les algorithmes sur les arbres. Limites La récursivité est limitée car le nombre d’appels récursifs est limité : environ un centaine au maximum (chaque appel récursif implique pour le programme de mémoriser tout l’environnement, ce qui est coûteux en mémoire. En général, les compilateurs limite le nombre d’appels récursifs à une centaine. Chaque environnement est empilé pour être ensuite dépilé, d’où le message d’erreur : « stack overflow ».). Il faut donc vérifier que cette limitation n’est pas contraignante. ALGORITHMIQUE - Cours 02 - page 16/28 - Bertrand LIAUDET Dans l’exemple traité, l’usage de la récursivité est à proscrire car on peut souhaiter calculer la somme des 10 000 premiers nombres. L’usage de la boucle est toutefois à optimiser en utilisant la formule = S = N(N+1)/2 ! 2. Exercices Exercice 1 Ecrire l’algorithme récursif permettant de calculer N ! Le choix de la récursivité est-il pertinent ? Exercice 2 On désire calculer le terme d’ordre n de la suite de Fibonacci définie par : F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2). Écrire une fonction itérative et une fonction récursive qui permet de calculer F(n) et un programme pour la tester. Le choix de la récursivité est-il pertinent ? Exercice 3 Ecrire l’algorithme récursif permettant de calculer X puissance N Le choix de la récursivité est-il pertinent ? Exercice 4 Ecrire l’algorithme récursif permettant de faire une recherche dichotomique dans un tableau trié. Le choix de la récursivité est-il pertinent ? Exercice 5 Ecrire l’algorithme récursif permettant de faire une recherche dans une liste chaînée. Le choix de la récursivité est-il pertinent ? ALGORITHMIQUE - Cours 02 - page 17/28 - Bertrand LIAUDET 6 - LES ARBRES 1. Modèle logique des arbres Un arbre est une collection d’éléments appelés « nœuds ». Ces nœuds sont reliés entre eux par un lien orienté : le « noeud parent » est au départ du lien, le « noeud enfant » est à l’arrivée du lien. Un noeud a un parent et un seul sauf un noeud particulier qui n’a pas de parent : la « racine ». 2. ABOH (arbre planaire) Modèle logique Un ABOH (arbre binaire orienté horizontalement) ou arbre « planaire » est un arbre dans lequel : les parents n’ont pas plus de deux enfants (arbre binaire) Les enfants sont distingués en « enfant gauche » et « enfant droit » les valeurs de « enfant gauche », parent, et « enfant droit » sont triées (arbre orienté horizontalement). Modèle physique des données – Version dynamique Principe arbre = pointeur sur un nœud nœud = éléments de l'arbre; l'élément contient des informations et deux pointeurs sur deux nœuds. C’est pointeurs sont appelés sous arbre droit et gauche, sad et sag. racine = nœud origine de l'arbre = premier nœud pointé par l'arbre. Chaque nœud peut être considéré comme la racine d'un arbre feuille = nœud qui n'a pas de sous arbres branche = suite des nœuds de la racine jusqu'à une feuille sans jamais remonter Exemple 10 6 14 3 8 12 17 13 ALGORITHMIQUE - Cours 02 - page 18/28 - Bertrand LIAUDET Définition des types Type TypElementArbre info : entier sag : TypElementArbre * sad : TypElementArbre * fin Type Arbre as TypElementArbre * Algorithmes Méthode Quel que soit l'exercice qu'on a à résoudre, il faut se demander : Est-ce qu'on va parcourir tous les nœuds ? Est-ce qu'on va parcourir une branche pour rechercher un nœud particulier ? Selon la réponse, on aboutira à un algorithme type ou à un autre. Deux algorithmes types Les deux algorithmes types correspondent aux deux façons de circuler dans un arbre : Parcourir tous les nœuds Algorithme de parcours en ordre (pour l'ordre), en post ordre (pour remonter les info des feuilles à la racine), en pré ordre (pour archiver et reconstituer l'arbre) : ce sont des algorithmes récursifs. Leurs versions itératives sont compliquées et utilisent des piles. Parcourir une branche Le parcours d’une branche est utilisé pour l’accès à un élément d’un arbre : pour la recherche, l’insertion, la suppression, etc. L’algorithme est de même type que celui d’un parcours de liste chaînée. Il peut être itératif ou récursif. Algorithme type de parcours de tous les noeuds tsLesNoeudsEnOrdre(A) si A!= 0 // traiter(*a.info) : pour le pré-ordre tsLesNoeudsEnOrdre(*a.sag) traiter(*a.info) // pour l’ordre tsLesNoeudsEnOrdre(*a.sad) // traiter(*a.info) : pour le post-ordre finsi ALGORITHMIQUE - Cours 02 - page 19/28 - Bertrand LIAUDET Algorithme type de recherche de tous les noeuds Version itérative arbre : chercher(A, info) nc A tq nc != 0 si *nc.info = info return nc sinon si *nc.info < info nc *nc.sag sinon nc *nc.sad finsi fintq return null fin Version récursive arbre : chercher(A, info) si A = null return A finsi si *A.info = info return A sinon si *A.info < info chercher (*nc.sag, info) sinon chercher(*nc.sad, info) finsi fin 3. Arbres équilibrés (AVL) Le problème des arbres est qu’ils peuvent devenir déséquilibré, c’est-à-dire ressembler à une liste chaînée. Les arbres équilibrés sont des arbres dont les algorithmes d’insertion et de suppression sont adaptés de telle sorte qu’ils permettent de maintenir l’équilibre de l’arbre. Le critère d’équilibre parfait est : pour tout nœud, les nombres de nœuds du SAG et du SAD diffèrent au maximum de 1. Un critère d’équilibre partiel courant est : pour tout nœud, les hauteurs du SAG et du SAD diffèrent au maximum de 1. 4. Arbres B (B trees) Présentation L’arbre B est un compromis entre les organisations arborescentes et les organisations séquentielles indexées. Ce sont des arbre n-aires orientés horizontalement (planaires). ALGORITHMIQUE - Cours 02 - page 20/28 - Bertrand LIAUDET Un nœud est appelé « page » et contient plusieurs enregistrements successifs. L’objectif d’une telle structure est de limiter les transferts disques. Principes spécifiques Un arbre B a un numéro d’ordre : N. Les nœuds ont au maximum 2 * N + 1 enfants. Les nœuds non-feuilles et non racines ont au minimum N + 1 enfants. Chaque nœud non-feuille porte NF-1 enregistrements, avec NF = nombre de fils du nœud. Chaque nœud feuille porte 2* N enregistrements au maximum (2*N fiches ou 2*N tuples par exemple). Exemple d’arbre B d’ordre 1 3 6 9 18 12 15 10-11 1-2 4-5 7-8 21 24 16-17 13 14 19-20 22-23 25-26 A noter que cet arbre B est un arbre BB. 5. Arbres BB Ce sont des arbres B dit « de Bayer » : arbre BB. Ils contiennent 1 ou 2 enregistrements par page. Ils sont aussi appelés « arbre 2-3 » car chaque page contient 2 ou 3 pointeurs sur ses pages filles. Ce type d’arbre n’est pas intéressant pour stocker une table sur disque car la petite taille des pages implique un grand nombre d’accès disque. Par contre, ce type d’arbre est intéressant pour gérer des tables entièrement en mémoire. 6. Arbres SBB (bicolores). Ce sont des arbres BB symétrique : SBB pour Symetric Binary B Tree. On parle aussi d’arbres « bicolores ». Le SBB est un arbre BB équilibré. ALGORITHMIQUE - Cours 02 - page 21/28 - Bertrand LIAUDET 7. Exercices sur les ABOH Exercice 1 Définir la structure d’un élément d’arbre binaire. Construire un arbre binaire avec les séries de valeurs suivantes : 5, 2, 1, 10, 8, 12 7, 5, 10, 1, 3, 12, 30, 25, 1, 2, 3 Qu’est-ce qu’un nœud ? Qu’est-ce qu’une feuille ? Qu’est-ce qu’une racine ? Qu’est-ce qu’une branche ? Exercice 2 : parcours d’une branche : recherche d’un élément Ecrire l’algorithme itératif et récursif de recherche d’un élément dans un arbre. Exercice 3 : parcours de tout l’arbre en ordre, pré-ordre, post-ordre Afficher tous les éléments d’un arbre, dans l’ordre croissant (en ordre). Ecrire l’algorithme correspondant. Afficher tous les éléments d’un arbre en « pré-ordre » . Ecrire l’algorithme correspondant. Afficher tous les éléments d’un arbre en « post-ordre » . Ecrire l’algorithme correspondant. Remarque : intérêt de l’ordre, du pré-ordre, et post-ordre L’ordre, comme son nom l’indique, permet de parcourir l’arbre dans l’ordre. Le pré-ordre permet de traiter chaque nœud complètement (comme un sous-arbre), en commençant par la racine. En enfilant les résultats, on peut reconstruire l’arbre en défilant. Le post-ordre permet de traiter chaque nœud complètement (comme un sous-arbre), en finissant par la racine. En empilant les résultats, on peut reconstruire l’arbre en dépilant. Exercice 4 : Compter le nombre d’éléments d’un arbre binaire Exercice 5 : Ajouter un élément dans un arbre binaire Exercice 6 : Suppression d’un élément dans un arbre binaire On commencera par écrire un algorithme qui permet de remplacer la racine par le plus grand élément de son sousarbre gauche, et un algorithme qui permet de remplacer la racine par le plus petit élément de son sous-arbre droit. Ensuite on écrira l’algorithme de suppression d’un élément dans un arbre. Exercice 7 : Calculer la hauteur d’un arbre binaire Exercice 8 : Vérifier l’équilibre partiel d’un arbre Arbre parfaitement équilibré : pour tout nœud, abs ( nbNoeuds(sag) - nbNoeuds(sad) ) <=1 Arbre partiellement équilibré : pour tout noeuc, abs ( hauteur(sag) –hauteur(sad) ) <=1 ALGORITHMIQUE - Cours 02 - page 22/28 - Bertrand LIAUDET 7 - LES GRAPHES 1. Présentation ALGORITHMIQUE - Cours 02 - page 23/28 - Bertrand LIAUDET 8 - HACHAGE 1. Présentation ALGORITHMIQUE - Cours 02 - page 24/28 - Bertrand LIAUDET 9 - LES TAD : VERS LA P.O.O. 1. Présentation générale des TAD Présentation TAD : type abstrait de donnée. Un TAD est un type complexe caractérisé par sa structure et par un ensemble de primitives permettant de s’en servir. Principaux TAD Chaînes de caractères Piles Files Collections Fichiers 2. Les chaînes de caractères Présentation Une chaîne de caractères est un nouveau type : « Chaîne » Les constantes chaînes de caractères sont entre guillemets, tandis que les caractères sont entre apostrophes. Primitives d’utilisation des chaînes de caractères : Longueur (ch) : entier // renvoie la longueur d’une chaîne. Concat (ch1, ch2) : chaîne // renvoie la concatenation de deux chaînes (on peut aussi utiliser le signe « + ») Substr (ch, debut, lgr) : chaîne // renvoie le morceau de chaîne commençant en « début » et de longueur « lgr ». ch[4] : caractère // renvoie le 4ème caractère de la chaîne (même principe que pour un tableau) Dans les algorithmes, on peut appliquer les primitives de chaînes aux caractères. Exercice 1 Écrire un algorithme qui détermine si une chaîne est un palindrome (exemples : Laval, radar, kayak, « Engage le jeu que je le gagne »). Exercice 2 Ecrire un algorithme qui détermine si une chaîne est un anagramme d’une autre (exemples : aimer et marie, baignade et badinage). Exercice 3 Ecrire un algorithme qui compte les occurrences d’un mot dans un texte. Exercice 4 Ecrire un algorithme qui compte les occurrences de tous les mots d’un texte. L’algorithme permettra de produire un fichier avec la liste des mots comptés et triés par ordre alphabétique. Dans un premier temps, on écrira l’algorithme en se dotant de sous-procédures qui traitent les principaux problèmes. ALGORITHMIQUE - Cours 02 - page 25/28 - Bertrand LIAUDET On considère que les séparateurs de mots sont des caractères contenus dans une chaîne de séparateurs. On ne s’intéresse qu’aux mots d’au moins 3 lettres. On se donne un tableau de mots d’au moins 3 lettres pour lesquels on ne fera pas le calcul. 3. Les piles Présentation Une pile est une structure de données permettant de gérer des collections d’éléments de même type, le principe d’utilisation étant que le dernier entré sera le premier sorti : LIFO (Last In, First Out, principe de la pile d’assiettes). Une pile est un nouveau type : typPile. Les primitives permettant d’utiliser une pile sont les suivantes : initPile (p) // permet d’initialiser une pile. pileVide (p) : booleen // dit si la pile est vide pilePleine (p) : booleen // dit si la pile est pleine dépiler (p, val, ok) // dépile la valeur val de la pile p. Ok vaut faux si la pile était vide. ES :p :typPile. S:val:typeAuChoix. S:ok:booleen. empiler (p, val, ok) // empile une valeur val dans la pile p. Ok vaut faut si la pile était pleine. ES :p :typPile. E:val:typAuChoix. S :ok :booleen. Exercice 1 La notation algébrique classique est la suivante : 5* ( (9 + 8)*(4 + 6) + 7 ) Elle peut être transformée par une écriture dite en « polonaise inversée » : 5 9 8 + 4 6 + * 7 + * Ou encore : 9 8 + 4 6 + * 7 + 5 * En notation polonaise inversée (notation post-fixée), l’opérateur figure après les opérandes sur lesquels il agit. La notation est gérée comme une pile : on empile les opérandes, et on en dépile deux dès qu’on tombe sur un opérateur, on fait l’opération et on empile le résultat. Par exemple : on empile 9, puis 8, puis on les dépile, on fait l’opération 9+8 et on empile le résultat 17. Puis on empile 4, puis 6 et on les dépile, etc. Ecrire un algorithme qui gère avec une pile une calculette en polonaise inversée. On considèrera que la calculette ne contient les touches suivantes : Chiffres : 10 touches pour constituer les opérandes : 0 à 9. (On ne gère pas la virgule, on ne gère la saisie que de chiffres <10). Opérateurs binaires : 4 touches d’opérateur : +, -, *, / . (On ne gère pas les opérateurs unaires). Annuler : 1 touche « ESC » pour tout annuler. Ecrire l’algorithme de la calculette en utilisant une pile et les primitives de pile. En cas d’erreur, on annulera tout le calcul en cours. Exercice 2 Définir la structure de donnée statique nécessaire pour gérer une pile (structure avec un tableau). Ecrire les algorithmes des primitives de gestion des piles. ALGORITHMIQUE - Cours 02 - page 26/28 - Bertrand LIAUDET 4. Les files Présentation Une file est une structure de données permettant de gérer des collections d’éléments de même type, le principe d’utilisation étant que le premier entré sera le premier sorti : FIFO (FIRST In, First Out, principe de la file d’attente). Une pile est un nouveau type : typFile. Les primitives permettant d’utiliser une pile sont les suivantes : initFile (p) // permet d’initialiser une pile. fileVide (p) : booleen // dit si la file est vide filePleine (p) : booleen // dit si la file est pleine défiler (f, val, ok) // défile la valeur val de la file f. Ok vaut faux si la file était vide. ES :p :typFile. S:val:typeAuChoix. S:ok:booleen. enfiler (f, val, ok) // emfile une valeur val dans la file f. Ok vaut faut si la file était pleine. ES :p :typFile. E:val:typAuChoix. S :ok :booleen. Exercice 1 Ecrire un algorithme qui permette de d’afficher tous les éléments d’une file en conservant la file et en n’utilisant que les primitives de file. Exercice 2 5. Définir la structure de donnée statique nécessaire pour gérer une file (structure avec un tableau circulaire). Ecrire les algorithmes des primitives de gestion des files. Les collections Une collection est une structure de données qui permet de gérer une collection quelconque. Les primitives permettant d’utiliser une collection sont : initCollection (c) collectionVide (c). Renvoie vrai si la collection est vide. collectionPleine (c). Renvoie vrai si la collection est pleine. ajouter(c, val, ok). Ajoute un élément de valeur val dans la collection/ retirerVal(c, val, ok). Retier l’élément de valeur val de la collection. rechercher(c, val, ok) : entier // Récupère la position de l’élément de valeur val dans la collection. 0 si pas trouvé. getCollec(c, i) : val // Renvoie la valeur du ième élément de la collection. Equivaut à c[i]. affecter(c, i, val) // Affecte val dans le ième élément de la collection. Equivaut à c[i] <- val. Les tableaux, les piles, les files, les listes, les arbres sont différents types de collection. 6. Les fichiers Les fichiers sont une forme de TAD. On les abordera à la fin de ce document. ALGORITHMIQUE - Cours 02 - page 27/28 - Bertrand LIAUDET 10 - USAGES TECHNIQUES DES POINTEURS 1. Gestion d’une pile LIFO avec une liste chaînée 2. Gestion d’une file FIFO avec une liste chaînée 3. Pointeurs et tableaux Tableaux de pointeurs Tableau et chaîne de caractères Tableaux et pointeurs 4. Pointeurs et chaînes de caractères 5. Pointeurs et mode de passage des paramètres ALGORITHMIQUE - Cours 02 - page 28/28 - Bertrand LIAUDET