Algorithmique et structures de données I Frédéric Bapst • Algorithme = enchaînement d'opérations destiné à résoudre un problème • Structure de données = façon d'organiser des informations pour en faciliter la manipulation Survol de la matière • Notion d'analyse de complexité algorithmique • Techniques de programmation (récursivité, chaînage…) • Algorithmes classiques (tris…) • Structures de données du langage (tableaux…) • Structures de données abstraites (piles, listes…) • Applications (cryptographie, mots croisés...) • Métier de programmeur (tests, traitement d'erreurs, assertions...) • Langage de programmation utilisé dans le cours : Java -1- Une distinction très importante Spécification • Sorte de "mode d'emploi" d'une méthode, d'une classe... • Spécification = + signature : entêtes java, type des paramètres... explications sur l'effet attendu • Le choix des identificateurs, commentaires • Il peut y avoir des mises en garde : "il est interdit de..." • Les explications peuvent être des commentaires Java, ou un document séparé (guide du programmeur) • Analogie : définir une perceuse, les commandes/boutons qu'elle accepte Implémentation • Réaliser le codage, écrire les instructions nécessaires pour que la méthode/classe fasse son travail • Analogie: fabriquer l'intérieur de la perceuse (moteur, circuits électroniques...) Utilisation • Employer la méthode/classe pour réaliser un autre programme en respectant scrupuleusement la spécification, et sans avoir besoin de connaître l'implémentation • Analogie : utiliser la perceuse (percer, visser, fraiser, poncer, autres usages...) -2- Exemple • Spécification public static double max(double[] t); // --- computes the maximum value in the array t // --- the array must have at least one element • Implémentation public static double max(double[] t) { double m = t[0]; for(int i=1; i<t.length; i++) if (t[i] > m) m = t[i]; return m; } • Utilisation static double maxInBoth(double[] a, double[] b) { double ma, mb; ma = max(a); mb = max(b); if (ma>mb) return ma; return mb; } -3- Type abstrait • Structure de données : façon de représenter des données, et description des opérations autorisées sur ces données • Notamment pour stocker une collection d'éléments. Opérations typiques : - ajouter un élément - rechercher un élément - enlever un élément • Exemple de structure de données prévue par Java : le tableau. Opérations : - construire un tableau à n cases - lire la case numéro i - écrire dans la case numéro i O(n) O(1) O(1) temps d'accès constant ! • Type abstrait : structure de données qu'on ne définit que par ses opérations (c.-à-d. sans référence aux détails de la représentation) • On utilise souvent "type abstrait" et "structure de données" comme synonymes • 3 aspects : définition implémentation utilisation • Implémentation : serait souvent facile, si on ne cherchait pas l'efficacité ! Minimiser l'ordre de grandeur de la complexité algorithmique des opérations -4- Exemple de type abstrait : la Pile • Collection d'éléments • Modèle d'organisation des données : Last In First Out (LIFO) • Importante propriété : la pile peut être vide push • Spécification en Java : public class CharStack { public CharStack(); public CharStack(int estSize); public boolean isEmpty(); public void push(char x); public char top(); public char pop(); // // // // // // // pop unknown capacity expected capacity returns true if empty adds x as the most recent elt gives the most recent elt gives and removes the most recent element } • Il y a des préconditions : il est interdit de faire pop() si isEmpty() est vrai (le comportement n'est pas défini, "à vos risques et périls") • On utilise toujours les identificateurs push() et pop(), parfois peek() pour top() -5- Utilisation : Exemple d'application • Problème : vérifier qu'une chaîne "avec parenthèses" est balancée Exemple : "aaa(aa(aaa[aaa{{(aaa)aaaa}aaa}]aaaa))a" • Algorithme en pseudo-code: pour chaque caractère x de la chaîne { si x est une des parenthèses ouvrantes, empiler x si x est une des parenthèses fermantes (*)dépiler un caractère et vérifier la correspondance • En (*), la pile doit être non-vide, et à la fin, elle doit être vide • Codage en Java : public static boolean isBalanced(String a) { CharStack s = new CharStack(); ... // cf. série d'exercice ! } • La pile est une structure de données essentielle. Par exemple, la machine virtuelle Java utilise une pile pour les appels de méthodes p() ; q() ; r() ; -6- ... { [ ( ( Implémentation à l'aide d'un tableau • Il nous faut : - 1 tableau buffer - 1 entier topIndex topIndex 0 1 2 3 4 5 a b c pour stocker les éléments pour désigner l'indice du sommet de la pile push(e) buffer • Cas de la pile vide : (tenter d'éviter les cas particuliers !) topIndex 0 1 2 3 4 5 e buffer topIndex -1 0 1 2 3 4 5 buffer • Codage en Java (cf. série d'exercice !): public class CharStack { private ... // décl. des attributs public CharStack() {...} // impl. des constructeurs public void push(char e);// impl. des autres méthodes } • Quelle taille pour le tableau ? On a le choix entre plusieurs approches : - taille indiquée lors de la création ou taille par défaut ? - en cas de dépassement, lever une exception ou redimensionner ? • Complexité des opérations : push, pop, top, isEmpty demande un nombre constant d'opérations, quelle que soit la taille de la pile. On dit que c'est en O(1) -7-