INF145 : Dixième cours Les Structures de Données : - Définition : o Organisation de l’information permettant d’améliorer l’efficacité d’un algorithme. Une telle structure peut contenir des informations redondantes - Caractéristiques les plus courantes : o Vitesse d’ajout, de retrait et de consultation ; o Espace mémoire occupée (y a t’il des métadonnées, est-ce que si la structure est vide celle-ci occupe quand même beaucoup de mémoire ?) ; o Flexibilité (la taille est-elle limitée par une constante à la compilation ?). Les Listes Chaînées Simples : - Définition : Structure de données où les objets sont arrangés de façon linéaire. À la différence d’un tableau, où l’ordre linéaire est déterminé par l’indice des éléments, l’ordre dans une liste chaînée est déterminé par un pointeur dans chacun des objets. Les listes chaînées fournissent une représentation simple et flexible pour implanter des ensembles dynamiques et supportent (bien que pas toujours de façon efficace) les opérations associées à un ensemble dynamique ; Consultez l’exposé powerpoint « Listes.pps ». - Implantation : Un « nœud » dans une liste chaînée est une structure (type struct) qui contient un pointeur à une autre structure du même type, et ainsi de suite : struct noeud { int data; /* Champ de données… */ struct noeud * suivant; }; Ou encore : typedef struct noeud T_noeud; struct noeud { int data; /* Champ de données… */ T_noeud * suivant; /* Pointeur */ }; Déclaration de 3 nœuds de type « T_noeud » : T_noeud a, b, c; 1 Initialisation des données (sans faire les liens!) : a.data = 1; b.data = 2; c.data = 3; a.suivant = b.suivant = c.suivant = NULL; Résultat : a 1 b NULL 2 c NULL 3 NULL La valeur NULL sera la valeur spéciale (=0) qui indiquera la fin d’une liste chaînée. Pour faire la liaison des 3 nœuds ensemble : a.suivant = &b; b.suivant = &c; Résultat : a b 1 c 2 Quelle est la valeur de : 3 NULL a.data ? a.suivant->data ? a.suivant->suivant->data ? Déclarations pour former une liste chaînée simple (ordonnée) : NOTE : Il est préférable d’utiliser typedef pour la déclaration du type des données. typedef int T_objet; typedef struct noeud * T_lien; struct noeud { T_objet data; T_lien suivant; }; /* champ des données */ /* pointeur au nœud suivant */ Le type « struct noeud » sera employé pour faire l’allocation dynamique (avec malloc) de la liste un nœud à la fois. Pour créer une liste vide, nous avons seulement besoin d’un seul pointeur : T_lien tete = NULL; Pour créer le premier nœud de la liste : tete = (T_lien) malloc(sizeof(struct noeud)); tete->data = 2; tete->suivant = NULL; Résultat : tete 2 NULL 2 //1 noeud On ajoute un 2ième nœud (à la fin de la liste) : T_lien tempo = (T_lien) malloc(sizeof(struct noeud)); tempo->data = 4; tempo->suivant = NULL; tete->suivant = tempo; Résultat : tete //faire le lien avec la tête 2 4 NULL (tempo) Ajouter un nœud au début de la liste : Étapes : 1. Créer un nouveau nœud avec « malloc ». Variable « tempo » pointe à ce nouveau nœud ; 2. Remplir « *tempo » avec les nouvelles données (dans : tempo->data ) ; 3. Initialiser « tempo->suivant » pour que ce pointeur pointe à la « tete » ; 4. Faire : « tete = tempo; » pour déplacer la tête de la liste. void inserer_au_debut(T_lien *tete, T_objet x) { T_lien tempo; tempo = (T_lien) malloc(sizeof(struct noeud)); if (tempo == NULL) return; //valider l'allocation dynamique tempo->data = x; tempo->suivant = *tete; *tete = tempo; } appel de cette fonction : inserer_au_debut(&tete, 1); Résultat : tete 1 2 4 NULL (tempo) Insérer un nœud dans la liste (au milieu) : Étapes : insertion après le nœud « Ici ». 1. Localiser l’endroit où on veut insérer le nœud (positionner pointeur « Ici ») ; 2. Créer le nouveau noeud « tempo » avec « malloc » et remplir son contenu ; 3. Initialiser « tempo->suivant = ici->suivant; » ; 4. Initialiser « ici->suivant = tempo; ». ATTENTION : ne PAS faire l’étape 4 avant l’étape 3 !!! 3 void inserer(T_lien *tete, T_objet x) { T_lien ici, next, tempo; tempo = (T_lien) malloc(sizeof(struct noeud)); if (tempo == NULL) return; //valider l'allocation dynamique tempo->data = x; ici = NULL; next = *tete; /* Boucle pour localiser l’endroit où « x » sera insérée */ while ((next != NULL) && (next->data < x)) { ici = next; next = next->suivant; } if (ici == NULL) tempo->suivant *tete = tempo; } else { tempo->suivant ici->suivant = } { //ajouter la valeur «x» au début! = *tete; //insertion du nouveau nœud avec valeur x = ici->suivant; tempo; } appel de cette fonction : inserer(&tete, 3); Résultat : tete 1 2 3 4 (ici) (tempo) (next) Retirer un nœud de la liste : Nous aurons besoin de 2 pointeurs : « ici » et « avant ». Étapes : retrait du nœud qui contient « x ». 1. Faire : ici = *tete; avant = NULL; 2. Tant que (ici ≠ fin de la liste) ET ( *ici ne contient pas « x ») Faire : avant = ici; ici = ici->suivant; 3. Si (ici ≠ NULL) //aller à droite // l’élément « x » à été trouvé Si (ici == *tete), *tete = ici->suivant; Sinon, avant->suivant = ici->suivant; 4. Libérer la mémoire du nœud éliminé : free(ici); 4 NULL Rechercher un élément dans la liste : Étapes : recherche de la valeur « x ». tempo = tete; 1. Initialiser : 2. Tant que (tempo ≠ fin de la liste) ET ( *tempo ne contient pas « x ») Faire : tempo = tempo->suivant; 3. Si (tempo == NULL) //fin de la liste! L’élément n’est pas dans la liste, on renvoie NULL Sinon, on a trouvé l’élément « x », on renvoie un pointeur à ce nœud. En C, sous forme de fonction : T_lien trouver_element(T_lien tete, T_objet x){ T_lien tempo = tete; while ((tempo != NULL) && (tempo->data != x)) tempo = tempo->suivant; return tempo; } Exemple d’appel de la fonction : T_lien ici = trouver_element(tete, 3); if (ici != NULL) { . . . } Intro_Liste.CPP : Exemple de gestion d'une liste chaînée simple #include #include #include #include <stdio.h> <conio.h> <stdlib.h> <time.h> #define MAX 10 /********************************************************************/ /* DEFINITION DES TYPES */ /********************************************************************/ typedef int T_objet; typedef struct noeud * T_lien; struct noeud { T_objet data; T_lien suivant; }; /********************************************************************/ /* Déclarations des fonctions de gestion de liste */ /********************************************************************/ void ajouter(T_lien *, T_lien *, T_objet); int retirer_le_i(const T_lien *, int, T_objet *); void retirer_du_debut(T_lien *); 5 /********************************************************************/ /* PROGRAMME PRINCIPAL */ /********************************************************************/ int main(void) { T_lien tete=NULL, fin; //pointeur sur la tete et la fin de la liste int nbelem=0, //nombre d'éléments de la liste i, valeur; srand(time(NULL)); //remplir la liste aléatoirement for (i=0; i<MAX; i++){ valeur = rand() % 100; ajouter(&tete, &fin, valeur); nbelem++; } //afficher la liste for (i=0; i<nbelem; i++){ retirer_le_i(&tete, i, &valeur); printf("%d --> ", valeur); } printf("NULL\n"); //tester quelques requêtes if (retirer_le_i(&tete, 6, &valeur)) printf("Le #6 est %d\n", valeur); if (retirer_le_i(&tete, 11, &valeur)) printf("Le #11 est %d\n", valeur); //effacer la liste while (nbelem){ retirer_du_debut(&tete); nbelem--; } system("pause"); return 0; } /********************************************************************/ /* DEFINITION DES FONCTIONS */ /********************************************************************/ /********************************************************************/ //Fonction qui crée un nouveau noeud avec la valeur "x" et qui va //placer ce noeud à la fin de la liste reçue en paramètre void ajouter(T_lien *tete, T_lien *fin, T_objet x) { T_lien p, q; p = (T_lien) malloc(sizeof(struct noeud)); if (p == NULL) return; //valider l'allocation dynamique p->data = x; if (*tete == NULL) { *tete = *fin = p; p->suivant = NULL; } //si la liste reçue est vide.. //ce premier noeud est la tete ET la fin de liste 6 else { q = *fin; p->suivant = NULL; q->suivant = p; *fin = p; //sinon, on va ajouter à la fin //déplacer fin de liste } } /********************************************************************/ //Fonction qui enlève le PREMIER noeud de la liste reçue en parametre void retirer_du_debut(T_lien *tete) { T_lien p= *tete; if (*tete != NULL) { *tete = p->suivant; free(p); } } /********************************************************************/ //Fonction qui récupère la valeur du noeud #i de la liste int retirer_le_i(const T_lien *tete, int i, T_objet *val) { T_lien ici = *tete; //pointeur temporaire pour traverser la liste int no=0; if (ici == NULL){ printf("\nOn ne peut pas retire l'objet #%d: la liste est vide\n\n", i); return 0; } else { /* Boucle pour retrouver la valeur #i dans la liste */ while ((ici != NULL) && (no < i)) { ici = ici->suivant; no++; } if (ici == NULL){ printf("\nIl n'y a pas d'item numero %d !!!\n\n", i); return 0; } else { *val = ici->data; //on récupère cette valeur return 1; } } } 7 LISTE.CPP : Gestion d’une liste chaînée ordonnée en ordre DÉCROISSANT. #include <stdio.h> #include <conio.h> #include <stdlib.h> /********* Déclarations des types pour la liste chaînée *********/ typedef int T_objet; typedef struct noeud * T_lien; struct noeud { T_objet data; T_lien suivant; }; /********* Déclarations des fonctions de gestion de liste *********/ void void void void void void void void void creer afficher inserer inserer_au_debut inserer_a_la_fin retirer retirer_du_debut retirer_de_la_fin trier(T_lien); (T_lien *); (T_lien); (T_lien *, T_objet); (T_lien *, T_objet); (T_lien *, T_objet); (T_lien *, T_objet); (T_lien *); (T_lien *); int main(void) { T_lien tete; creer(&tete); inserer_au_debut(&tete, 75); inserer_a_la_fin(&tete, 5); inserer(&tete, 25); retirer(&tete, 40); retirer(&tete, 41); retirer_du_debut(&tete); retirer_de_la_fin(&tete); trier(tete); afficher(tete); afficher(tete); afficher(tete); afficher(tete); afficher(tete); afficher(tete); afficher(tete); afficher(tete); afficher(tete); getch(); getch(); getch(); getch(); getch(); getch(); getch(); getch(); getch(); return 0; } void creer(T_lien *tete) { T_lien p; int i; //va créer la liste 50 --> 40 --> 30 --> 20 --> 10 --> NULL *tete = NULL; for (i=1; i<=5; i++) { p = (T_lien) malloc(sizeof(struct noeud)); if (p == NULL) { *tete = NULL; return; } //valider l'allocation dynamique p->data = 10 * i; p->suivant = *tete; *tete = p; } } 8 int compter(T_lien tete) { T_lien p=tete; int n=0; //petite fonction qui compte le nombre d’éléments dans la liste while (p != NULL) { n++; p = p->suivant; } return n; } void afficher(T_lien tete) { T_lien p=tete; //pour afficher la liste du début à la fin while (p != NULL) { printf( "%5d", p->data); p = p->suivant; } printf("\n\nLa liste compte %d éléments\n\n\n", compter(tete)); } /* Fonction qui insère l’objet « x » au début de la liste */ void inserer_au_debut(T_lien *tete, T_objet x) { T_lien p; p = (T_lien) malloc(sizeof(struct noeud)); if (p == NULL) return; //valider l'allocation dynamique p->data = x; p->suivant = *tete; *tete = p; } /* Fonction qui insère l’objet « x » à la fin de la liste */ void inserer_a_la_fin(T_lien *tete, T_objet x) { T_lien p, q; p = (T_lien) malloc(sizeof(struct noeud)); if (p == NULL) return; //valider l'allocation dynamique p->data = x; if (*tete == NULL) { *tete = p; p->suivant = NULL; } else { q = *tete; while (q->suivant != NULL) q = q->suivant; p->suivant = NULL; q->suivant = p; } } 9 /* Fonction qui insère l’objet « x » en ordre (selon sa valeur) dans la liste */ void inserer(T_lien *tete, T_objet x) { T_lien ici, next, p; p = (T_lien) malloc(sizeof(struct noeud)); if (p == NULL) return; //valider l'allocation dynamique p->data = x; ici = NULL; next = *tete; /* Boucle pour localiser l’endroit où « x » sera insérée */ while ((next != NULL) && (next->data > x)) { ici = next; next = next->suivant; } if (ici == NULL) { //insertion du nouveau nœud avec valeur « x » p->suivant = *tete; *tete = p; } else { p->suivant = ici->suivant; ici->suivant = p; } } /* Fonction qui enlève le premier nœud de la liste */ void retirer_du_debut(T_lien *tete) { T_lien p= *tete; if (*tete != NULL) { *tete = p->suivant; free(p); } } /* Fonction qui enlève le nœud avec la valeur « x » de la liste */ void retirer(T_lien *tete, T_objet x) { T_lien ici= *tete, avant=NULL; if (ici == NULL) printf("\nOn ne peut pas retirer l'objet %d: la liste est vide\n\n", x); else { while ((ici != NULL) && (ici->data != x)) { //on cherche la valeur « x » avant = ici; ici = ici->suivant; } if (ici == NULL) printf("\nIl n'y a pas d'item numéro %d !!!\n\n", x); else { if (ici == *tete) *tete = ici->suivant; else avant->suivant = ici->suivant; free(ici); } } } 10 /* Fonction qui enlève le dernier nœud de la liste */ void retirer_de_la_fin(T_lien *tete) { T_lien ici= *tete, avant=NULL; if (ici == NULL) printf("\nOn ne peut pas retirer d'objet: la liste est vide\n\n"); else { while (ici->suivant != NULL) { //on cherche la fin de la liste avant = ici; ici = ici->suivant; } if (ici == *tete) *tete = NULL; else avant->suivant = NULL; free(ici); } } /* Fonction qui échange deux valeurs (pour le tri) */ void permute(T_objet *a, T_objet *b){ T_objet temp=*a; *a = *b; *b = temp; } /* Fonction qui va trier une liste chaînée avec l'algorithme du Tri par Sélection. PARAMETRE: liste - pointeur vers la liste à trier */ void trier(T_lien liste){ T_lien p=liste, q, min; /* "p" pointe sur le premier noeud */ while (p->suivant != NULL) { q = p->suivant; min = p; /* on ira jusqu'au dernier noeud */ /* boucle pour trouver la valeur minimale du reste de la liste */ while (q != NULL) { if (q->data < min->data) min = q; q = q->suivant; } /* On échange ce minimum avec le .data du noeud pointé par "p" */ permute(&p->data, &min->data); p = p->suivant; /* on passe au noeud suivant.. */ } } ÉTAT DE LA LISTE APRÈS CHAQUE AJOUT/RETRAIT : tete 50 40 30 20 10 NULL tete 75 50 40 30 20 10 NULL tete 75 50 40 30 20 10 5 NULL tete 75 50 40 30 25 20 10 5 NULL tete 75 50 30 25 20 10 5 NULL tete 50 30 25 20 10 5 NULL tete 50 30 25 20 10 NULL tete 10 20 25 30 50 NULL (après la fonction « trier ») 11