Structure de donnes Liste IFT 1025 Programmation 2

  • Slides: 41
Download presentation
Structure de données: Liste IFT 1025, Programmation 2 Jian-Yun Nie

Structure de données: Liste IFT 1025, Programmation 2 Jian-Yun Nie

Points importants • Utilité d’une structure de données • Comment utiliser une structure de

Points importants • Utilité d’une structure de données • Comment utiliser une structure de données de liste existante • Comment programmer pour gérer soimême une liste • D’autres structures souvent utilisées en informatique

Pourquoi une structure de données? • Structure de données: – Une organisation des informations

Pourquoi une structure de données? • Structure de données: – Une organisation des informations – Pour faciliter le traitement efficace (tableau, liste, …) – Pour mieux regrouper les informations pour le même concept (classe, …)

Structures de données déjà vues • Tableau: pour organiser une séquence de données –

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

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

Liste • Utilité: pour organiser une séquence de données • Une structure plus flexible que tableau Taille fixe Ordre entre éléments Insertion et enlèvement difficile Liste Taille variable Ordre Plus facile Accès rapide Plus lent

Illustration • Entête (node) • noeuds enchaînés node A B C • Chaque noeud

Illustration • Entête (node) • noeuds enchaînés node A B C • Chaque noeud enchaîné: – Valeur stockée (A, B, …) – Référence vers le prochain nœud D

Structure de données: Liste • Deux parties: – Structure pour l’entête • Référence vers

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 • Facultatif: – Référence vers le nœud précédent (liste doublement chaînée)

Définition d’une liste simplement chaînée • Entête contenant une référence public class Liste {

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; } }

Création • Créer une entête: Liste l = new Liste(); l null • Créer

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

Traverser une liste public class Liste { … public void print() { Noeud n

Traverser une 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"); } … Noeud premier; } Résultat: 1 ->2 ->3 ->null

Trouver un élément public class Liste { … public Noeud trouver(int v) { Noeud

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; } … Noeud premier; }

Déterminer la longueur public class Liste { … public int longueur() { Noeud n

Déterminer la longueur public class Liste { … public int longueur() { Noeud n = premier; int nb=0; if (premier == null) return 0; // cette ligne peut être enlevée while (n != null) { nb++; n = n. prochain; } return nb; } … Noeud premier; }

Ajouter un élément au début public class Liste { … public void ajout. Debut(int

Ajouter un élément au début public class Liste { … public void ajout. Debut(int v) { premier = new Noeud(v, premier); } … } l 1 5 2 3 null

Ajouter un élément à la fin public class Liste { … public void ajout.

Ajouter un élément à la fin public class Liste { … public void ajout. Fin(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 l 1 2 null 3 n 5

Enlever un élément • Besoin de garder la référence sur le précédent élément •

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; Attention à l’ordre des tests return; } // 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; } … } • Pas besoin de déplacer les autres éléments pour laisser une place (comme tableau)

Concatenation de 2 listes public class Liste { … public void concat(Liste l) {

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

Et si? • Si on a une réf. vers le dernier élément? public class

Et si? • Si on a une réf. vers le dernier élément? public class Liste { Noeud premier, dernier; // méthodes pour traiter une liste? } • Exemple: public void ajout. Fin(int v) { Noeud n = premier; // cas 1: aucun autre élément ajouté if (premier == null) premier = dernier = new Noeud(v, null); // cas 2: il y a déjà des éléments else { dernier. prochain = new Noeud(v, null); dernier = dernier. prochain; } }

Traitement récursif • Itératif: Liste = suite d’éléments – Traitement typique: parcourir la liste

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 (cas d’arrêt de récursion) – Cas 2: liste non vide • L’élément courant • Appel récursif pour la suite

Déterminer la longueur • Longueur = – 0, si la liste est vide –

Déterminer la longueur • Longueur = – 0, si la liste est vide – 1 + longueur du reste, sinon. public class Liste { Noeud premier; public int longueur() { if (premier == null) return 0; Cet appel est possible seulement else return premier. longueur(); quand premier!=null } } 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(); } }

Illustration l 1 premier. longueur()= = 3 1 + ? 2 2 longueur()= 1

Illustration l 1 premier. longueur()= = 3 1 + ? 2 2 longueur()= 1 + ? 1 public class Nœud { public int longueur() { // cas 1: pas de récursion if (prochain==null) return 1; // cas 2: récursion else return 1+ prochain. longueur(); } } 3 longueur()= 1 null

Déterminer la longueur (bis) public class Liste { Noeud premier; public int longueur() {

Déterminer la longueur (bis) public class Liste { Noeud premier; public int longueur() { return longueur(premier); } Cet appel est possible même si premier==null // 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); } }

Ajouter à la fin public class Liste { … public void ajout. Fin(int v)

Ajouter à la fin public class Liste { … public void ajout. Fin(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. ajout. Fin(v); } } public class Noeud { public void ajout. Fin(int v) { if (prochain == null) prochain = new Noeud(v, null); else prochain. ajout. Fin(v); } … } Traiter le cas d’une liste vide Ajouter un élément dans une liste nonvide

Ajouter à la fin (bis) public class Liste { … public void ajout. Fin(int

Ajouter à la fin (bis) public class Liste { … public void ajout. Fin(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 ajout. Fin(premier, v); } public void ajout. Fin(Noeud n, int v) //même nom: ça fonctionne, //mais déconseillé { if (n. prochain == null) n. prochain = new Noeud(v, null); else ajout. Fin(n. prochain, v); } … }

Ajouter à la fin (bis-bis) public class Liste { … public void ajout. Fin(int

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

Réflexions • Les façons récursives d’implanter les autres méthodes: – Enlever un élément –

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 –…

Notion de complexité asymptotique • Ne pas mesurer le traitement en temps réel sur

Notion de complexité asymptotique • Ne pas mesurer le traitement en temps réel sur un ordinateur (dépendant de l’ordinateur) • Déterminer une expression de complexité en fonction du nombre d’éléments à traiter (n) • Notation grand O: – Une approximation – Ignore les constantes: • O(n+3) = O(n) • O(4 n) = O(n), O(10) = O(1) – Déterminer par le plus grand facteur • O(n+n 2) = O(n 2) (car n 2 > n) • O(n 2+n log n) = O(n 2) (car n 2 > n log n)

Complexité des opérations • Longueur: O(n) – n éléments déjà dans la liste •

Complexité des opérations • Longueur: O(n) – n éléments déjà dans la liste • Trouver un élément: O(n) – En moyenne O(n/2)=O(n) • Enlever un élément: O(n) – En moyenne O(n/2) • Ajouter au début: O(1) – Quelques opérations – nombre constante • Ajouter à la fin: O(n) – Aller à la fin, et ajouter – Améliorable ?

Liste simplement chaînée: amélioration • Ajouter une référence au dernier élément public class Liste

Liste simplement chaînée: amélioration • Ajouter une référence au dernier élément public class Liste { Noeud premier; Noeud dernier; public void ajout. Fin(int v) { if (premier==null) premier = dernier = new Noeud(v, null); else { dernier. prochain = new Noeud(v, null); dernier = dernier. prochain; } } … } O(n) O(1)

Liste doublement chaînée • Référence vers le prochain nœud • Référence vers le précédent

Liste doublement chaînée • Référence vers le prochain nœud • Référence vers le précédent nœud public class Noeud { int valeur; Noeud prochain; Noeud precedent; … } • Permet d’avancer et reculer dans la liste

Exemple: Enlever public class Liste { public void enlever(int v) { Noeud n =

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; if (n. prochain != null) n. prochain. precedent = n. predcedent; } } 1 2 n 3 null

Généralisation • Définir un nœud pour contenir tout Object public class Noeud { Object

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

Réflexion • Adapter les traitements à cette structure générale avec Object

Réflexion • Adapter les traitements à cette structure générale avec Object

Tableau v. s. Liste • Tableau (array) – – Taille fixe Accès rapide avec

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

Allocation de mémoire • La mémoire est allouée quand on crée un nœud •

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émoires qui ne sont plus utilisées – Pas besoin de gérer l’allocation et désallocation de mémoire en Java

De l’implantation vers un type abstrait • Implantation de Liste pour les éléments contenant

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) • • • Ajouter à la fin: boolean add(Object o) Ajouter à une position: void add(int index, Object o) Enlever tout: void clear() Contanir un élément? Boolean contains(Object o) Vide? : boolean empty() Enlever le premier o: boolean remove(Object o) Taille: int size() … Iterator: Iterator irerator()

Implantation avec une liste chaînée • Linked. List public class Linked. List<E> extends Abstract.

Implantation avec une liste chaînée • Linked. List public class Linked. List<E> extends Abstract. List<E> { … private class Node { private E element; private Node next; // an inner class only used in Linked. List // Create a Node containing specified element. public Node (E element) { this. element = element; this. next = null; } } // end of class Node } // end of class Linked. List

Implantation Linked. List (cont. ) public class Linked. List<E> extends Abstract. List<E> { private

Implantation Linked. List (cont. ) public class Linked. List<E> extends Abstract. List<E> { private int size; private Node first; // Create an empty Linked. List<E>. public Linked. List () { size = 0; first = null; } … }

Implantation Linked. List (cont. ) • Une méthode interne pour obtenir le i-ième élément

Implantation Linked. List (cont. ) • Une méthode interne pour obtenir le i-ième élément /** * The i-th node of this Linked. List. * The Linked. List must be non-empty. * require 0 <= i && i < this. size() */ private Node get. Node (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 = get. Node(index); return p. element; } Dans Linked. List: abstract Object get(int index)

Implantation Linked. List • Enlever le i-ième élément public void remove (int index) {

Implantation Linked. List • Enlever le i-ième élément public void remove (int index) { if (index == 0) if (first!==null) {first = first. get. Next(); size--; } else { Node p = get. Node(index-1); if (p!==null) {p. set. Next(p. get. Next()); size--; } } }

Implantation avec tableau? • À réflechir • Exemple: – Création: créer un tableau de

Implantation avec tableau? • À réflechir • Exemple: – Création: créer un tableau de 10 éléments (à agrandir si nécessaire) – Chercher le i-ième élément (si i<longueur, retourner tab[i], sinon null) – Ajouter à la position courante – Ajouter à la fin (ajouter à tab[longueur], longueur++) – Insérer à la i-ième place (déplacer i: fin, ajouter à i) –…

Exemple public class Array. List { public Array. List() { tab = new Object[10];

Exemple public class Array. List { public Array. List() { tab = new Object[10]; position = 0; size = 0; //nombre d’éléments capacity = 10; //taille de tableau } public void add(Object o) { if (position >= capacity) { …} // doubler la taille tab[position] = o; position++; size++; } … private int size, capacity; private Object[] tab; private int position; }