1 / 58

Structure de données: Liste

Structure de données: Liste. IFT1025, Programmation 2 Jian-Yun Nie. Points importants. Utilité d’une structure de données Comment utiliser une structure de données de liste existante Comment programmer pour gérer soi-même une liste

duncan
Télécharger la présentation

Structure de données: Liste

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Structure de données: Liste IFT1025, Programmation 2 Jian-Yun Nie

  2. Points importants • Utilité d’une structure de données • Comment utiliser une structure de données de liste existante • Comment programmer pour gérer soi-même une liste • D’autres structures souvent utilisées en informatique (arbre, pile, …)

  3. Pourquoi une structure de données? • Structure de données: • Une organisation des informations • Pour regrouper une série de données de nature similaire (tableau, liste, …) • Pour mieux regrouper les informations pour le même concept (classe, …) • Traditionnellement • Structure de données • Traitement • Maintenant • Classe: structure de données et traitements

  4. Structures de données déjà vues • Tableau (Array): pour organiser une séquence de données • int [ ] a = new int [10]; • Similairement: String • Classe: pour regrouper les attributs du même concept public class C { int a; String name; } C ref = new C(); a ref int a: String name: 0 null

  5. Liste • Utilité: pour organiser une séquence de données • Une structure plus flexible que tableau

  6. Tête A B C D Illustration • Entête (Tête): réfère au premier noeud • noeuds enchaînés • Chaque noeud enchaîné: • Valeur stockée (A, B, …) • Référence (lien, pointeur) vers le prochain nœud

  7. Structure de données: Liste • Deux parties: • Structure pour l’entête • Référence vers le premier nœud • D’autres attributs optionnels • Référence vers le dernier nœud • Nombre de nœuds • … • Structure pour nœud • Structure (ou classe) pour le stockage d’une valeur • Référence vers le prochain nœud • Optionnel: • Référence vers le nœud précédent (liste doublement chaînée)

  8. Définition d’une liste simplement chaînée • Entête contenant une référence public class Liste { Noeud premier; // méthodes pour traiter une liste } • Nœud contenant un int public class Noeud { int valeur; Noeud prochain; public Noeud (int v, Noeud p) { valeur = v; prochain = p; } // méthodes pour traiter un nœud }

  9. Création • Créer une entête: Liste l = new Liste(); • Créer des nœuds: l.premier = new Noeud(1, new Noeud(2, new Noeud(3,null))); null null

  10. Traverser une liste (tout se fait dans Liste) public class Liste { … public void print() { Noeud n = premier; while (n!=null) { System.out.print(n.valeur + "->"); n = n.prochain; } System.out.println("null"); } … } 1->2->3->null

  11. Trouver un élément public class Liste { … public Noeud trouver(int v) { Noeud n = premier; while (n != null && n.valeur != v) n = n.prochain; return n; } … }

  12. Déterminer la longueur public class Liste { … public int longueur() { Noeud n = premier; int nb=0; if (premier == null) return 0; while (n != null) { nb++; n = n.prochain; } return nb; } … }

  13. Ajouter un élément au début public class Liste { … public void ajoutDebut(int v) { premier = new Noeud(v, premier); } … } null

  14. Ajouter un élément à la fin public class Liste { … public void ajoutFin(int v) { Noeud n = premier; // cas 1: aucun autre élément ajouté if (premier == null) premier = new Noeud(v,null); // cas 2: il y a déjà des éléments else { while (n.prochain != null) n = n.prochain; n.prochain = new Noeud(v,null); } } … } • Attention: tester sur le prochain • Pas de limite de taille null n

  15. Enlever un élément • Besoin de garder la référence sur le précédent élément • Tester sur le prochain public class Liste { … public void enlever(int v) { Noeud n = premier; // cas 1: Si le premier est null if (premier == null) return; // cas 2: Si le premier est à enlever if (premier.valeur == v) { premier = premier.prochain; return; // On pourrait aussi vouloir retourner le neoud enlevé } // cas 3: sinon, tester sur les autres éléments while (n.prochain != null && n.prochain.valeur != v) n = n.prochain; if (n.prochain != null) n.prochain = n.prochain.prochain; } … } • Pas besoin de déplacer les autres éléments pour laisser une place (comme tableau) Attention à l’ordre des tests

  16. Concatenation de 2 listes public class Liste { … public void concat(Liste l) { if (premier == null) { premier = l.premier; return; } Noeud n = premier; while (n.prochain != null) n=n.prochain; n.prochain = l.premier; } … } // Aller à la fin

  17. Traitement récursif • Itératif: Liste = suite d’éléments • Traitement typique: parcourir la liste avec while • Récursif: Liste = un élément + reste (une plus petite liste) • Traitement récursif • Traiter un élément • Traiter le reste par un appel récursif • Décomposition générale • Cas 1: liste vide ou un seul élément (cas d’arrêt de récursion) • Cas 2: liste non vide • L’élément courant • Appel récursif pour la suite

  18. Déterminer la longueur(Deux niveaux de traitement) • Longueur = • 1, si la liste contient un seul élément • 1 + longueur du reste, sinon. public class Liste { Noeud premier; public int longueur() { if (premier == null) return 0; else return premier.longueur(); } } public class Nœud // Premier option: utiliser la récursion sur les noeuds { public int longueur() { // cas 1: pas de récursion if (prochain==null) return 1; // cas 2: récursion else return 1+ prochain.longueur(); } } Cet appel est possible seulement quand premier!=null

  19. Structure générale • Class Liste: • public int longueur() { appel à la méthode de Nœud; } • Class Nœud: • public int longueur() { • Cas simple: retourner 1 • Cas complexe: 1 + appel récursif }

  20. Déterminer la longueur (bis) public class Liste { Noeud premier; public int longueur() { return longueur(premier); } // Deuxième option: utiliser la récursion dans Liste, avec nœud comme paramètre public int longueur(Noeud n) { if (n==null) return 0; else return 1+ longueur(n.prochain); } } Cet appel est possible même si premier==null

  21. Structure générale • Classe Liste: • public int longueur() { appel à une méthode avec Nœud comme paramètre; } • public int longueur(Nœud n) { déterminer la longueur à partir du nœud n }

  22. Ajouter à la fin public class Liste { … public void ajoutFin(int v) { // cas 1: aucun autre élément ajouté if (premier == null) premier = new Noeud(v,null); // cas 2: il y a déjà des éléments else premier.ajoutFin(v); } } public class Noeud { public void ajoutFin(int v) { if (prochain == null) prochain = new Noeud(v,null); else prochain.ajoutFin(v); } … } Traiter le cas d’une liste vide Ajouter un élément dans une liste non-vide

  23. Ajouter à la fin (bis) public class Liste { … public void ajoutFin(int v) { // cas 1: aucun autre élément ajouté if (premier == null) premier = new Noeud(v,null); // cas 2: il y a déjà des éléments else ajoutFin(premier, v); } public void ajoutFin(Noeud n, int v) { if (n.prochain == null) n.prochain = new Noeud(v,null); else ajoutFin(n.prochain,v); } … }

  24. Ajouter à la fin (bis-bis) public class Liste { … public void ajoutFin(int v) { premier =ajoutFin(premier, v); } public Noeud ajoutFin(Noeud n, int v) { if (n == null) return new Noeud(v,null); else { n.prochain = ajoutFin(n.prochain,v); return n; } } … }

  25. Réflexions • Les façons récursives d’implanter les autres méthodes: • Enlever un élément • Ajouter au début • Insérer dans l’ordre • Inverser la liste • Concaténer deux liste • Obtenir le i-ième noeud • …

  26. Complexité des opérations • Longueur: O(n) • Trouver un élément: O(n) • Enlever un élément: O(n) • Ajouter au début: O(1) • Ajouter à la fin: O(n) • Améliorable ?

  27. Liste simplement chaînée: amélioration • Ajouter une référence au dernier élément pour faciliter l’ajout à la fin public class Liste { Noeud premier; Noeud dernier; public void ajoutFin(int v) { if (premier==null) premier = dernier = new Noeud(v,null); else { dernier.prochain = new new Noeud(v,null); dernier = dernier.prochain; } } … }

  28. Liste doublement chaînée • Référence vers le prochain nœud • Référence vers le précédent nœud (permettre de reculer) public class Noeud { int valeur; Noeud prochain; Noeud precedent; … }

  29. Exemple: Enlever public class Liste { public void enlever(int v) { Noeud n = premier; if (premier == null) return; if (premier.valeur == v) { premier = premier.prochain; if (premier==null) dernier = null; return; } while (n != null && n.valeur != v) n = n.prochain; if (n != null) { n.precedent.prochain = n.prochain; n.prochain.precedent = n.predcedent; } } null n

  30. Généralisation • Définir un nœud pour contenir tout Object public class Noeud { Object element; Noeud prochain; Noeud precedent; … }

  31. Réflexion • Adapter les traitements à cette structure générale avec Object • Comment faire pour pouvoir trier une liste?

  32. Tableau v.s. Liste • Tableau (array) • Taille fixe • Accès rapide avec index (O(1)) • Ajouter/enlever un élément: plus difficile (O(n)) • À utiliser si • Beaucoup d’accès aléatoire • Pas besoin de modifier souvent l’ordre des éléments • Nombre d’éléments à stocker déterminée (ou une limite existe) • Liste • Taille flexible • Accès plus lent (O(n)) • Ajouter/enlever un élément: plus facile (O(1)) • À utiliser si • Peu d’accès aléatoire (souvent un parcours de tous les éléments) • Nombre d’élément très variable • Éléments sont souvent re-ordonnés, ajoutés ou enlevés

  33. Allocation de mémoire • La mémoire est allouée quand on crée un nœud • Les nœuds enlevés ne sont plus utilisés • Gabbage collector: récupère les mémoiresqui ne sont plus utilisées • Pas besoin de gérer l’allocation et désallocation de mémoire en Java • On ne peut pas contrôler quand le gabbage collector sera lancé (dépend de l’implantation de JVM) • finalize(): la méthode héritée de Object, mais qu’on peut remplacer • Définir des opérations à effectuer quand le gabbage collector va récupérer cette mémoire. • Ne permet pas d’évoquer le gabbage collector • À utiliser dans certains cas spéciaux (e.g. pour évoquer delete d’un objet en C++, utilisé en Java)

  34. De l’implantation vers un type abstrait • Implantation de Liste pour les éléments contenant int, Object, etc. • Généralisation • Définir les opérations sur la liste pour tout type de données • Les opérations communes sur une liste: (interface List) • boolean add(Object o): Ajouter à la fin • void add(int index, Object o): Ajouter à une position • void clear(): Enlever tout • boolean contains(Object o): Contanir un élément? • boolean isEmpty(): Vide? • boolean remove(Object o): Enlever le premier o • int size(): nombre d’éléments • … • Iterator irerator(): Iterator – permet de parcourir les éléments

  35. Implantation avec une liste chaînée • LinkedList public class LinkedList<E> extends AbstractList<E> { … private class Node { private E element; private Node next; // Create a Node containing specified element. public Node (E element) { this.element = element; this.next = null; } } // end of class Node } // end of class LinkedList

  36. Implantation LinkedList public class LinkedList<E> extends AbstractList<E> { private int size; private Node first; // Create an empty LinkedList<E>. public LinkedList () { size = 0; first = null; } … }

  37. Implantation LinkedList • Une méthode interne pour obtenir le i-ième élément /** * The i-th node of this LinkedList. * The LinkedList must be non-empty. * require 0 <= i && i < this.size() */ private Node getNode (int i) { Node p = first; int pos = 0; // p is pos-th Node while (pos != i) { p = p.next; pos = pos + 1; } return p; } public E get (int index) { Node p = getNode(index); return p.element; }

  38. Implantation LinkedList • Enlever le i-ième élément public void remove (int index) { if (index == 0) first = first.next; else { Node p = getNode(index-1); p.next = p.next.next; } size = size - 1; }

  39. Implantation avec tableau? • ArrayList: implante l’interface List avec un tableau • Possède une taille limite, mais est agrandi automatiquement de 50% au besoin • Accès rapide avec un indexe • Ajouter un élément: O(n) pour déplacer les éléments

  40. Méthodes de ArrayList • void add(int index, Object o) • boolean add(Object o): ajouter à la fin • void clear() • boolean contains(Object o) • void ensureCapacity(int minCapacity): assurer qu’il y a des places nécessaires • boolean isEmpty() • Object remove(int index) • Object set(int index, Object o) • int size(): numbre d’éléments dans la liste • void trimtosize(): enlever les espaces non utilisé • …

  41. Opération de tri sur une liste public class Liste { Noeud premier, dernier; public void concatener(Liste l) {…} // à réfléchir public void quicksort() { if (premier == null) return; Liste l1 = new Liste(); Liste l2 = new Liste(); this.separer(premier.valeur, l1, l2); l1.quicksort(); l2.quicksort(); l1.concatener(l2); premier = l1.premier; } …

  42. Opération de tri sur une liste private separer(int pivot, Liste l1, Liste l2) { Noeud n = premier; while (n!=null) { if (n.valeur<=pivot) l1.ajoutFin(n.valeur); else l2.ajoutFin(n.valeur); } • À réfléchir: généralisation - trier une liste contenant des éléments Comparable

  43. Iterator • Permet de parcourir sur les éléments d’une liste (ou de n’importe quel ensemble) • Référence courant: Position courante (initialisée au premier) • Utilisation: LinkedList list; … Iterator iter = list.iterator(); while (iter.hasNext()) { Object obj = iter.next(); // do something with obj }

  44. Interface Interator • boolean hasNext() • Object next(): retourner l’élément courant, et avancer la référence courante au prochain • void remove(): enlever l’élément courant, la référence courante réfère au prochain élément

  45. Implanter Iterator pour Liste (ex.) public class Liste { Noeud premier, dernier, courant; public ListeIterator iterator() { returnnew ListeIterator(this); } } publicclass ListeIterator implements Iterator { private Liste liste; private Noeud courant; public ListeIterator(Liste liste) { this.liste= liste; courant=liste.premier; } publicboolean hasNext() { return courant != null} public Object next() { return courant; if (hasNext()) courant = courant.prochain; } publicvoid remove() { thrownew UnsupportedOperationException(); } } Liste liste = ...; for (Iterator i= liste.iterator(); i.hasNext(); ) { Object obj= i.next(); // do something with obj }

  46. D’autres types de données • Pile (stack): premier entré, dernier sorti • Empiler un élément • Dépiler un élément • Test isEmpty() • Queue: premier entré, premier sorti • Mettre dans la queue (enqueue) • Enlever de la queue (dequeue) • Test isEmpty() • Implantations (à réfléchir) • Avec tableau (circulaire) • Avec liste

  47. Arbre de recherche binaire • Arbre: • Racine • Enfants • Chaque nœud a un parent au plus (0 parent pour racine) • Arbre binaire: • Un parent peut avoir 2 enfants au plus

  48. Arbre de recherche binaire • Arbre de recherche binaire: • Organiser les nœuds selon leurs valeurs • Branche gauche: valeurs <= valeur du parent • Branche gauche: valeurs > valeur du parent 7 5 9 3 7 10

  49. Pourquoi arbre de recherche binaire? • Recherche dans une liste: O(n) • Recherche dans un arbre de recherche binaire, selon une valeur v: • Valeur de racine r = v? • Sinon: • Si v < r, chercher à gauche; • Sinon, chercher à droite • Si l’arbre est balancé: O(log n)

  50. Implantation en Java • class Noeud { int valeur; Noeud gauche, droite; public Noeud(int v, Noeud g, Noeud d) { valeur = v; gauche = g; droire = d; } } • class Arbre { Noeud racine; public void add(int v) { … } }

More Related