Objectifs du cours d aujourd hui Informatique I : Cours d introduction à l informatique et à la programmation Structures de Données Abstraites & Tris Continuer l approfondissement de la programmation de base en présentant d autres thèmes de l informatique : introduction aux structures de données abstraites : listes chaînées piles introduction aux algorithmes de tris Jamila Sam Haroud Laboratoire d Intelligence Artificielle Faculté I&C Informatique I Cours1 : S.D.A. & Tris 1 Informatique I Cours1 : S.D.A. & Tris Pourquoi modéliser les données? C est quoi une «structure de données abstraite»? L élaboration d un algorithme est grandement facilité par l utilisation de structures de données abstraites, de plus haut niveau, et de fonctions de manipulations associées. Une structure de données doit modéliser au mieux les informations à traiter pour en faciliter le traitement par l algorithme considéré. Choisir les bons modèles de données est aussi important que le choix de bons algorithmes Algorithme et structure de données abstraite sont intimement liés : Programme = algorithme + données Une structure de données abstraite (S.D.A.) est un ensemble organisé d informations (ou données) reliées logiquement et pouvant être manipulées non seulement individuellement mais aussi comme un tout. Exemples généraux : tableau (au sens général du terme) contenu : divers éléments de types à préciser interactions : demander la taille du tableau, accéder (lecture/écriture) à chaque élément individuellement,... vecteur (au sens général, pas C++) : formalisation mathématique d espace vectoriel sur un corps K contenu : n coordonnées (éléments de K) interactions : les propriétés élémentaires définissant un espace vectoriel Informatique I Cours1 : S.D.A. & Tris 3 Informatique I Cours1 : S.D.A. & Tris 4
C est quoi une «structure de données abstraite»? Une structure de données abstraite (S.D.A.) est un ensemble organisé d informations (ou données) reliées logiquement et pouvant être manipulées non seulement individuellement mais aussi comme un tout. Exemple informatique élémentaire : Vous connaissez déjà des structures de données abstraites, très simples : les types élémentaires. Par exemple, un int interactions : affectation, lecture de la valeur, +, -, *, / Spécifications des structures de données abstraites Une S.D.A. est caractérisée par : son contenu les interactions possibles (manipulation, accès,...) Du point de vue informatique, une structure de données abstraite peut être spécifiée à deux niveaux : niveau fonctionnel / logique : spécification formelle des données et des algorithmes de manipulation associés niveau physique (programmation) : comment est implémentée la structure de données abstraite dans la mémoire de la machine déterminant pour l efficacité des programmes utilisant ces données. Informatique I Cours1 : S.D.A. & Tris 4 Informatique I Cours1 : S.D.A. & Tris 5 Spécifications des S.D.A. [] Spécifications des S.D.A. [3] Au niveau formel (modèle), on veut généraliser cette idée «d objets» manipulables par des opérateurs propres, sans forcément en connaître la structure interne et encore moins l implémentation. Par exemple, vous ne pensez pas un int comme une suite de 3 bits, mais bien comme un «entier» (dans un certain intervalle) avec ses opérations propres : +, -, *, / Une structure de données abstraite définit une abstraction des données et cache les détails de leur implémentation. abstraction : identifier précisément les caractéristiques de l entité (par rapport à ses applications), et en décrire les propriétés. Informatique I Cours1 : S.D.A. & Tris Une structure de données abstraite modélise donc l «ensemble des services» désirés plutôt que l organisation intime des données (détails d implémentation) On identifie usuellement 4 types de «services» : 1. les modificateurs, qui modifient la S.D.A.. les sélecteurs, qui permettent «d interroger» la S.D.A. 3. les itérateurs, qui permettent de parcourir la structure 4. les constructeurs (que l on verra plus tard) Exemple : tableau dynamique modifieur : affectation d un élément (t[i]=a) sélecteur : lecture d un élément (t[i]) sélecteur : le tableau est-il vide? (t.size() == 0) itérateur : index d un élément ([i] ci-dessus) Informatique I Cours1 : S.D.A. & Tris 7
Divers exemples de S.D.A. Il y a beaucoup de structures de données abstraites en Informatique. Dans ce cours, nous n allons voir que les plus fondamentales (après les tableaux) : les listes et les piles Autres : files d attente (avec ou sans priorité) multi-listes arbres (pleins de sorte...) graphes tables de Hash Listes Spécification logique : Ensemble d éléments successifs (pas d accès direct), ordonnés ou non Interactions : accès au premier élément (sélecteur) accès à l élément suivant d un élément (sélecteur) modifier l élément courant (modificateur) insérer/supprimer un élément après(/avant) l élément courant (modificateur) tester si la liste est vide (sélecteur) parcourir la liste (itérateur) Informatique I Cours1 : S.D.A. & Tris 8 Informatique I Cours1 : S.D.A. & Tris 10 Listes Réalisations d une liste Exemple concret : Exemple informatique : visionneuse stéreo (essayez d accéder à la 3e image directement, sans passer par la e!) ( a ( b ( c (d)))) a b c d 0 Une liste peut être vu comme une structure récursive : liste = élément + liste OU liste = vide réalisation statique : tableau réalisation dynamique (liste chaînée) : ou vector (mais inconvénient 1 ci-après) structure : struct Element ; typedef Element* ListeChainee ; struct Element { type_el element; ListeChainee suivant; Informatique I Cours1 : S.D.A. & Tris 11 Informatique I Cours1 : S.D.A. & Tris 1
Pourquoi les listes dynamiques? Les tableaux sont un type de données très utile en programmation mais présentent limitations : 1. les données sont contiguës (les unes derrières les autres) et donc l insertion d un nouvel élément au milieu du tableau demande la recopie (le décalage) de tous les éléments suivants. = insertion en O(n). pour les tableaux statiques, augmenter la taille (par exemple si elle n est pas connue a priori) nécessite la création d un nouveau tableau = O(n) Complexité optimale des opérations insérer un élément : supprimer un élément : calculer la longueur : élémentaires sur les listes O(1) O(1) O(n) (voire O(1) si le stockage de cette valeur est effectué, en particulier si «longueur» a été spécifiée dans les «services» de la SDA «liste») vider la liste : parcourir la liste : O(n) O(n) Informatique I Cours1 : S.D.A. & Tris 13 Informatique I Cours1 : S.D.A. & Tris 14 Exemples d implémentation des opérations élémentaires sur les listes Exemples d insertion d un élément À des fins pédagogiques, voici une implémentation simple des listes dynamiques sous forme de listes chaînées struct Element; typedef Element* ListeChainee; struct Element { type_el element; ListeChainee suite; dans une liste void insere(listechainee& liste, type_el un_element) { if (est_vide(liste)) { liste = new Element; (*liste).element = un_element; // On peut aussi écrire liste->element = un_element; liste->suite = LISTE_VIDE; else insere(*liste, un_element); après un élément donné Informatique I Cours1 : S.D.A. & Tris 15 void insere(element& existant, type_el a_inserer) { Element* e(new Element); e->element = a_inserer; e->suite = existant.suite; existant.suite = e; Informatique I Cours1 : S.D.A. & Tris 1
de la tête de liste Exemples de suppression void supprime(listechainee& liste) { if (!est_vide(liste)) { ListeChainee nouvelle(liste->suite); delete liste; liste = nouvelle; Exemple de calcul de la longueur unsigned int taille(listechainee liste) { unsigned int taille(0); while (!est_vide(liste)) { ++taille; liste = liste->suite; return taille; d un élément suivant un élément donné void supprime(element& e) { // supprime le premier élément de la liste "suite" supprime(e.suite); Informatique I Cours1 : S.D.A. & Tris 17 Informatique I Cours1 : S.D.A. & Tris 18 Piles Piles : exemples Spécification : Une pile est une structure de données abstraite dynamique contenant des éléments homogènes (de type non précisé) à 1 point d accès et permettant d ajouter une valeur à la pile (empiler ou push) ; de lire la dernière valeur ajoutée ; d enlever la dernière valeur ajoutée (dépiler ou pop) ; de tester si la pile est vide. Exemples concrets : une pile d assiettes une tour dans le jeu des tours de Hanoi On ne «connait» donc de la pile que le dernier élément empilé (son sommet). Spécification physique : liste chaînée ou tableau dynamique (vector) Informatique I Cours1 : S.D.A. & Tris 0 Informatique I Cours1 : S.D.A. & Tris 1
Piles : exemples () Exemple d utilisation des piles Exemple d utilisation (formelle) : empiler x empiler a dépiler empiler b x a x x b x Le problème des parenthèses : étant donnée une expression avec des parenthèses, est-elle bien ou mal parenthésée? ((a + b) c (d + 4) (5 + (a + c))) (c + (d + (e + 5 g) f) a) (correct) (a + b)( (incorrect) Encore un peu plus complexe : différentes parenthèses Exemple avec [ et ( ([])[()(()[])] correct ([)] incorrect empiler y dépiler y b x b x Informatique I Cours1 : S.D.A. & Tris Autres exemples d utilisation des piles (non traités ici) : tours de Hanoi notation postfixée (ou «polonaise inverse») : 4 + 5 ( 5 (4 + )) Informatique I Cours1 : S.D.A. & Tris 3 Vérification de parenthésage Deuxième Exemple Tant que lire caractère c Si c est ( ou [ empiler c Sinon Si c est ) ou ] Si pile vide ÉCHEC Sinon c lire la pile Si c et c correspondent dépiler Sinon ÉCHEC Si pile vide OK Sinon ÉCHEC Exemple Entrée : ([)] empile ( ( empile [ lu = ), top = [ ne correspond pas = ERREUR [ ( Entrée : ([()]) empile ( ( empile [ empile ( lu ) correspond = dépile lu ] correspond = dépile ( lu ) correspond = dépile [ ( ( [ ( [ ( Informatique I Cours1 : S.D.A. & Tris 4 pile vide = OK Informatique I Cours1 : S.D.A. & Tris 5
code C++ bool check(string s) { Pile p; for (unsigned int i(0); i < s.size(); ++i) { if ((s[i] == ( ) (s[i] == [ )) empile(p,s[i]); else if (s[i] == ) ) { if ((!est_vide(p)) && (top(p) == ( )) depile(p); else return false; else if (s[i] == ] ) { if ((!est_vide(p)) && (top(p) == [ )) depile(p); else return false; return est_vide(p); Plan Terminons nos approfondissements en nous intéressant au problème du tri d une S.D.A. Structures de données abstraites Listes Piles Tris Informatique I Cours1 : S.D.A. & Tris Informatique I Cours1 : S.D.A. & Tris 7 Algorithmes et données : les tris les tris Les méthodes de tris sont très importantes en pratique non seulement en soi, mais aussi parce qu elle interviennent dans beaucoup d autres algorithmes. Elles sont par exemple nécessaires pour faire une recherche efficace. Le problème du tri est également un problème intéressant en tant que tel et un bon exemple de problème pour lequel il existe de nombreux algorithmes. Spécification du problème : On considère une structure de données abstraite contenant des éléments que l on peut comparer (entre eux : relation d ordre totale sur l ensemble des éléments) On dira que la S.D.A est triée si ses éléments sont disposés par ordre croissant pour l opérateur de comparaison. Informatique I Cours1 : S.D.A. & Tris 8 Par exemple : on cherche à trier un tableau de taille fixe d entiers. Remarques : entrée 1 3 programme de tri un tri ne supprime pas les doublons sortie 1 3 quelque soit l algorithme de tri, une S.D.A. vide ou réduite à un seul élément est déjà triée!... On parle de tri interne (par opposition à tri externe) lorsque l on peut effectuer le tri en mémoire dans la machine, sans utiliser de support extérieur (fichier). Informatique I Cours1 : S.D.A. & Tris 9
Un premier exemple : le tri par insertion Exemple de déroulement du tri par insertion Le principe du tri par insertion est extrêmement simple : un élément mal placé dans le tableau va systématiquement être inséré à sa «bonne place» dans le tableau. Tant que il y a un élément mal placé on cherche sa bonne place on déplace l élément à sa bonne place 1 3 5 4 1 3 5 4 1 3 4 5 Par «élément mal placé», on entend ici tout élément du tableau strictement plus petit que son prédécesseur. Tant que il y a un élément mal placé on cherche sa bonne place on déplace l élément à sa bonne place Informatique I Cours1 : S.D.A. & Tris 30 Informatique I Cours1 : S.D.A. & Tris 31 Tri par insertion : résolution globale Tri par insertion : résolution détaillée Le schéma général de l algorithme de tri par insertion est donc le suivant : tri insertion entrée : un tableau sortie : le tableau trié Tant que il y a un élément mal placé on cherche sa bonne place on déplace l élément à sa bonne place Il faut spécifier plus clairement les blocs ci-dessus. Le bloc mal placé entrée : un tableau tab sortie : indice du 1 er élément de tab de prédécesseur strictement plus grand Par convention, s il n y a pas d élément mal placé, la sortie sera l indice du premier élément du tableau (0 en C++). Comme le 1 er élément de tab ne peut être mal placé (car sans prédécesseur), le bloc mal placé parcourira donc les éléments de tab à partir du e. Le bloc mal placé effectue donc une itération sur les éléments de tab, du deuxième au dernier. Informatique I Cours1 : S.D.A. & Tris 3 Informatique I Cours1 : S.D.A. & Tris 33
Tri par insertion : résolution détaillée () Le bloc bonne place entrée : un tableau tab et l indice pos d un élément mal placé sortie : l indice newpos de la bonne place de l élément mal placé dans le tableau. La «bonne position» correspond à la plus grande position newpos (<pos) dans le tableau tab telle que tab[newpos-1] tab[pos]. Le bloc bonne place doit donc parcourir les positions de tab, entre le premier élément et pos, à la recherche d une bonne position. Le bloc bonne place effectue donc aussi une itération sur les éléments du tableau, du premier élément à celui d indice pos. Tri par insertion : résolution détaillée (3) Le bloc déplace entrée : un tableau tab, un indice d origine pos et un indice final newpos Il doit déplacer l élément d indice pos dans tab à l indice newpos. On peut effectuer cette opération par décalages successifs (en utilisant un stockage temporaire tmp). tmp tab... newpos newpos+1 pos Informatique I Cours1 : S.D.A. & Tris 34 Informatique I Cours1 : S.D.A. & Tris 35 Tri par insertion : codage Tri par insertion : codage (3) Codage de l algorithme de tri par insertion : void tri_insertion(int tab[], unsigned int taille) { for (int pos(mal_place(tab,taille)); pos > 0; pos = mal_place(tab,taille)) deplace(pos, bonne_place(pos,tab), tab); Codage de la fonction mal placé : unsigned int mal_place(int tab[], unsigned int taille) { for (unsigned int i(1); i < taille; ++i) if (tab[i-1] > tab[i]) return i; return 0; Informatique I Cours1 : S.D.A. & Tris 3 Codage de la fonction bonne place : unsigned int bonne_place(unsigned int pos, int tab[]) { // il est important de déclarer i avant : pourquoi? unsigned int i; for (i = 0; (i < pos) && (tab[i] <= tab[pos]); ++i); return i; Codage de la fonction déplace : void deplace(unsigned int pos, unsigned int newpos, int tab[]) { int tmp(tab[pos]); // stockage temporaire for (unsigned int i(pos); i > newpos; --i) { tab[i] = tab[i-1]; tab[newpos] = tmp; Informatique I Cours1 : S.D.A. & Tris 37
Version itérative : améliorations Version itérative : améliorations 1. Pour rechercher le prochain élément mal placé, ce n est pas la peine de recommencer du début à chaque fois. On peut partir de la dernière position mal placée.. On pourrait faire la recherche de la bonne place et le déplace ment à cette place en même temps (i.e. en 1 seule boucle) Si on regroupe tout ceci on arrive à l algorithme suivant : Pour i de à n pivot tableau[i] j i Tant que j et tableau[j 1]>pivot tableau[j] tableau[j 1] j j 1 tableau[j] pivot Informatique I Cours1 : S.D.A. & Tris 38 Et voici le code en C++ : void tri_insertion(int tab[], unsigned int taille) { for (unsigned int pos(1); pos < taille; ++pos) { int mal_place(tab[pos]); unsigned int j; for (j = pos; (j > 0) && (tab[j-1] > mal_place); --j) tab[j] = tab[j-1]; tab[j] = mal_place; Informatique I Cours1 : S.D.A. & Tris 39 Version récursive On peut aussi concevoir le tri par insertion de façon récursive : tri entrée : tableau de n éléments sortie : tableau trié condition arrêt : moins de éléments tri (instance réduite du problème) entrée : tableau de n 1 éléments sortie : tableau trié... insertion du n ème élément dans le tableau trié de n 1 éléments Version récursive : exemple 1 3 5 4 1 3 5 4 tri 1 3 5 4 insertion 1 3 4 5 Informatique I Cours1 : S.D.A. & Tris 40 Informatique I Cours1 : S.D.A. & Tris 41
Implémentation récursive L algorithme de tri récursif sera implémenté en C++ par la fonction : // trie le tableau tab entre first et last void tri_recursif(int tab[], int first, int last) { if (last > first) // moins de éléments -> fini { tri_recursif(tab, first, last-1); deplace(last, bonne_place(last, tab), tab); Schéma des appels récursifs : exemple tri_recursif({3,,1,0,) {1,,3 tri_recursif({3,,1,0,1) ; deplace(, {,3,1) {,3,1 Appel de départ : tri_recursif(mon_tab, 0, taille-1) ; tri_recursif({3,,1,0,0) ; deplace(1,{3,,1) {3,,1 Informatique I Cours1 : S.D.A. & Tris 4 Informatique I Cours1 : S.D.A. & Tris 43 Conclusions sur les tris Conclusions sur les tris : comparaison Ce n était qu une brève introduction, du fait de l importance des tris. Il existe plein d autres algorithmes de tris : par sélection bulles tri shaker tri de Shell tri tournois tri fusion tri par tas quick sort («tri rapide»)... COMPLEXITÉ TEMPORELLE moyenne pire cas bulles O(n ) O(n ) par sélection O(n ) O(n ) insertion O(n ) O(n ) de Shell O(n 1.5 ) quick sort O(n log n) O(n ) par tas O(n log n) O(n log n) optimum théorique O(n log n) O(n log n) Mais en pratique : à partir de quelle taille les méthodes simples deviennent-elle vraiement plus mauvaises que les méthodes sophistiquées (quick sort ou tri par tas)? Informatique I Cours1 : S.D.A. & Tris 44 Informatique I Cours1 : S.D.A. & Tris 45
Conclusions sur les tris : comparaison () Cela dépend de nombreux facteurs, mais en général on peut dire que pour moins d une centaine d éléments les tris sophisitiqués n en valent pas la peine. Par ailleurs, Expérimentalement le quick sort est a 3 fois plus rapide que le tris par tas Ce que j ai appris aujourd hui les bases de la formalisation des données : les structures de données abstraites les deux structures de données abstraites les plus utilisées en informatique (en plus des tableaux et des types élémentaires) : les listes chaînées et les piles introduction aux algorithmes de tri Dans le cas de liste presque triées, les tris par insertion sont efficaces Le tri bulles, très simple à écrire, est le moins bon des tris : à proscrire (sauf à des fins pédagogiques) Informatique I Cours1 : S.D.A. & Tris 4 Informatique I Cours1 : S.D.A. & Tris 47