ETNA – 1ème année Guillaume Belmas – guillaume@3ie.org Algorithmique ETNA – 1ème année Guillaume Belmas – guillaume@3ie.org
Plan de cours Cours 1: Listes chaînées, piles et files Cours 2: Arbres, tables de hash Cours 3: Graphes Cours 3 bis: Coding guidelines Cours 4: Modélisation objet: rappels Cours 5: Design patterns 1 Cours 6: Design patterns 2
Listes chaînées, piles et files
Rappels Analyse d’algorithme Analyse pessimiste des performances Le pire cas d’une recherche arrive souvent ! Le cas optimal ne nous apprend rien Le cas moyen est difficile à obtenir Le pire cas garantit que l’algorithme ne fera jamais moins bien
Rappels Complexité d’un algorithme Somme des coûts des actions élémentaires S’exprime en fonction du volume de données à traiter Complexité != Temps d’exécution
Rappels O(1) = Exécution en temps constant (le mieux !) O(n) = Parcours d’une liste de n éléments O(n2) = Parcours d’une matrice Plus la complexité est élevée, moins l’algorithme est efficace pour un grand nombre de données
Rappels Les structures (agrégats) Éléments de différents types comme type unique Utile pour représenter les données métier typedef struct s_personne { char *Nom, int Age, char Sexe } t_personne;
Les listes chaînées
Les listes chaînées Définition Ensemble d’éléments de même type liés par des pointeurs 1 élément = 2 parties (agrégat) Le champ de données Le pointeur suivant Données Élément suivant
Les listes chaînées Définition récursive Une liste est: Soit une liste vide (représentée par le NULL) Soit un élément suivit d’une liste Données Élément suivant Tête de liste
Les opérations de base Déclaration de la structure d’un élément struct s_list { struct s_list *suivant; void *donnee; }; typedef struct s_list t_list; Nouvelle liste t_list *liste = (t_list)malloc(sizeof(t_list)); liste->suivant = null; liste->donnee = …
Les opérations de base Insertion d’un élément Prototype: int list_ins_next(t_list **list, t_list *element, void *donnee) list : la liste de destination element: élément qui précédera le nouvel élément donnee: données du nouvel élément Complexité d’exécution O(1) pour un ajout en tête de liste Éventuellement O(n) pour un ajout avec recherche
Les opérations de base
Les opérations de base int list_ins_next(t_list **liste, t_list *element, const void *donnee) { t_list *nouv_element; if ((nouv_element = (t_list *)malloc(sizeof(t_list))) == NULL) return -1; nouv_element->donnee = (void *)donnee; if (element == NULL) // Insertion en tête de liste nouv_element->suivant = *liste; *liste = nouv_element; }
Les opérations de base else { nouv_element->suivant = element->suivant; element->suivant = nouv_element; } return 0;
Les opérations de base Suppression d’un élément Prototype: int list_rem_next(t_list **list, t_list *element, void **donnee) list : la liste de destination element: élément qui précédera le nouvel élément donnee: données du nouvel élément Complexité d’exécution O(1) pour un ajout en tête de liste Éventuellement O(n) pour un ajout avec recherche
Les opérations de base
Les opérations de base int list_rem_next(t_list **liste, t_list *element, void **donnee) { t_list *ancien_element; if (list_size(*liste) == 0) return -1; if (element == NULL) //Gestion de la suppression en tête de liste *donnee = *liste->donnee; ancien_element = *liste; *liste = *liste->suivant; }
Les opérations de base else { if (element->suivant == NULL) return -1; *donnee = element->suivant->donnee; ancien_element = element->suivant; element->suivant = element->suivant->suivant; } free(ancien_element); return 0;
Quizz A vous de jouer ! Donnez les 2 implémentations de list_size - Itérative - Récursive int list_size(t_list *liste)
Solutions Implémentation itérative int list_size(t_list *liste) { int count = 0; t_list *curseur = liste; if (curseur == NULL) return count; while (curseur->suivant != NULL) count++; curseur = curseur->suivant; }
Solutions Implémentation récursive int list_size(t_list *liste) { if (liste == NULL) return 0; else return (1 + list_size(liste->suivant)); }
Avantages Allocation dynamique de la mémoire à l’éxecution Pas besoin de connaître le volume de données lors du design de l’application Faible coût de l’insertion et de la suppression Complexité O(1) des opération d’ajout et de suppression
Inconvénients Gestion des données relativement lourde Déréférencement des pointeurs Nécessité d’avoir un pointeur par donnée Mal adapté aux parcours Pas d’accès direct aux données Parcours lent du au déréférencement
Listes chaînées VS tableaux
Les autres types de liste Listes doublement chaînées Pointeur suivant et precedent Traitement +/- lourd mais la complexité ne change pas Listes circulaires Le dernier élément pointe sur le premier Listes custom Primitives optimisées en fonction des besoins
Les piles (stacks) Stockage et restitution de l’information selon l’ordre LIFO (Last In First Out). Gestion simple avec peu de primitive « Empiler » une donnée (push) « Dépiler » une donnée (pop) Et les primitives de création et destruction
Fonctionnement
Implémentation Pas besoin d’accès direct aux données Structure sollicitant l’ajout et la suppression de données Les listes sont donc adaptées pour servir de support aux piles !
Implémentation Push (empiler) == Ajouter en début de liste Pop (dépiler) == suppression du 1° élément
Les files (queue) Stockage et restitution de l’information selon l’ordre FIFO (First In First Out). Tout comme les piles, peu de primitives: « Enfiler » (équivalent du push) « Défiler » (équivalent du pop)
Fonctionnement
Implémentation Structure très similaire aux piles Plusieurs possibilités d’implémentation Utilisation de tableaux (limite la taille de la file) Utilisation des listes chaînées Liste à 2 pointeurs (Tête et Queue)
Implémentation Enfiler() == Ajouter en FIN de liste Défiler() == Suppression du 1° élément Exemple d’utilisation Gestion des évènements dans un OS
Conclusion « L’emploi de ces structures dépend essentiellement du besoin de l’application. »
Questions