Structures et listes chaînées en C : piles, files et arbres

Apprends à regrouper des données liées avec des structures et typedef, à nommer des ensembles de valeurs avec des énumérations et à stocker différentes valeurs possibles dans des unions. Construis ensuite les structures de données chaînées classiques à partir de nœuds alloués avec malloc : listes chaînées, piles, files, arbres binaires de recherche et tables de hachage. Pour chacune, tu insères, recherches, supprimes et parcours des éléments, tu traites les cas vides et les cas limites, et tu libères chaque nœud sans fuite de mémoire.

DébutantUnité 6 sur 8 de la formation Formation : Langage C : pointeurs, mémoire et structures

Ce que tu vas apprendre

  1. Définir des types structure, leur donner des noms courts avec typedef, accéder à leurs membres par des variables et des pointeurs, et conserver plusieurs enregistrements dans des tableaux de structures.
    • Définir une structure et manipuler ses membres
      Déclarer un type structure, créer et initialiser des variables de ce type, lire et modifier leurs membres, et comprendre leur copie, leur comparaison et leur disposition en mémoire.
    • Nommer des types avec typedef et passer des structures à des fonctions
      Créer des alias de types avec typedef, passer des structures par valeur ou à l’aide de pointeurs, utiliser l’opérateur flèche et allouer des structures dans le tas.
    • Stocker et traiter des enregistrements dans des tableaux de structures
      Conserver plusieurs enregistrements dans un tableau de structures, les parcourir avec une boucle, les rechercher et les trier, agrandir le tableau dans le tas et gérer les structures propriétaires d’autres zones de mémoire.
  2. Remplacer les nombres magiques par des énumérations et utiliser des unions et des unions étiquetées pour stocker une valeur parmi plusieurs types possibles dans la même zone de mémoire.
    • Utiliser des énumérations pour définir des ensembles de valeurs nommées
      Définir des énumérations, contrôler leurs valeurs, les utiliser dans des conditions et des instructions switch, et savoir ce que le C vérifie ou non à leur sujet.
    • Superposer différentes représentations avec des unions et des unions étiquetées
      Déclarer des unions, savoir ce que produisent la lecture et l’écriture de leurs membres, associer une union à une étiquette d’énumération pour stocker en toute sécurité des valeurs de différents types, et regrouper de petits indicateurs dans des champs de bits.
  3. Relier des nœuds alloués dynamiquement en listes, les parcourir, insérer et supprimer des nœuds à n’importe quelle position, les libérer entièrement et déterminer quand une liste convient mieux qu’un tableau à une tâche.
    • Créer, parcourir et libérer une liste simplement chaînée
      Définir un type de nœud, ajouter des nœuds en tête et en fin de liste, visiter chaque nœud, écrire des fonctions qui modifient le pointeur de tête et libérer tous les nœuds.
    • Rechercher des nœuds, les insérer dans l’ordre et les supprimer
      Trouver une valeur dans une liste, insérer un nœud à la bonne place dans une liste triée, puis déchaîner et libérer un nœud en traitant le premier nœud, le dernier et les valeurs absentes.
    • Comparer les listes aux tableaux et enrichir leur structure
      Comparer les compromis des listes chaînées et des tableaux, conserver un pointeur de queue et un compteur, construire des listes doublement chaînées et libérer des nœuds propriétaires d’autres zones de mémoire.
  4. Implémenter des piles dernier entré, premier sorti et des files premier entré, premier sorti avec des tableaux et des nœuds chaînés, gérer les structures vides et pleines, et les appliquer à des tâches concrètes.
    • Construire une pile et l’utiliser
      Décrire les opérations d’une pile, implémenter une pile avec un tableau et avec des nœuds chaînés, gérer les piles vides et pleines, et utiliser des piles pour vérifier des délimiteurs et évaluer des expressions postfixées.
    • Construire une file avec des nœuds chaînés ou un tampon circulaire
      Décrire les opérations d’une file, implémenter une file avec des pointeurs de tête et de queue et avec un tableau circulaire de taille fixe, et distinguer une file vide d’une file pleine.
  5. Représenter des arbres binaires avec des nœuds chaînés, rechercher, insérer et supprimer des clés dans un arbre binaire de recherche, visiter les nœuds dans différents ordres et libérer un arbre entier.
    • Représenter un arbre binaire et effectuer une recherche dans un arbre binaire de recherche
      Utiliser le vocabulaire des arbres binaires, énoncer la propriété d’un arbre binaire de recherche et effectuer une recherche dans un arbre de façon récursive et avec une boucle.
    • Insérer, parcourir, supprimer et libérer les nœuds d’un arbre
      Insérer des clés dans un arbre binaire de recherche, visiter les nœuds par parcours infixe, préfixe et postfixe, calculer la taille et la hauteur, supprimer des clés et libérer chaque nœud.
  6. Transformer des clés en indices d’alvéoles avec une fonction de hachage, gérer les collisions avec des chaînes de nœuds et implémenter, utiliser et libérer une table de hachage en C.
    • Associer les clés aux alvéoles avec une fonction de hachage
      Expliquer le rôle d’une fonction de hachage, calculer les indices d’alvéoles pour des clés de type chaîne, reconnaître les collisions et les répartitions inégales, et utiliser le facteur de charge pour décider quand agrandir une table.
    • Implémenter une table de hachage à chaînage séparé en C
      Construire une table de hachage à partir d’un tableau d’alvéoles contenant des listes chaînées : rechercher, insérer et compter des clés, copier les clés de type chaîne en toute sécurité et libérer chaque allocation.

Structures et listes chaînées en C : cours

6 chapitres

1

Regrouper des données liées dans des structures

Des valeurs liées sont plus faciles à manipuler lorsqu’elles circulent sous la forme d’un seul enregistrement. Les structures donnent des noms à ces valeurs, et les alias de types rendent les déclarations plus lisibles. Les appels de fonctions posent alors un choix pratique : passer une copie ou passer une adresse donnant accès à l’original. Les tableaux étendent ce choix à plusieurs enregistrements ; pour les trier, les redimensionner et copier leurs membres pointeurs, il faut savoir clairement quels objets existent encore et quel code en est propriétaire.

1.1 Membres, copies et disposition des structures

Une structure regroupe des valeurs liées dans un seul enregistrement. Ses membres ont des noms et peuvent être de types différents. Une mesure de capteur, par exemple, comporte à la fois un identifiant et une température.

Définir et initialiser un enregistrement
struct reading {
    int id;
    double temp_c;
};
struct reading r = {.temp_c = 21.5, .id = 7};
r.temp_c += 0.5;

La définition introduit le type struct reading ; reading est son étiquette. Le point-virgule final se place après l’accolade fermante. Définir le type n’alloue aucun objet. La déclaration de r crée un objet, et . sélectionne ses membres. Ici, la température devient 22,0 °C. Lorsqu’une seule étiquette est déclarée, le nom du type inclut struct : reading r; nécessite un typedef distinct.

Un initialiseur tel que {7, 21.5} suit l’ordre de déclaration. Des désignateurs tels que .id = 7 permettent de nommer les membres dans un autre ordre. Les membres omis dans un initialiseur reçoivent des valeurs nulles : {0} initialise donc tout l’enregistrement. Une déclaration locale struct reading r; sans initialiseur laisse ses membres indéterminés jusqu’à ce qu’une valeur leur soit affectée.

Un membre a son propre type et sa propre adresse. Pour lire une température avec scanf, utilise scanf("%lf", &r.temp_c) et vérifie que la valeur de retour est 1. Le point est prioritaire sur & : l’adresse du membre de type double est donc transmise.

Copier et comparer les valeurs
struct reading s = r;
s.temp_c = -4.0;
int same = r.id == s.id && r.temp_c == s.temp_c;

L’affectation copie tous les membres. Modifier s.temp_c laisse r.temp_c à 22,0. Les tableaux de taille fixe contenus dans les structures sont aussi copiés : un membre char name[32] appartient à l’enregistrement. Cela fonctionne même si les tableaux C indépendants ne peuvent pas être affectés avec =. Les membres pointeurs demandent plus d’attention, car le pointeur copié pointe toujours vers le même objet.

Le C ne définit pas == pour les structures entières. Compare les membres qui définissent l’égalité pour ta tâche. memcmp compare les octets bruts, y compris le bourrage, et peut signaler une différence entre des enregistrements dont les valeurs des membres sont égales.

Suivre la disposition en mémoire

Les membres apparaissent dans l’ordre de déclaration, sans bourrage avant le premier membre. Le compilateur peut ajouter des octets inutilisés entre les membres et à la fin pour respecter les exigences d’alignement. Sur une plateforme 64 bits courante avec un int de 4 octets aligné sur 4 octets, struct { char c; int i; char d; } occupe 12 octets. Ses membres commencent aux décalages 0, 4 et 8. Les réordonner sous la forme int i; char c; char d; réduit généralement la taille à 8 octets. Utilise sizeof pour obtenir la taille réelle et offsetof de <stddef.h> pour obtenir un décalage.

Les structures peuvent être imbriquées. Avec struct point { double x_m, y_m; }; et struct rect { struct point corner; double width_m, height_m; };, box.corner.x_m donne accès à l’abscisse du coin en mètres. Une structure ne peut pas contenir un membre complet de son propre type : cela exigerait un objet de taille infinie. Un pointeur vers son propre type a une taille fixe et rend possibles les nœuds chaînés.

Points clés

  • Utilise . pour sélectionner un membre d’un objet de type structure.
  • Un initialiseur met à zéro les membres omis ; une structure locale non initialisée a des membres indéterminés.
  • L’affectation d’une structure entière copie ses membres, y compris les tableaux qu’elle contient.
  • Compare les valeurs des membres et utilise sizeof pour inclure le bourrage.
  • Une structure peut contenir un pointeur vers son propre type.

1.2 Noms de types, appels de fonctions et pointeurs vers des structures

Un typedef donne un autre nom à un type existant. Il change la façon d’écrire le type, pas le comportement de ses objets. typedef double meters; fait de meters un autre nom pour double.

Nommer une structure et la passer par valeur
typedef struct {
    int id;
    double temp_c;
} reading;

reading make_reading(int id, double t) {
    reading r = {id, t};
    return r;
}
void warm_copy(reading r) {
    r.temp_c += 1.0;
}

Tu peux maintenant déclarer reading r = make_reading(7, 21.5);. Renvoyer une structure renvoie sa valeur. L’appel warm_copy(r) donne une copie à la fonction : la température de la fonction appelante reste donc à 21,5 °C. Une copie d’un petit enregistrement est souvent l’interface la plus claire lorsque l’objet de la fonction appelante doit rester inchangé.

Un type autoréférentiel a besoin de son étiquette à l’intérieur de la définition :

typedef struct node {
    int number;
    struct node *next;
} node;

L’alias node devient disponible après la déclaration. À l’intérieur de ces accolades, struct node désigne déjà le type incomplet et peut être utilisé pour un pointeur. L’étiquette et l’alias peuvent avoir la même orthographe, car les étiquettes utilisent leur propre espace de noms.

Accéder à l’objet de la fonction appelante par un pointeur
void warm(reading *p) {
    p->temp_c += 1.0;
}
reading r = {7, 21.5};
reading *p = &r;
p->temp_c -= 1.5;
warm(&r);

La soustraction modifie r.temp_c pour lui donner la valeur 20,0 ; warm lui donne ensuite la valeur 21,0. La fonction reçoit une copie de l’adresse, et les deux pointeurs donnent toujours accès au même enregistrement. Utilise -> avec un pointeur et . avec un objet de type structure. p->temp_c signifie (*p).temp_c. Les parenthèses sont nécessaires, car l’accès au membre est prioritaire sur le déréférencement : *p.temp_c tente de sélectionner un membre de p avant d’appliquer *.

Pour lire sans copier la structure, accepte un paramètre const reading *p. Une affectation à p->temp_c à travers ce pointeur est rejetée. La variable pointeur peut encore changer, par exemple lorsqu’elle parcourt un tableau. En revanche, reading *const p rend le pointeur lui-même constant. Pour les structures ayant des membres pointeurs, un pointeur vers un objet constant ne rend pas accessibles en lecture seule les données allouées séparément vers lesquelles ces membres pointent.

Donner un propriétaire à un enregistrement dans le tas

Inclus <stdlib.h> pour l’allocation et <stdbool.h> pour ce résultat indiquant la réussite :

bool make_heap_reading(reading **out, int id, double t) {
    reading *p = malloc(sizeof *p);
    if (p == NULL) return false;
    *p = (reading){.id = id, .temp_c = t};
    *out = p;
    return true;
}

La fonction écrit dans *out seulement après la réussite de l’allocation. La fonction appelante peut déclarer reading *item = NULL;, appeler make_heap_reading(&item, 8, -2.0), traiter un résultat faux, puis appeler free(item) plus tard. sizeof *p mesure le type pointé même avant que p pointe vers un enregistrement initialisé ; cette expression ne lit pas cet enregistrement.

Un pointeur vers une structure locale automatique devient pendant au retour de la fonction. Renvoie la valeur de la structure ou une allocation dans le tas ayant un propriétaire clairement défini. Renvoyer &r depuis une fonction dont la variable locale est r ne peut pas prolonger sa durée de vie.

Points clés

  • typedef crée un alias, et une définition autoréférentielle utilise son étiquette de structure.
  • Passe une structure par valeur pour obtenir une copie ou passe son adresse pour modifier l’original.
  • p->member équivaut à (*p).member.
  • Un pointeur vers un objet constant empêche de modifier ses membres à travers ce pointeur.
  • Vérifie l’allocation avant l’initialisation et donne un propriétaire à chaque enregistrement dans le tas.
  • Renvoyer une structure locale par valeur est valide ; renvoyer son adresse laisse un pointeur pendant.

1.3 Tableaux d’enregistrements et chaînes dont ils sont propriétaires

Un tableau de structures conserve des enregistrements côte à côte. Utilise un indice pour sélectionner un enregistrement, puis un point pour sélectionner un membre. Les parcours, les recherches et les tris fonctionnent ainsi à peu près comme avec un tableau de nombres.

Parcourir les enregistrements avec un nombre explicite
reading readings[4] = {
    {1, 21.5}, {2, -3.0}, {3, 18.25}, {4, 30.0}
};
size_t n = sizeof readings / sizeof readings[0];
size_t best = 0;
for (size_t i = 1; i < n; ++i) {
    if (readings[i].temp_c > readings[best].temp_c)
        best = i;
}

Ici, reading est l’alias de la structure contenant un identifiant et une température. L’indice du meilleur élément passe de 0 à 3 : l’enregistrement de température la plus élevée a donc l’identifiant 4 et la température 30,0 °C. Commencer avec un élément réel fonctionne aussi lorsque toutes les températures sont négatives ; commencer avec un maximum supposé de zéro peut échouer. Ce parcours exige au moins un élément. Une recherche réutilisable doit signaler un tableau vide avant de lire l’élément 0.

Le calcul du nombre d’éléments avec sizeof fonctionne là où readings est un tableau. Dans un paramètre de fonction, une déclaration de tableau devient un pointeur : passe donc n séparément. (items + i)->temp_c et items[i].temp_c donnent accès au même membre.

Fournir une fonction de comparaison à qsort

Inclus <stdlib.h>. Pour des températures finies, cette fonction de comparaison renvoie un signe sans convertir une petite différence de température en entier :

int cmp_temp(const void *a, const void *b) {
    const reading *x = a;
    const reading *y = b;
    return (x->temp_c > y->temp_c)
         - (x->temp_c < y->temp_c);
}
/* With the array above: */
qsort(readings, n, sizeof readings[0], cmp_temp);

La fonction de comparaison reçoit les adresses des éléments. Un résultat négatif signifie que le premier vient avant, zéro signifie qu’ils sont égaux, et un résultat positif qu’il vient après. L’ordre des identifiants après tri est 2, 3, 1, 4. Renvoyer directement la différence de type double tronque une différence telle que −0,25 à zéro. Soustraire des clés entières peut provoquer un débordement. Les comparer d’abord évite ces erreurs. qsort ne conserve pas l’ordre initial des éléments égaux.

Agrandir un tableau sans perdre son stockage

Conserve séparément son nombre d’éléments utilisés et sa capacité. Pour un tableau alloué dont cap est positif, utilise un pointeur temporaire lorsque tu doubles la capacité. Inclus <stdint.h> pour SIZE_MAX :

if (cap > SIZE_MAX / 2 / sizeof *items) return false;
size_t new_cap = 2 * cap;
reading *tmp = realloc(items, new_cap * sizeof *items);
if (tmp == NULL) return false;
items = tmp;
cap = new_cap;

Ces instructions se placent dans une fonction qui renvoie un bool ; items est son pointeur vers le tableau et cap sa capacité positive. En cas d’échec, l’ancienne allocation reste disponible. En cas de réussite, l’allocation peut être déplacée : les anciens pointeurs vers ses éléments doivent donc être remplacés. Une capacité initiale nulle nécessite sa propre allocation initiale.

Déterminer ce dont une copie est propriétaire

Avec typedef struct { char *name; int age; } person;, person b = a; copie l’adresse contenue dans name. Les deux enregistrements donnent accès à la même chaîne. Libérer les deux copies en tant que propriétaires libérerait deux fois la même allocation. Pour obtenir une copie indépendante, alloue strlen(a.name) + 1 octets, vérifie la réussite et utilise strcpy de <string.h>. Un membre de taille fixe char name[32] est copié avec la structure. strdup effectue l’allocation et la copie, mais appartient à POSIX et à C23, pas à la bibliothèque standard C17.

Points clés

  • Utilise items[i].member et passe un nombre d’éléments aux fonctions qui reçoivent des tableaux.
  • Initialise une recherche de maximum à partir d’un élément existant après avoir vérifié que le tableau n’est pas vide.
  • Une fonction de comparaison pour qsort renvoie un signe ; soustraire des clés peut tronquer le résultat ou provoquer un débordement.
  • Vérifie le calcul de la taille d’allocation et stocke le résultat de realloc dans un pointeur temporaire.
  • Un membre pointeur copié désigne toujours l’allocation d’origine.
2

Nommer des choix avec des énumérations et partager le stockage avec des unions

Un programme a souvent besoin d’un choix portant un nom lisible ou d’une valeur qui peut prendre plusieurs formes. Les énumérations nomment les choix ; les unions partagent le stockage entre plusieurs possibilités. Les associer permet à chaque valeur stockée d’indiquer son propre type. Il faut veiller à la cohérence entre le nom et la charge utile, tout en sachant que le C laisse au programme le contrôle de la validation des entrées et des choix de représentation.

2.1 Valeurs d’énumération et vérification des choix

Une énumération donne des noms lisibles à des choix représentés par des entiers. Des noms tels que HIGH rendent visible le rôle d’une condition sans obliger le lecteur à se souvenir de ce que représente le nombre 10.

Suivre la numérotation
enum weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
enum level { LOW = 1, MEDIUM, HIGH = 10, CRITICAL };
enum level alert = HIGH;

Une première valeur omise vaut zéro. Les valeurs omises suivantes augmentent la valeur précédente de un. Ainsi, MON vaut 0 et SUN vaut 6 ; les niveaux valent 1, 2, 10 et 11. Une valeur explicite relance le comptage à partir de cette valeur. Des constantes différentes peuvent partager une valeur, mais des valeurs identiques ne peuvent pas apparaître comme étiquettes case distinctes dans un même switch.

Un alias peut raccourcir une déclaration : typedef enum { RED, YELLOW, GREEN } light; permet d’écrire light signal = RED;. Les constantes d’énumération partagent l’espace de noms des identificateurs ordinaires dans leur portée. Deux énumérations différentes ne peuvent pas toutes deux déclarer RED dans la même portée. Des préfixes tels que LIGHT_RED et PAINT_RED permettent de distinguer les noms.

Traiter chaque choix
const char *level_name(enum level value) {
    switch (value) {
        case LOW: return "low";
        case MEDIUM: return "medium";
        case HIGH: return "high";
        case CRITICAL: return "critical";
    }
    return "unknown";
}

Chaque return termine son cas. Si un cas effectue plutôt une action et doit ensuite s’arrêter, ajoute-lui un break ; atteindre l’étiquette suivante y poursuit l’exécution. Avec GCC, -Wall active les avertissements pour les cas d’énumération manquants lorsqu’il n’y a pas de default ; Clang signale aussi ces omissions par défaut. Un avertissement peut révéler un cas manquant lorsque tu ajoutes une constante par la suite.

Valider une entrée parmi un ensemble de choix

Le type énumération ne garantit pas l’appartenance à l’ensemble nommé. C17 autorise l’affectation d’un entier : enum level x = 42; peut donc compiler. Vérifier que le nombre est compris entre 1 et 11 accepte encore des valeurs sans nom comme 6. Teste les valeurs effectivement autorisées :

#include <stdbool.h>
bool valid_level(int value) {
    return value == LOW || value == MEDIUM
        || value == HIGH || value == CRITICAL;
}

Ce n’est qu’après cette vérification que l’entrée doit devenir un niveau. Pour les jours de la semaine consécutifs, une vérification des bornes de MON à SUN suffit. Dans une énumération ordinaire C17, les constantes d’énumération sont de type int. C23 permet en outre de spécifier un type sous-jacent, comme dans enum color : unsigned char ; cette syntaxe n’appartient pas à C17.

Afficher les noms et utiliser les nombres d’éléments

printf("%d\n", SUN) affiche « 6 », pas « SUN ». Pour des valeurs consécutives commençant à zéro, un tableau de noms fournit le texte : const char *day_names[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};. Après validation d’un indice, day_names[SUN] vaut « Sun ». Utiliser une valeur de niveau telle que HIGH comme indice d’un tel tableau exigerait une autre correspondance, car les valeurs des niveaux ne sont pas consécutives.

Ajouter DAY_COUNT immédiatement après SUN lui donne la valeur 7, utile comme borne de boucle ou taille de tableau. Cette constante compte les jours, mais n’est pas elle-même un jour valide. Les noms d’énumération suivent les règles de portée du C et peuvent être affichés par les débogueurs ; un #define effectue quant à lui une substitution de texte par le préprocesseur.

Points clés

  • La numérotation d’une énumération commence à zéro sauf si une valeur est fournie, et reprend après chaque valeur explicite.
  • Une variable d’énumération peut contenir une valeur sans nom : valide donc les entrées externes.
  • Utilise break ou return pour terminer un cas lorsque l’enchaînement des cas n’est pas souhaité.
  • Un tableau associant les indices aux noms exige des indices validés, consécutifs et commençant à zéro.

2.2 Unions avec une étiquette correspondante

Une union donne à plusieurs membres la même zone de stockage. Elle convient à une valeur qui peut être de plusieurs types. Une structure donne à chaque membre sa propre zone de stockage et convient à un enregistrement qui a besoin de toutes ces valeurs ensemble.

Stocker une des valeurs possibles
union value {
    int i;
    double d;
    char c;
};
union value v = {.d = 2.5};

Tous les membres commencent à la même adresse. L’union doit contenir son plus grand membre et respecter les exigences d’alignement ; avec un double de 8 octets sur les plateformes courantes, cette union occupe 8 octets. Un initialiseur simple {42} initialise son premier membre, i ; .d = 2.5 choisit explicitement d.

Écrire v.i = 42 après l’initialisation remplace la valeur stockée. Lire ensuite v.d ne convertit pas 42 en double : cela interprète les octets stockés selon une autre représentation. Cette représentation peut être invalide. Lis le membre correspondant à la valeur que tu as stockée.

Indiquer quel membre est valide

Associe l’union à une énumération. L’énumération est l’étiquette, et l’union est la charge utile :

enum kind { K_INT, K_DOUBLE, K_CHAR };
struct tagged {
    enum kind kind;
    union value as;
};
struct tagged make_double(double d) {
    return (struct tagged){.kind = K_DOUBLE, .as = {.d = d}};
}

Une fonction auxiliaire définit les deux éléments ensemble. Le code qui écrit maintient les deux champs synchronisés. Si le code écrit dans t.as.i mais laisse t.kind à K_DOUBLE, une lecture ultérieure choisit la mauvaise représentation.

#include <stdio.h>
void print_cell(struct tagged t) {
    switch (t.kind) {
        case K_INT: printf("%d", t.as.i); break;
        case K_DOUBLE: printf("%.1f", t.as.d); break;
        case K_CHAR: printf("%c", t.as.c); break;
    }
}

Appeler cette fonction pour des cellules contenant 42, 2,5 et 'x', en affichant des espaces entre les appels, produit « 42 2.5 x ». Chaque cas choisit à la fois le membre et le format correspondants. Un jeton de calculatrice peut de même contenir soit un nombre, soit un opérateur.

Sur les plateformes 64 bits courantes, une énumération de 4 octets, du bourrage et cette union de 8 octets font que struct tagged occupe 16 octets. Une structure stockant séparément l’énumération et les trois valeurs possibles occupe généralement 24 octets. Ce sont des exemples de disposition ; interroge les types réels avec sizeof.

Regrouper de petits indicateurs dans des champs de bits
struct flags {
    unsigned int ready : 1;
    unsigned int mode : 3;
};

La largeur suit les deux-points. Un champ non signé de 1 bit contient 0 ou 1 ; un champ de 3 bits contient une valeur de 0 à 7. Stocker 9 dans mode conserve les trois bits de poids faible et stocke 1. Cela perd de l’information : valide donc un mode avant de le stocker lorsque des valeurs plus grandes indiquent une erreur.

Un champ de bits ne possède pas d’emplacement adressable séparément : &flags.mode n’est donc pas valide pour une variable nommée flags. Son ordre d’allocation et son regroupement dépendent de l’implémentation. N’utilise pas une déclaration de champ de bits comme garantie portable d’une disposition précise de fichier ou de matériel. Une largeur indiquée précise la plage de valeurs du champ, pas la disposition de chaque octet autour de lui.

Points clés

  • Les membres d’une union se superposent, tandis que ceux d’une structure ont des zones de stockage distinctes.
  • Une étiquette doit correspondre au membre de l’union qui contient la valeur stockée.
  • Un switch sélectionne le membre et le format de sortie correspondants.
  • La largeur des champs de bits non signés limite les valeurs stockées ; les bits de poids fort excédentaires sont perdus.
  • La disposition des champs de bits est définie par l’implémentation et les champs n’ont pas d’adresse propre.
3

Construire et gérer des listes chaînées

Les structures chaînées grandissent en reliant des allocations distinctes. Une liste rend ces connexions visibles : un pointeur commence la chaîne, et chaque nœud mène au suivant. Le travail délicat intervient lorsque les liens changent, notamment au premier ou au dernier nœud. Conserver le bon lien entrant permet à une même opération de traiter plusieurs positions, tandis que les pointeurs de queue et les liens dans les deux sens modifient les opérations qui nécessitent une recherche.

3.1 Créer une liste et garder chaque nœud accessible

Une liste simplement chaînée stocke chaque élément dans un nœud alloué séparément. Chaque nœud contient sa valeur et l’adresse du nœud suivant. Le pointeur de tête pointe vers le premier nœud ; NULL marque à la fois une liste vide et la fin d’une chaîne.

Définir le lien et insérer en tête
#include <stdbool.h>
#include <stdlib.h>
typedef struct node {
    int number;
    struct node *next;
} node;

bool prepend(node **head, int value) {
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    n->number = value;
    n->next = *head;
    *head = n;
    return true;
}

Commence par node *list = NULL;. Appelle prepend(&list, value) et traite un résultat faux. La fonction prend l’adresse du pointeur de tête de la fonction appelante. *head désigne donc le pointeur qui doit changer. Prendre seulement node *head et affecter une valeur à ce pointeur local laisserait le pointeur de tête de la fonction appelante inchangé.

L’ordre des dernières affectations préserve l’ancienne liste. Relie d’abord le nouveau nœud à l’ancienne tête, puis remplace le pointeur de tête. Inverser ces instructions fait pointer n->next vers n lui-même et fait perdre l’ancienne chaîne. Pour les valeurs arrivant dans l’ordre 1, 2 et 3, les insertions successives en tête produisent 1, puis 2 → 1, puis 3 → 2 → 1 → NULL. L’insertion en tête inverse l’ordre d’arrivée.

Parcourir sans déplacer le pointeur qui donne accès à la liste
#include <stdio.h>
void print_list(const node *head) {
    for (const node *p = head; p != NULL; p = p->next)
        printf("%d ", p->number);
}

Le pointeur de parcours p se déplace tandis que head reste disponible. L’exemple affiche « 3 2 1 », avec un espace final. Pour une liste vide, le corps de la boucle s’exécute zéro fois. Il n’y a pas d’indice de tableau : chaque étape lit un lien pour trouver le nœud suivant. Les allocations peuvent être dispersées en mémoire, et insérer un nœud ne déplace pas les nœuds existants.

Ajouter en fin de liste
bool append(node **head, int value) {
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    *n = (node){.number = value, .next = NULL};
    node **link = head;
    while (*link != NULL) link = &(*link)->next;
    *link = n;
    return true;
}

link commence à l’adresse du pointeur de tête de la fonction appelante. Il passe ensuite à l’adresse de chaque membre next jusqu’à désigner le lien nul final. Affecter une valeur à cet emplacement fonctionne aussi pour une liste vide. Sans pointeur de queue conservé, chaque ajout en fin doit d’abord suivre la chaîne existante jusqu’à son extrémité. Une fonction peut aussi renvoyer un nouveau pointeur de tête, à condition que la fonction appelante le conserve et traite un échec d’allocation sans perdre l’ancienne liste.

Libérer les nœuds dans un ordre sûr
void clear_list(node **head) {
    while (*head != NULL) {
        node *old = *head;
        *head = old->next;
        free(old);
    }
}

Lis le successeur tant que old existe encore, déplace le pointeur de tête, puis libère l’ancien nœud. Après la boucle, le pointeur de tête de la fonction appelante vaut NULL. Lire old->next après avoir libéré old est une utilisation après libération, même si la mémoire semble inchangée. Un outil de vérification de la mémoire peut aider à révéler les lectures invalides et les allocations oubliées ; le nettoyage doit aussi être exécuté après l’échec d’une opération ultérieure.

Points clés

  • Une liste vide a un pointeur de tête nul, et le dernier nœud a un lien vers le suivant nul.
  • Alloue et initialise un nouveau nœud avant de modifier la chaîne existante.
  • Donne l’adresse du pointeur de tête aux fonctions qui le modifient ou conserve le pointeur de tête qu’elles renvoient.
  • Parcours la liste avec un pointeur distinct pour qu’elle reste accessible.
  • Sans pointeur de queue, l’ajout en fin nécessite de parcourir la liste jusqu’à son extrémité.
  • Lis le successeur avant de libérer un nœud.

3.2 Modifier le lien entrant pour insérer et supprimer

La recherche dans une liste suit les liens un par un. L’insertion et la suppression demandent une information supplémentaire : l’adresse du lien qui doit changer. Ce lien peut être le pointeur de tête ou un membre next situé dans un nœud précédent.

Rechercher une valeur

Avec le type node et ses membres number et next :

node *find(node *head, int value) {
    for (node *p = head; p != NULL; p = p->next)
        if (p->number == value) return p;
    return NULL;
}

Une correspondance renvoie l’adresse du nœud lui-même. Une valeur absente donne NULL. Atteindre le 1 000e nœud exige de suivre 999 liens depuis la tête, même lorsque tu connais sa position à l’avance.

Conserver un pointeur vers le lien à modifier

Un node ** peut pointer soit vers le pointeur de tête, soit vers le champ next d’un nœud. Appelons-le link : *link est le nœud actuellement atteint par ce lien. Avancer avec link = &(*link)->next change le lien que tu examines ; cela ne modifie pas la liste.

bool insert_sorted(node **head, int value) {
    node **link = head;
    while (*link != NULL && (*link)->number < value)
        link = &(*link)->next;
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    n->number = value;
    n->next = *link;
    *link = n;
    return true;
}

Inclus <stdbool.h> et <stdlib.h>. La liste reçue doit déjà être triée par ordre croissant. Arrête-toi avant la première valeur qui n’est pas inférieure à la nouvelle : les doublons sont ainsi placés avant les valeurs égales. La partie gauche de && vérifie si le pointeur vaut NULL avant que la partie droite ne déréférence le nœud. Inverser ces tests déréférencerait un pointeur nul à la fin.

Pour les insertions 5, 2, 8 et 4, la liste finale est 2 → 4 → 5 → 8. Lors de l’insertion de 4 dans 2 → 5 → 8, link passe du pointeur de tête à 2.next. Le nouveau nœud pointe vers 5, et 2.next est redirigé vers le nouveau nœud. Si le nouvel élément est le plus petit, link désigne encore le pointeur de tête. À la fin, il désigne le lien nul final.

Déchaîner avant de libérer
bool remove_first(node **head, int value) {
    node **link = head;
    while (*link != NULL && (*link)->number != value)
        link = &(*link)->next;
    if (*link == NULL) return false;
    node *old = *link;
    *link = old->next;
    free(old);
    return true;
}

La boucle trouve le lien entrant. Sauvegarder sa cible dans old garde le nœud accessible pendant que *link est modifié pour pointer vers son successeur. Ce n’est qu’ensuite que le nœud retiré est libéré. Supprimer 5 de 2 → 4 → 5 → 8 donne 2 → 4 → 8. Supprimer ensuite 2 modifie le pointeur de tête et laisse 4 → 8. Si la valeur est absente, la fonction renvoie une valeur fausse sans modifier la liste.

Une version à deux pointeurs conserve prev et cur. Lorsque prev est nul, le nœud trouvé est la tête et le pointeur de tête doit changer ; sinon, mets à jour prev->next. Oublier le cas de la tête laisse un pointeur vers une zone de mémoire libérée. La version utilisant l’adresse du lien traite les deux cas par la même affectation.

Teste une liste vide, une liste à un nœud, une correspondance en premier ou en dernier, ainsi qu’une valeur absente. Pour chaque modification, repère le lien entrant, le nœud concerné et son successeur avant d’écrire une affectation. Aucun nœud conservé ne doit devenir inaccessible, et aucun lien ne doit pointer vers un nœud libéré.

Points clés

  • La recherche renvoie le nœud correspondant ou NULL lorsqu’elle atteint la fin.
  • Un pointeur vers un lien peut désigner le pointeur de tête ou un champ contenant le pointeur vers le nœud suivant.
  • L’insertion ordonnée relie le nouveau nœud à son successeur avant de rediriger le lien entrant.
  • La suppression redirige le lien entrant avant de libérer le nœud retiré.
  • Teste les cas de la tête, de la queue, d’une liste vide, d’une liste à un nœud et d’une valeur absente.

3.3 Choisir un tableau, un pointeur de queue ou des liens dans les deux sens

La question utile est de savoir quelles opérations ton programme effectue. Les tableaux et les listes peuvent contenir les mêmes valeurs, mais ils y accèdent et les déplacent différemment.

Comparer les opérations
TâcheTableauListe simplement chaînée
Atteindre la position 500Utiliser directement a[500]Suivre les liens depuis la tête
Insérer après une position connueDécaler les éléments suivantsModifier deux liens après l’allocation
Parcourir toutes les valeursLire des éléments contigusSuivre des nœuds potentiellement dispersés
Supprimer un nœud connuDécaler les éléments suivantsObtenir aussi son lien entrant

Une liste ne permet pas de trouver une position sans parcours. L’insertion après un nœud dont tu disposes déjà est directe, mais la suppression nécessite aussi le prédécesseur ou le lien qui pointe vers le nœud. Avec seulement l’adresse du nœud, une liste simplement chaînée exige généralement un parcours depuis la tête pour trouver ce lien entrant.

Le stockage compte aussi. Sur une plateforme 64 bits typique, un nœud comportant un int de 4 octets et un pointeur de 8 octets occupe 16 octets après ajout du bourrage. Pour 1 000 entiers, les nœuds utilisent 16 000 octets plus les informations de gestion des allocations ; un tableau contigu d’entiers utilise 4 000 octets. Parcourir le tableau est généralement plus rapide, car les valeurs voisines sont stockées ensemble.

Regrouper les métadonnées de la liste
typedef struct {
    node *head;
    node *tail;
    size_t count;
} list_t;

Initialise une structure englobante avec list_t list = {0};. Les règles pour l’état vide sont head == NULL, tail == NULL et count == 0. Chaque insertion ou suppression réussie doit mettre à jour le compteur ainsi que les liens. Un échec d’allocation ne modifie aucun de ces éléments.

Avec un pointeur de queue, l’ajout en fin après vérification de l’allocation utilise ces affectations :

n->next = NULL;
if (list.tail == NULL) list.head = n;
else list.tail->next = n;
list.tail = n;
++list.count;

Ici, n est un nœud initialisé. Pour le premier nœud, les pointeurs de tête et de queue deviennent égaux. La suppression du dernier nœud doit faire reculer le pointeur de queue ; pour une liste simplement chaînée, cela exige le prédécesseur. La suppression de l’unique nœud met les deux pointeurs à NULL et le compteur à zéro.

Relier dans les deux sens
typedef struct dnode {
    int value;
    struct dnode *prev, *next;
} dnode;
typedef struct {
    dnode *head, *tail;
} dlist;

void unlink_node(dlist *list, dnode *d) {
    if (d->prev != NULL) d->prev->next = d->next;
    else list->head = d->next;
    if (d->next != NULL) d->next->prev = d->prev;
    else list->tail = d->prev;
    free(d);
}

Cette fonction reçoit un nœud encore valide appartenant à la liste. Les liens des deux voisins sont rétablis avant sa libération. Aux extrémités, le pointeur de tête ou de queue est modifié à la place d’un voisin absent. Une liste doublement chaînée peut être parcourue à rebours et permet de supprimer un nœud dont on dispose sans chercher son prédécesseur, au prix d’un pointeur supplémentaire par nœud et d’un plus grand nombre de liens à maintenir.

Libérer les données possédées avant leur nœud

Un word_node peut être propriétaire à la fois d’une allocation pour le nœud et d’une allocation pour une chaîne accessible par word. Sauvegarde next, appelle free(n->word), puis free(n), et passe au successeur sauvegardé. Libérer uniquement le nœud provoque une fuite de mémoire pour la chaîne. Lire n->word après avoir libéré le nœud est invalide. La propriété indique quel code doit libérer chaque allocation, même lorsque plusieurs pointeurs permettent d’y accéder.

Points clés

  • Les tableaux offrent un accès direct par indice et un stockage contigu compact.
  • Les listes permettent de modifier localement les liens une fois la position ou le lien entrant nécessaire connu.
  • Un pointeur de queue rend l’ajout en fin direct, mais chaque opération doit maintenir les deux extrémités à jour.
  • Les nœuds doublement chaînés stockent le prédécesseur et le successeur.
  • Libère une charge utile possédée avant de libérer le nœud qui stocke son adresse.
  • Un compteur stocké doit changer à chaque insertion et suppression réussie.
4

Implémenter des piles et des files

Les piles et les files décrivent l’ordre dans lequel les tâches sont traitées. Une pile garde à disposition l’élément inachevé le plus récent, ce qui convient à l’imbrication et à l’évaluation d’expressions. Une file garde à disposition l’élément en attente le plus ancien, ce qui convient à l’ordre d’arrivée. Toutes deux peuvent utiliser des tableaux ou des nœuds chaînés. Leurs implémentations doivent rendre explicites les états vides et pleins et garder cohérents les compteurs, les pointeurs d’extrémité et les indices après une modification.

4.1 Opérations sur les piles et tâches imbriquées

Une pile retire d’abord l’élément ajouté le plus récemment : dernier entré, premier sorti (LIFO). Ses opérations définissent un type abstrait de données indépendamment du fait qu’un tableau ou des nœuds chaînés contiennent les valeurs. push ajoute au sommet, pop retire l’élément au sommet et peek le lit sans le retirer.

Utiliser un tableau et un compteur
#include <stdbool.h>
#define STACK_CAP 100
typedef struct {
    int items[STACK_CAP];
    int top;
} stack;

bool push(stack *s, int value) {
    if (s->top == STACK_CAP) return false;
    s->items[s->top++] = value;
    return true;
}
bool pop(stack *s, int *out) {
    if (s->top == 0) return false;
    *out = s->items[--s->top];
    return true;
}

Initialise avec stack s = {0};. Ici, top est le nombre d’éléments stockés : l’indice occupé au sommet est donc top - 1. Un empilement réussi écrit dans la prochaine case libre et augmente le compteur. Un dépilement réussi diminue d’abord le compteur, puis lit l’élément à cet indice. La fonction appelante fournit un pointeur out valide et vérifie le résultat avant d’utiliser sa valeur.

Empiler 4, 7 et 1, puis dépiler deux fois renvoie 1 et 7, et laisse 4. Sans vérification de capacité, empiler dans une pile pleine écrit dans items[STACK_CAP], après la fin du tableau. Sans vérification de l’état vide, le top signé passe de 0 à −1 et le dépilement lit items[-1]. Les deux accès ont un comportement indéfini.

Utiliser la tête d’une liste comme sommet

Une pile chaînée empile en insérant en tête un nœud dont l’allocation a été vérifiée. Le dépilement retire la tête :

bool pop_linked(node **top, int *out) {
    if (*top == NULL) return false;
    node *old = *top;
    *out = old->number;
    *top = old->next;
    free(old);
    return true;
}

node possède un entier number et un pointeur next. Une pile chaînée n’a pas de capacité fixe de tableau, mais un empilement peut échouer si l’allocation échoue. Elle exige toujours de vérifier si elle est vide et de libérer les nœuds restants à la fin.

Apparier les délimiteurs imbriqués

Parcours l’entrée de gauche à droite. Empile chaque parenthèse, crochet ou accolade ouvrant. Pour chaque délimiteur fermant, dépile et vérifie que les types correspondent. Un délimiteur fermant sans délimiteur ouvrant provoque immédiatement un échec. Termine en vérifiant que la pile est vide : des délimiteurs ouvrants restants indiquent aussi une discordance.

Le texte « ( [ ] { } ) » est équilibré. Pour « ( ] », le délimiteur fermant ne correspond pas au délimiteur ouvrant dépilé. Pour « ( ( ) », un délimiteur ouvrant reste après le parcours. Un simple comptage des délimiteurs ouvrants et fermants manquerait une imbrication incorrecte comme « ( [ ) ] ».

Évaluer des expressions postfixées

Empile les nombres à mesure qu’ils arrivent. Pour un opérateur binaire, dépile d’abord l’opérande de droite, puis celui de gauche, calcule left operator right et empile le résultat. Pour « 3 4 + 2 * », + combine 3 et 4 pour donner 7, puis * combine 7 et 2 pour donner 14. Pour « 9 4 - », dépile 4 comme opérande de droite et 9 comme opérande de gauche, ce qui donne 5. Les inverser donne −5. Rejette les expressions qui n’ont pas assez d’opérandes et exige un seul résultat final ; la division exige aussi un opérande de droite non nul.

Les appels de fonctions ont des durées de vie organisées en pile : une fonction appelée se termine avant que la fonction appelante ne poursuive son exécution. Chaque appel actif a besoin d’un cadre de pile, si bien qu’une récursivité très profonde peut épuiser la pile d’appels même lorsque les allocations dans le tas réussissent.

Points clés

  • LIFO signifie que l’élément le plus récent est retiré en premier.
  • Dans l’implémentation sur tableau, top compte les éléments ; vérifie si la pile est vide ou pleine avant tout accès.
  • Une pile chaînée empile et dépile en tête de liste.
  • L’appariement des délimiteurs exige que leurs types correspondent et que la pile soit vide à la fin.
  • Pour une soustraction ou une division postfixée, la première valeur dépilée est l’opérande de droite.

4.2 Extrémités d’une file et stockage circulaire

Une file retire d’abord l’élément le plus ancien : premier entré, premier sorti (FIFO). Enfiler ajoute en queue ; défiler retire en tête. Après avoir enfilé A, B et C, les deux retraits suivants renvoient A et B. Cela convient aux tâches qui doivent être traitées dans leur ordre d’arrivée.

Conserver les deux extrémités d’une file chaînée

Avec des nœuds comportant les membres number et next, et en incluant <stdbool.h> et <stdlib.h> :

typedef struct { node *head, *tail; } queue;

bool enqueue(queue *q, int value) {
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    *n = (node){.number = value, .next = NULL};
    if (q->tail == NULL) q->head = n;
    else q->tail->next = n;
    q->tail = n;
    return true;
}
bool dequeue(queue *q, int *out) {
    if (q->head == NULL) return false;
    node *old = q->head;
    *out = old->number;
    q->head = old->next;
    if (q->head == NULL) q->tail = NULL;
    free(old);
    return true;
}

Initialise avec queue q = {0};. L’enfilement définit le lien de l’ancienne queue avant de déplacer le pointeur de queue. Pour le premier nœud, il définit aussi le pointeur de tête. Le défilement fait avancer le pointeur de tête. Lorsque cela retire l’unique nœud, le pointeur de queue doit lui aussi devenir NULL. Sinon, le prochain enfilement écrit à travers un pointeur de queue pendant. Aucune des deux opérations n’exige de parcourir la liste.

Réutiliser un tableau fixe comme anneau

Une file dans un tableau ordinaire qui retire toujours l’élément d’indice 0 doit décaler les valeurs restantes. Un tampon circulaire suit plutôt un indice de tête et un nombre d’éléments :

#define CAP 4
typedef struct {
    char items[CAP];
    unsigned head, count;
} ring;

bool ring_put(ring *q, char value) {
    if (q->count == CAP) return false;
    q->items[(q->head + q->count) % CAP] = value;
    ++q->count;
    return true;
}
bool ring_get(ring *q, char *out) {
    if (q->count == 0) return false;
    *out = q->items[q->head];
    q->head = (q->head + 1) % CAP;
    --q->count;
    return true;
}

Initialise un tampon circulaire avec {0}. head indique toujours l’élément en tête lorsque la file n’est pas vide. Le prochain indice d’insertion est l’indice de tête augmenté du nombre d’éléments, avec retour au début grâce à % CAP. Lis l’élément en tête avant de déplacer head. Le compteur permet de distinguer une file vide d’une file pleine sans renoncer à une case.

Suivre le retour au début

Enfile A, B et C dans les cases 0, 1 et 2. Défile A : l’indice de tête devient 1 et le compteur devient 2. D entre dans la case 3 ; E entre dans la case 0. Le tableau physique contient maintenant E, B, C, D, mais l’ordre logique de retrait est B, C, D, E. Le tampon circulaire est plein parce que le compteur vaut 4, même si la tête est à l’indice 1.

Une autre conception suit l’indice de tête et l’indice de queue correspondant à la prochaine case libre, sans compteur. Dans cette conception, des indices égaux pourraient signifier que la file est vide ou pleine : une convention courante laisse donc une case inutilisée. La file est vide lorsque head == tail et pleine lorsque (tail + 1) % CAP == head. Ne mélange pas cette convention avec les règles de capacité de la version utilisant un compteur. Les files permettent aussi de visiter un arbre niveau par niveau en conservant les nœuds qui attendent leur tour.

Points clés

  • FIFO retire la valeur présente depuis le plus longtemps dans la file.
  • Une file chaînée ajoute en queue et retire en tête.
  • Retirer l’unique nœud doit réinitialiser les pointeurs de tête et de queue.
  • Une file circulaire ramène les indices au début et conserve un ordre logique indépendant de l’ordre physique.
  • Utilise un compteur ou une convention laissant une case inutilisée pour distinguer une file vide d’une file pleine.
  • Lis l’élément en tête avant de faire avancer l’indice de tête.
5

Construire et parcourir des arbres binaires

Les arbres remplacent une chaîne unique par des branches. Un arbre de recherche utilise l’ordre des clés pour choisir une branche, mais sa forme détermine le nombre de nœuds atteints par la recherche. La même idée de sous-arbre guide l’insertion, le parcours, le comptage et la suppression. Chaque opération a besoin d’un cas vide, d’une règle pour traiter le nœud courant et d’une décision claire sur le pointeur du parent qui doit recevoir la racine modifiée d’un sous-arbre.

5.1 Rechercher dans un arbre en suivant la règle d’ordre

Un arbre binaire donne à chaque nœud au plus deux enfants, appelés gauche et droit. La racine est le nœud au sommet. Une feuille n’a pas d’enfant, et un nœud accompagné de tous ses descendants forme un sous-arbre. Un enfant absent et un arbre vide sont représentés par NULL.

Représenter les branches
typedef struct tnode {
    int key;
    struct tnode *left;
    struct tnode *right;
} tnode;

Chaque pointeur vers un enfant mène à la racine d’un autre arbre. La même définition décrit donc une feuille, un grand arbre ramifié ou une longue chaîne. La hauteur compte les arêtes du plus long chemin de la racine à une feuille : une racine seule a une hauteur de 0. Utilise −1 pour un arbre vide, de sorte que les deux sous-arbres vides d’une feuille conduisent naturellement à la hauteur 0. Compter les niveaux donnerait un de plus.

Appliquer la règle aux sous-arbres entiers

Un arbre binaire de recherche ordonne les clés. Pour chaque nœud, toutes les clés de son sous-arbre gauche sont plus petites et toutes celles de son sous-arbre droit sont plus grandes. Stocke chaque clé une seule fois. Cette condition inclut les descendants éloignés. Une racine 50 avec un enfant gauche 30 et un enfant droit 60 sous ce 30 est invalide : 60 se trouve toujours dans le sous-arbre gauche de 50.

Insère 50, 30, 70, 20, 40, 60 et 80 dans un arbre binaire de recherche vide. La racine est 50 ; 30 et 70 sont ses enfants. Les enfants de 30 sont 20 et 40, et ceux de 70 sont 60 et 80. Chaque choix de branche préserve les bornes héritées des ancêtres.

Choisir une branche à chaque étape
#include <stddef.h>
tnode *search(tnode *t, int key) {
    while (t != NULL) {
        if (key == t->key) return t;
        if (key < t->key) t = t->left;
        else t = t->right;
    }
    return NULL;
}

La recherche de 60 dans l’exemple visite 50, va à droite vers 70, puis à gauche vers 60 : trois nœuds. La recherche de 65 suit le même chemin, puis essaie l’enfant droit de 60. Cet enfant vaut NULL : 65 est donc absent. Tu n’as pas besoin d’examiner le sous-arbre gauche de 50, car toutes ses clés sont inférieures à 50.

La version récursive traite d’abord t == NULL et une clé correspondante, puis effectue la recherche dans l’enfant choisi. Le test du sous-arbre vide est le cas de base. Sans lui, une recherche infructueuse finit par déréférencer un pointeur nul. La version avec boucle s’arrête à la même limite sans ajouter d’appels récursifs.

Expliquer le travail par la forme de l’arbre

Un arbre parfaitement équilibré à 20 niveaux contient 1 048 575 clés, et une recherche visite au plus 20 nœuds. Insérer à la place les clés déjà triées 10, 20, 30, 40, 50 crée une chaîne dirigée vers la droite. Sa hauteur est de 4, et trouver 50 visite les cinq nœuds. La seule règle d’ordre d’un arbre binaire de recherche ne garantit pas une faible hauteur. Les arbres autoéquilibrés comme les arbres AVL réorganisent les branches pour contrôler la hauteur.

Pour trouver un minimum, suis les enfants gauches jusqu’à ce que le lien gauche suivant vaille NULL. Pour trouver un maximum, suis les enfants droits. Dans l’exemple à sept nœuds, ces clés sont 20 et 80. Les mêmes choix de direction fonctionnent à la racine de n’importe quel sous-arbre, car la règle d’ordre s’y applique aussi.

Points clés

  • Un nœud binaire a au plus deux enfants ; les enfants absents sont des pointeurs nuls.
  • L’ordre d’un arbre binaire de recherche s’applique à chaque descendant de chaque sous-arbre.
  • La recherche suit la branche gauche pour une clé plus petite et la branche droite pour une clé plus grande.
  • Ici, la hauteur compte les arêtes, avec une hauteur de −1 pour un arbre vide.
  • Un arbre de faible hauteur réduit le nombre de nœuds visités ; un arbre binaire de recherche peut néanmoins devenir une chaîne.

5.2 Mettre à jour, parcourir et libérer un arbre de recherche

Les fonctions sur les arbres travaillent sur un sous-arbre et relient leur résultat à son parent. Garde explicite le cas du sous-arbre vide et distingue les opérations qui modifient les liens de celles qui ne font que visiter les nœuds.

Insérer par le lien du sous-arbre

Avec tnode contenant key, left et right, inclus <stdbool.h> et <stdlib.h> :

bool insert(tnode **link, int key) {
    if (*link == NULL) {
        tnode *n = malloc(sizeof *n);
        if (n == NULL) return false;
        *n = (tnode){.key = key, .left = NULL, .right = NULL};
        *link = n;
        return true;
    }
    if (key < (*link)->key) return insert(&(*link)->left, key);
    if (key > (*link)->key) return insert(&(*link)->right, key);
    return true;
}

Appelle la fonction avec insert(&root, key) et vérifie son résultat. Une clé existante laisse l’arbre inchangé et compte comme une réussite. L’adresse du lien permet à l’insertion de mettre à jour soit la racine de la fonction appelante, soit un pointeur vers un enfant. Avec une interface qui renvoie la racine, conserve chaque résultat : t->left = insert(t->left, key) et root = insert(root, key). Ignorer une allocation renvoyée alors que le sous-arbre initial était vide laisse ce nœud sans lien et provoque une fuite de mémoire.

Choisir quand visiter la racine
ParcoursOrdre de visiteUtilisation typique
InfixeGauche, racine, droiteAfficher les clés d’un arbre binaire de recherche dans l’ordre
PréfixeRacine, gauche, droiteVisiter les parents avant les enfants
PostfixeGauche, droite, racineTraiter les enfants avant de libérer un parent

Pour les insertions 50, 30, 70, 20, 40, 60, 80, le parcours infixe produit « 20 30 40 50 60 70 80 ». Le parcours préfixe donne « 50 30 20 40 70 60 80 » ; le parcours postfixe donne « 20 40 30 60 80 70 50 ».

void free_tree(tnode *t) {
    if (t == NULL) return;
    free_tree(t->left);
    free_tree(t->right);
    free(t);
}

Après free_tree(root), définis root = NULL chez le propriétaire. La fonction libère d’abord les enfants afin de pouvoir lire leurs adresses dans le parent encore valide. La taille est zéro pour un sous-arbre vide, sinon un plus la taille des deux enfants. La hauteur est −1 pour un sous-arbre vide, sinon un plus la plus grande hauteur des enfants. L’exemple a une taille de 7 et une hauteur de 2.

Supprimer sans rompre l’ordre

Une feuille devient NULL. Un nœud ayant un enfant est remplacé par cet enfant. Pour deux enfants, copie dans le nœud la plus petite clé du sous-arbre droit, puis supprime ce successeur de sa position d’origine :

tnode *erase(tnode *t, int key) {
    if (t == NULL) return NULL;
    if (key < t->key) t->left = erase(t->left, key);
    else if (key > t->key) t->right = erase(t->right, key);
    else {
        if (t->left == NULL || t->right == NULL) {
            tnode *child = t->left != NULL ? t->left : t->right;
            free(t);
            return child;
        }
        tnode *s = t->right;
        while (s->left != NULL) s = s->left;
        t->key = s->key;
        t->right = erase(t->right, s->key);
    }
    return t;
}

Appelle la fonction avec root = erase(root, key). Supprimer 50 de l’exemple place 60 dans la racine et retire l’ancien nœud 60. Le parcours infixe devient « 20 30 40 60 70 80 » ; le parcours préfixe devient « 60 30 20 40 70 80 ». Un successeur peut avoir un enfant droit : sa suppression doit donc reconnecter cet enfant.

Pour des clés de type mot, utilise le signe de strcmp, pas l’égalité des pointeurs, et donne à chaque mot possédé sa propre allocation. Libère le mot avant son nœud. La suppression d’enregistrements de ce type dont le nœud a deux enfants exige aussi de prévoir la propriété du mot déplacé et de toute valeur associée ; copier un pointeur propriétaire puis libérer le mot dans son nœud d’origine laisserait une clé pendante.

Points clés

  • Une insertion doit installer une nouvelle racine de sous-arbre et signaler un échec d’allocation.
  • Le parcours infixe affiche les clés d’un arbre binaire de recherche dans l’ordre ; le parcours préfixe commence par le parent.
  • Le nettoyage en parcours postfixe libère les deux enfants avant leur parent.
  • La taille additionne les tailles des deux enfants ; la hauteur utilise la plus grande hauteur des enfants.
  • La suppression reconnecte zéro ou un enfant, ou remplace la clé d’un nœud à deux enfants par celle de son successeur.
  • Conserve la racine renvoyée après une suppression, y compris celle de la racine d’origine.
  • Les arbres à clés de type chaîne comparent le texte et gèrent chaque allocation de chaîne dont ils sont propriétaires.
6

Stocker et rechercher des clés avec des tables de hachage

Les tables de hachage transforment une clé en emplacement de départ, puis résolvent les collisions éventuelles à cet endroit. Le hachage et le test d’égalité doivent s’accorder sur les clés considérées comme identiques. Une implémentation à chaînage séparé réunit tableaux, insertion dans une liste, copies de chaînes et nettoyage. Son utilité dépend de la répartition des clés entre les alvéoles et du recalcul de leurs emplacements lorsque le nombre d’alvéoles change.

6.1 Hacher les clés vers des alvéoles et gérer les collisions

Une table de hachage choisit un emplacement de départ à partir d’une clé. Une fonction de hachage produit un nombre ; réduire ce nombre modulo un nombre positif d’alvéoles donne un indice. Le chaînage séparé conserve une liste chaînée dans chaque alvéole : la recherche parcourt donc la chaîne sélectionnée.

Distinguer hachage et égalité

Une fonction de hachage utile est déterministe, rapide à calculer et répartit les clés attendues entre les alvéoles. Des clés égales doivent produire des résultats de hachage égaux. Des clés différentes peuvent néanmoins partager un indice : c’est une collision, et toute table doit la gérer. Lorsqu’il existe plus de clés possibles que d’alvéoles, certaines collisions sont inévitables.

Pour des mots commençant par une lettre sur un système compatible avec ASCII, toupper((unsigned char)word[0]) - 'A' attribue 26 alvéoles aux lettres ; « Luigi » commence dans l’alvéole 11. Beaucoup de mots anglais commencent par « s » et peu par « x » : cette règle peut donc produire des chaînes très inégales. La conversion explicite est nécessaire, car les fonctions de <ctype.h> exigent une valeur de caractère non signé ou EOF. Vérifie la première lettre et l’hypothèse sur le jeu de caractères avant d’utiliser cette règle d’indexation.

Un hachage par somme des caractères présente aussi des faiblesses. Avec les codes ASCII et dix alvéoles, « cat » donne la somme 99 + 97 + 116 = 312 et atteint l’alvéole 2 ; « dog » donne la somme 314 et atteint l’alvéole 4. « act » donne aussi la somme 312. L’addition perd l’ordre des lettres : toute permutation produit donc une collision.

Intégrer l’ordre des caractères au hachage
unsigned long hash_word(const char *word) {
    unsigned long h = 5381;
    const unsigned char *p = (const unsigned char *)word;
    while (*p != '\0') {
        h = h * 33 + *p;
        ++p;
    }
    return h;
}

Cette construction djb2 utilise des opérations non signées, dont le débordement provoque un rebouclage défini. Un débordement signé aurait un comportement indéfini. Pour les chaînes ASCII « cat » et « act », les valeurs sont 193488125 et 193486013. Réduis-les avec hash_word(word) % N, où N est le nombre positif d’alvéoles. L’ordre des lettres change ces résultats de hachage, même si leur réduction peut encore produire une collision.

Si une table considère « Cat » et « cat » comme égaux, le hachage et la comparaison doivent tous deux appliquer la même règle de casse. Ignorer la casse uniquement lors de la comparaison peut envoyer des clés égales dans des alvéoles différentes, où la recherche ne les comparera pas. Un reste négatif obtenu à partir d’un résultat de hachage signé ne convient pas non plus comme indice de tableau.

Choisir une stratégie de gestion des collisions

Le chaînage séparé stocke toutes les entrées en collision dans la liste chaînée d’une alvéole. La recherche compare les clés jusqu’à trouver une correspondance ou à atteindre la fin. L’adressage ouvert stocke les entrées dans le tableau lui-même. Avec le sondage linéaire, une collision conduit à essayer la case suivante, puis à revenir au début après la fin. La recherche suit la même séquence de sondage et peut donc examiner plusieurs cases du tableau.

Recalculer les indices lorsque la table grandit

Le facteur de charge est le nombre de clés stockées divisé par le nombre d’alvéoles. Douze clés dans huit alvéoles donnent en moyenne 1,5 entrée par alvéole. Seize alvéoles réduisent cette moyenne à 0,75. Cette moyenne ne garantit pas que chaque chaîne est courte ; une mauvaise fonction de hachage peut placer toutes les clés ensemble.

Un indice d’alvéole dépend du nombre d’alvéoles. Après avoir alloué et initialisé un tableau d’alvéoles plus grand, recalcule l’indice de chaque clé stockée pour le nouveau nombre et relie-la à son nouvel emplacement. Copier les anciens pointeurs de tête dans les nouvelles alvéoles de même numéro laisse les clés à des endroits où la nouvelle recherche ne les cherchera pas. Une table à chaînage séparé peut contenir plus d’entrées que d’alvéoles ; l’adressage ouvert exige des cases inutilisées pour les nouvelles entrées et un sondage fiable.

Points clés

  • Réduis un résultat de hachage déterministe modulo un nombre positif d’alvéoles pour obtenir un indice.
  • Des clés égales exigent des résultats de hachage égaux ; des clés distinctes peuvent produire une collision.
  • Les opérations non signées évitent un débordement signé indéfini dans le calcul du hachage.
  • Le chaînage parcourt une liste d’alvéole ; l’adressage ouvert suit une séquence de sondage.
  • Le facteur de charge mesure le nombre moyen d’entrées par alvéole, pas la longueur de la plus longue chaîne.
  • Hache à nouveau chaque clé lorsque le nombre d’alvéoles change.

6.2 Construire une table de comptage de mots propriétaire de ses clés

Une table à chaînage séparé combine un tableau, des liens de liste, la comparaison de chaînes et la propriété des allocations. Chaque entrée est propriétaire de sa clé copiée. Le tableau d’alvéoles est propriétaire des chaînes, et chaque chaîne se termine par NULL.

Initialiser les alvéoles et rechercher du texte
#include <stdbool.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#define N 26
typedef struct entry {
    char *key;
    int count;
    struct entry *next;
} entry;
static entry *table[N];

Avec une durée de stockage statique, chaque alvéole contient initialement un pointeur nul. Un tableau local a en revanche besoin d’un initialiseur tel que entry *table[N] = {NULL};. Utilise la fonction hash_word non signée qui commence à 5381 et intègre chaque octet avec le multiplicateur 33.

entry *find_word(const char *key) {
    size_t i = hash_word(key) % N;
    for (entry *e = table[i]; e != NULL; e = e->next)
        if (strcmp(e->key, key) == 0) return e;
    return NULL;
}

strcmp compare le texte. e->key == key vérifierait si les pointeurs désignent le même emplacement. Deux tampons distincts peuvent contenir des mots égaux : l’égalité des pointeurs ne permet donc pas de rechercher un mot.

Compter une clé existante ou en installer une nouvelle
bool add_word(const char *key) {
    entry *e = find_word(key);
    if (e != NULL) {
        if (e->count == INT_MAX) return false;
        ++e->count;
        return true;
    }
    e = malloc(sizeof *e);
    if (e == NULL) return false;
    e->key = malloc(strlen(key) + 1);
    if (e->key == NULL) {
        free(e);
        return false;
    }
    strcpy(e->key, key);
    e->count = 1;
    size_t i = hash_word(key) % N;
    e->next = table[i];
    table[i] = e;
    return true;
}

La fonction appelante fournit une chaîne terminée par un caractère nul. La vérification de INT_MAX signale un échec avant que le compteur ne dépasse la plage de son type int. L’octet supplémentaire alloué pour la chaîne contient '\0'. Si la seconde allocation échoue, libère l’entrée qui n’est pas encore reliée. Le pointeur de tête de l’alvéole ne change qu’après la réussite des deux allocations et de l’initialisation.

Enregistrer e->key = key conserverait à la place l’adresse de la zone de stockage de la fonction appelante. Si l’entrée réutilise sans cesse un même tampon, chaque pointeur enregistré pourrait montrer le mot le plus récent. Copier le texte donne à chaque entrée une durée de vie indépendante de ce tampon. strdup fournit cette opération dans POSIX et C23 ; l’allocation explicite et strcpy fonctionnent en C17.

Dans un exemple à dix alvéoles utilisant la somme des codes de caractères, insérer « cat », « dog », « act » laisse « act » → « cat » dans l’alvéole 2 et « dog » dans l’alvéole 4. La recherche de « tac » parcourt l’alvéole 2, compare les deux mots et atteint NULL. Une alvéole correspondante n’est qu’un emplacement possible, pas une preuve d’égalité des clés.

Libérer les charges utiles, les nœuds et les pointeurs de tête des alvéoles
void clear_table(void) {
    for (size_t i = 0; i < N; ++i) {
        entry *e = table[i];
        while (e != NULL) {
            entry *next = e->next;
            free(e->key);
            free(e);
            e = next;
        }
        table[i] = NULL;
    }
}

La sauvegarde de next et la libération de la clé ont lieu tant que l’entrée est encore valide. Réinitialiser chaque alvéole permet de la réutiliser. Les outils de vérification de la mémoire aident à vérifier les chemins exécutés ; les chemins d’échec d’allocation nécessitent aussi un nettoyage. L’ordre de hachage n’est pas un ordre trié. Pour afficher les clés par ordre alphabétique, rassemble-les pour les trier ou stocke-les dans un arbre binaire de recherche.

En adressage ouvert, la suppression nécessite un marqueur de suppression afin que la recherche traverse la case supprimée pour atteindre les clés en collision situées plus loin. Une case réellement inutilisée peut mettre fin à une recherche infructueuse. Les séquences de sondage doivent être bornées, et la table doit grandir avant que toutes les cases soient occupées.

Points clés

  • Initialise chaque pointeur de tête d’alvéole à NULL.
  • Le hachage sélectionne une chaîne ; strcmp confirme si une clé correspond.
  • Incrémente le compteur d’une clé existante au lieu d’ajouter une entrée en double.
  • Copie les chaînes d’entrée et libère l’entrée si l’allocation de la clé échoue.
  • Libère la clé avant son entrée, sauvegarde le successeur et réinitialise chaque alvéole.
  • En adressage ouvert, la suppression préserve les chemins de recherche à l’aide de marqueurs de suppression.

Structures et listes chaînées en C : exemples

6 Leçons Interactives

1

Suivre des enregistrements à travers copies et pointeurs

15 étapes
Construire un enregistrement de mesure, le passer à des fonctions, parcourir un tableau et distinguer les données intégrées des pointeurs propriétaires.
  1. 1
    IntroductionGarder ensemble les données d’une mesure

    Une mesure associe un identifiant et une température. Les conserver dans un même enregistrement, une structure C, permet au code de passer ou de stocker la paire comme une seule valeur.

    Suis cet enregistrement depuis sa définition jusqu’à un appel de fonction, puis dans un tableau. À chaque étape, distingue l’objet des pointeurs qui y donnent accès : cette distinction détermine si une modification touche une copie ou l’original, et si une copie superficielle d’un membre pointeur partage le stockage.

  2. 2
    ExempleDéfinir l’enregistrement

    La définition introduit un type ; la déclaration suivante crée un objet de ce type.

    struct reading { int id; double temp_c; };
    struct reading r = {.id = 24, .temp_c = 16.5};
    r.temp_c += 0.5;
    

    Le point sélectionne un membre de r : sa température est donc maintenant de 17,0 °C. Chaque membre a son propre type, et le point-virgule après l’accolade fermante termine la définition du type.

  3. 3
    Point cléCopier les valeurs des membres

    Crée maintenant un second enregistrement avec struct reading s = r;. Cette affectation copie les valeurs des deux membres. Modifier ensuite s.id ou s.temp_c laisse r inchangé.

    Un tableau stocké dans une structure est aussi copié. Le bourrage peut néanmoins différer entre les objets : tester l’égalité consiste donc à comparer les membres pertinents pour la tâche. Le C ne fournit pas d’opérateur == pour une structure entière.

  4. 4
    DéfiLire un membre omis

    Quelle valeur struct reading a = {.id = 31}; place-t-il dans a.temp_c, et le code peut-il lire cette valeur ?

    ---reveal---

    Le membre omis est initialisé à zéro : la température vaut donc 0.0 et peut être lue. Un initialiseur est présent même s’il ne nomme que id. Une déclaration d’un a local sans initialiseur exigerait en revanche d’affecter des valeurs à ses membres avant de les lire.

  5. 5
    ExplicationDonner un nom court au type

    Un typedef fournit un alias sans modifier le comportement des objets.

    typedef struct { int id; double temp_c; } reading;
    reading make(int id, double t) {
        return (reading){.id = id, .temp_c = t};
    }
    

    Un appel peut renvoyer l’enregistrement complet par valeur. Si un nœud doit faire référence à son propre type à l’intérieur d’une définition, donne-lui une étiquette et écris ce lien vers son propre type avec struct node *next.

  6. 6
    ExempleChoisir une copie ou une adresse

    Compare deux interfaces de fonctions :

    void warm_copy(reading x) { x.temp_c += 1.0; }
    void warm(reading *p) { p->temp_c += 1.0; }
    

    warm_copy(r) modifie une copie locale. warm(&r) accède à l’enregistrement de la fonction appelante et modifie son membre. La flèche signifie (*p).temp_c ; les parenthèses font en sorte que le déréférencement ait lieu avant que le point ne sélectionne le membre.

  7. 7
    ConseilAllouer un enregistrement avec un propriétaire clairement défini

    Inclus <stdlib.h> et alloue avec reading *p = malloc(sizeof *p);. Avant d’écrire à travers p, vérifie s’il vaut NULL et traite l’échec.

    En cas de réussite, initialise les champs et désigne un propriétaire responsable de free(p). Renvoyer une structure par valeur est sûr ; renvoyer l’adresse d’une structure locale laisse un pointeur pendant, car cet objet local cesse d’exister au retour de sa fonction.

  8. 8
    DéfiPrévoir quelle température change

    Une fonction appelante commence avec reading r = {24, 17.0};, appelle warm_copy(r), puis warm(&r) en utilisant les deux fonctions présentées. Quelle température r contient-il ?

    ---reveal---

    Il contient 18,0 °C. Le premier appel ne modifie que sa copie propre : la fonction appelante a donc toujours 17,0 lorsque le second appel commence. Celui-ci reçoit l’adresse de l’enregistrement de la fonction appelante et ajoute un à travers ce pointeur.

  9. 9
    ExempleParcourir un ensemble d’enregistrements

    Supposons qu’un tableau contienne les températures −5,5, −1,0 et −7,5 °C. Après avoir vérifié que le nombre d’éléments n’est pas nul, initialise best à l’indice 0 et compare chaque enregistrement suivant au meilleur trouvé.

    L’indice 1 remplace l’indice 0 parce que −1,0 est plus élevé que −5,5. L’indice 2 ne le remplace pas. Commencer à partir d’un élément réel fonctionne lorsque toutes les valeurs sont inférieures à zéro ; inventer un maximum initial de zéro ne permettrait pas de les trouver.

  10. 10
    ConseilRenvoyer un signe depuis la fonction de comparaison

    Pour des températures finies, une fonction de comparaison peut renvoyer (x->temp_c > y->temp_c) - (x->temp_c < y->temp_c). Le résultat est négatif, nul ou positif.

    Renvoyer la différence de température sous forme de int peut transformer une petite différence en zéro. Une soustraction d’entiers peut provoquer un débordement pour des clés extrêmes. Un signe fondé sur des comparaisons évite les deux erreurs, et qsort ne garantit toujours pas de conserver l’ordre des éléments égaux.

  11. 11
    ExplicationPréserver le tableau en cas d’échec du redimensionnement

    Pour un tableau dans le tas, conserve le nombre d’éléments utilisés séparément de la capacité. Vérifie que le nombre d’octets demandé est représentable, puis affecte à un pointeur temporaire le résultat de realloc pour une taille non nulle.

    En cas d’échec, le tableau d’origine reste disponible. En cas de réussite, installe ensemble la nouvelle adresse de base et la capacité. Reconstitue les pointeurs vers les éléments à partir de leurs indices sauvegardés, car l’allocation peut avoir été déplacée ou avoir remplacé son stockage précédent.

  12. 12
    LienVoir ce que partage une copie de pointeur

    Un enregistrement de personne contenant char *name stocke une adresse : person b = a; laisse donc les deux noms pointer vers une seule chaîne. C’est une copie superficielle.

    Si chaque enregistrement doit être propriétaire d’une chaîne indépendante, donne à b.name sa propre allocation vérifiée et copie le texte, y compris l’octet nul final. Chaque propriétaire peut alors libérer sa propre chaîne une fois. Copier un tableau de caractères intégré donne déjà des caractères distincts.

  13. 13
    DéfiCorriger la copie du nom

    Le name d’un enregistrement est une chaîne du tas dont il est propriétaire. Le code copie l’enregistrement, puis prévoit de libérer les deux noms indépendamment. Que doit-il se passer avant ce nettoyage ?

    ---reveal---

    Alloue un nom distinct pour la copie, vérifie la réussite et copie le texte d’origine avant de libérer l’une ou l’autre chaîne. Les deux enregistrements partagent actuellement une adresse. Libérer les deux pointeurs partagés libérerait deux fois le même bloc ; copier à nouveau le pointeur n’y changerait rien.

  14. 14
    DéfiRenvoyer un enregistrement avec un libellé intégré

    Une fonction renvoie par valeur une structure contenant char label[12]. Le libellé devient-il pendant parce que la structure d’origine était locale ? Explique en utilisant les notions de durée de vie et de copie.

    ---reveal---

    Non. Le retour par valeur copie les valeurs des membres de la structure, y compris le tableau intégré, dans la valeur renvoyée. La fonction appelante est propriétaire de l’enregistrement obtenu. Renvoyer un pointeur vers la structure locale demanderait en revanche à la fonction appelante d’accéder à un objet dont la durée de vie est terminée.

  15. 15
    RécapitulatifSuivre les valeurs et les propriétaires

    Un objet de type structure stocke des valeurs de membres. Un pointeur désigne un objet, et un pointeur copié désigne toujours la même cible. Les paramètres de fonctions et les valeurs de retour suivent cette distinction.

    Pour les tableaux, garde les nombres d’éléments explicites et préserve le propriétaire pendant le redimensionnement. Pour les enregistrements comportant des pointeurs propriétaires, décide si une copie partage le stockage ou reçoit des allocations indépendantes. Ces décisions déterminent les mises à jour et le nettoyage.

2

Maintenir la cohérence entre les choix et les charges utiles des unions

11 étapes
Suivre les valeurs d’énumération, rejeter les valeurs manquantes, construire une cellule étiquetée et choisir entre des champs simultanés ou alternatifs.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionNommer le choix et identifier sa charge utile
  2. 2
    ExempleSuivre les valeurs explicites d’une énumération
  3. 3
    Point cléVérifier l’ensemble autorisé
  4. 4
    ConseilEmpêcher l’enchaînement des cas
  5. 5
    DéfiValider une valeur d’étape
  6. 6
    ExplicationSuperposer les valeurs possibles
  7. 7
    ExempleDéfinir les deux parties d’une valeur étiquetée
  8. 8
    DéfiRepérer la mise à jour incohérente
  9. 9
    ConseilStocker un indicateur dans une petite plage
  10. 10
    DéfiChoisir le stockage d’un enregistrement
  11. 11
    RécapitulatifGarder chaque description cohérente avec ses données
3

Suivre la création, l’insertion et la suppression dans une liste

16 étapes
Utiliser les liens entrants pour préserver une chaîne, puis maintenir les pointeurs d’extrémité, les compteurs et les charges utiles possédées.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionModifier une chaîne un lien à la fois
  2. 2
    ExplicationCommencer avec un pointeur de tête nul
  3. 3
    ExempleInsérer en tête sans perdre la chaîne
  4. 4
    Point cléPermettre à la fonction appelante de conserver le nouveau pointeur de tête
  5. 5
    DéfiPrévoir l’ordre après des insertions en tête
  6. 6
    ExplicationSuivre l’adresse d’un lien
  7. 7
    ExempleTrouver un point d’insertion ordonnée
  8. 8
    DéfiInsérer avant la tête
  9. 9
    ExempleRetirer un nœud du milieu en toute sécurité
  10. 10
    ConseilS’arrêter sans risque lorsqu’une valeur est absente
  11. 11
    ExplicationAjouter un pointeur de queue et un compteur
  12. 12
    LienUtiliser un lien vers le précédent lorsque la suppression l’exige
  13. 13
    DéfiChoisir le stockage pour des lectures répétées par indice
  14. 14
    ConseilLibérer une charge utile avant son nœud
  15. 15
    DéfiCombiner suppression et métadonnées
  16. 16
    RécapitulatifGarder valide le chemin d’accès entrant
4

Suivre les opérations d’une pile et le retour au début d’une file

14 étapes
Évaluer des opérations imbriquées, maintenir les pointeurs d’extrémité d’une file chaînée et suivre l’ordre logique d’une file circulaire.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionChoisir le prochain élément en attente
  2. 2
    ExplicationUtiliser top comme nombre d’éléments
  3. 3
    ExempleSuivre l’ordre d’une pile
  4. 4
    ExempleFaire correspondre le délimiteur fermant à celui au sommet
  5. 5
    ExempleGarder les opérandes postfixés dans l’ordre
  6. 6
    DéfiVérifier une expression postfixée modifiée
  7. 7
    ExplicationDistinguer la tête de la queue
  8. 8
    ConseilCorriger le cas d’un seul nœud
  9. 9
    ExplicationUtiliser un anneau plutôt que décaler les éléments
  10. 10
    ExempleSuivre une disposition avec retour au début
  11. 11
    DéfiTrouver la prochaine position d’insertion
  12. 12
    Point cléGarder une seule règle pour l’état plein
  13. 13
    DéfiChoisir l’ordre de traitement
  14. 14
    RécapitulatifFaire correspondre l’ordre et l’état
5

Construire un arbre binaire de recherche, y rechercher une clé, le parcourir et y supprimer une clé

15 étapes
Suivre un arbre à travers le choix des branches, l’insertion, les ordres de parcours, le remplacement par un successeur et le nettoyage.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionSuivre un arbre de la racine au sous-arbre
  2. 2
    Point cléAppliquer la règle sous chaque ancêtre
  3. 3
    ExempleConstruire l’arbre de l’exemple
  4. 4
    ExempleRechercher une branche à la fois
  5. 5
    DéfiDiagnostiquer une violation impliquant un descendant
  6. 6
    LienMesurer la forme de l’arbre
  7. 7
    ExempleRelier une insertion à l’endroit où la recherche s’arrête
  8. 8
    ExempleChoisir un ordre de visite
  9. 9
    ExplicationCompter les branches et mesurer la longueur du chemin
  10. 10
    ExplicationTraiter les cas de zéro ou un enfant
  11. 11
    ExempleRemplacer la clé d’un nœud à deux enfants
  12. 12
    DéfiPréserver l’enfant du successeur
  13. 13
    ConseilLibérer les descendants avant leur parent
  14. 14
    DéfiVérifier ensemble le parcours et la hauteur
  15. 15
    RécapitulatifReconnecter chaque sous-arbre modifié
6

Construire un compteur de mots avec une table de hachage

14 étapes
Suivre les collisions, l’égalité, les clés copiées, l’agrandissement, les échecs d’allocation et le nettoyage complet.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionUtiliser une clé pour réduire le champ de recherche
  2. 2
    ExempleCalculer une alvéole de départ
  3. 3
    Point cléGarder l’égalité et le hachage cohérents
  4. 4
    DéfiDistinguer une collision d’une correspondance
  5. 5
    ExplicationUtiliser le facteur de charge sans oublier la répartition
  6. 6
    ExempleRecalculer les emplacements après l’agrandissement
  7. 7
    ExplicationRechercher du texte dans une chaîne
  8. 8
    ExempleInstaller une nouvelle clé possédée
  9. 9
    DéfiGérer une allocation partielle
  10. 10
    ConseilNe pas conserver un tampon d’entrée réutilisé
  11. 11
    ExplicationPréserver les chemins de sondage après une suppression
  12. 12
    DéfiCombiner gestion des collisions et comptage des mots
  13. 13
    LienLibérer chaque clé et chaque nœud
  14. 14
    RécapitulatifFaire concorder emplacement, égalité et durée de vie

Structures et listes chaînées en C : QCM

50 Questions Pratiques en 5 séries

Série 1 : Lire des enregistrements et suivre les opérations de base10 Questions Pratiques

Question 1 sur 10

Quelle valeur r.temp_c a-t-il après struct reading { int id; double temp_c; }; struct reading r = {.id = 12}; ?
  1. Une valeur indéterminée
  2. 0.0
  3. 12.0
  4. Une erreur d’initialisation
Afficher la réponseMasquer la réponse
Réponse : B
L’initialiseur désigné définit id et initialise à zéro le membre omis temp_c : sa valeur est donc 0.0. Cela diffère de la déclaration d’une structure locale sans aucun initialiseur. L’initialiseur fourni rend le membre omis utilisable sans affectation ultérieure.

Question 2 sur 10

Un reading *p valide pointe vers un enregistrement de température. Quelle expression donne accès à son membre temp_c ?
  1. p.temp_c
  2. *p.temp_c
  3. &p.temp_c
  4. p->temp_c
Afficher la réponseMasquer la réponse
Réponse : D
L’opérateur flèche sélectionne un membre à travers un pointeur et équivaut à (*p).temp_c. Le point exige un objet de type structure à sa gauche : p.temp_c est donc invalide ici. Écrire *p.temp_c ne résout pas le problème, car le point est appliqué avant le déréférencement.

Question 3 sur 10

Des enregistrements contiennent les températures finies −8,0 ; −2,5 et −6,0 °C aux indices 0, 1 et 2. Une recherche du maximum commence avec best = 0 et le met à jour pour un enregistrement de température strictement supérieure. Quel indice reste à la fin ?
  1. 1
  2. 0
  3. 2
  4. Aucun indice ; le maximum reste à zéro
Afficher la réponseMasquer la réponse
Réponse : A
Le parcours commence par l’enregistrement d’indice 0, puis le remplace par celui d’indice 1 parce que −2,5 est supérieur à −8,0. La dernière température, −6,0, n’est pas supérieure à −2,5 : l’indice 1 reste donc. Les températures négatives ne posent pas de problème particulier lorsque le meilleur élément initial est un enregistrement réel.

Question 4 sur 10

Quelle valeur PAUSED a-t-il dans enum state { IDLE = 3, RUNNING, FAILED = 9, PAUSED }; ?
  1. 5
  2. 4
  3. 10
  4. 9
Afficher la réponseMasquer la réponse
Réponse : C
Une valeur implicite d’énumération est égale à la constante d’énumération immédiatement précédente plus un. FAILED fixe explicitement la séquence à 9 : PAUSED vaut donc 10. Compter PAUSED depuis le début de la déclaration ignorerait la reprise du comptage introduite par la valeur explicite.

Question 5 sur 10

Une union étiquetée possède une étiquette d’énumération kind et une union as avec les membres int i et double d. Une valeur existante porte l’étiquette K_DOUBLE. Quelle mise à jour stocke l’entier 18 de façon cohérente ?
  1. t.kind = K_DOUBLE; t.as.i = 18;
  2. t.kind = K_INT; t.as.d = 18.0;
  3. t.kind = K_INT; t.as.i = 18;
  4. t.kind = K_DOUBLE; t.as.d = 18.0;
Afficher la réponseMasquer la réponse
Réponse : C
L’étiquette doit indiquer quel membre contient la valeur stockée. Affecter K_INT et écrire dans as.i fait correspondre les deux parties à l’entier 18. Écrire dans le membre entier tout en conservant l’étiquette double conduirait le code ultérieur à lire la mauvaise représentation ; l’étiquette ne change pas automatiquement.

Question 6 sur 10

Une liste vide reçoit des insertions en tête de 6, 11, puis 3. Quelles valeurs apparaissent lorsqu’elle est parcourue depuis sa tête ?
  1. 6 → 11 → 3
  2. 3 → 11 → 6
  3. 11 → 6 → 3
  4. 3 → 6 → 11
Afficher la réponseMasquer la réponse
Réponse : B
Chaque insertion en tête place le nouveau nœud avant la tête actuelle. Les têtes successives sont donc 6, puis 11, puis 3, avec les nœuds précédents reliés derrière elles. L’ordre d’arrivée 6 → 11 → 3 résulterait d’ajouts en fin de liste, pas des insertions en tête indiquées.

Question 7 sur 10

Une liste triée contient 1 → 7 → 12. Une boucle d’insertion avance tant que la valeur courante est inférieure à 9. À quel lien entrant s’arrête-t-elle ?
  1. Au pointeur de tête qui pointe vers le nœud contenant 1
  2. Au champ next du nœud contenant 1
  3. Au champ next du nœud contenant 12
  4. Au champ next du nœud contenant 7
Afficher la réponseMasquer la réponse
Réponse : D
La boucle passe 1 et 7, puis s’arrête avant 12 parce que 12 n’est pas inférieur à 9. Le lien qui atteint actuellement 12 est le membre next du nœud 7. S’arrêter au lien après 12 ajouterait 9 en fin de liste et violerait l’ordre croissant de la liste.

Question 8 sur 10

Une pile vide exécute les opérations suivantes : empiler 8, empiler 2, dépiler, empiler 5, dépiler, dépiler. Quelles valeurs les trois dépilements renvoient-ils ?
  1. 2, 5, 8
  2. 8, 2, 5
  3. 2, 8, 5
  4. 5, 2, 8
Afficher la réponseMasquer la réponse
Réponse : A
Le premier dépilement retire 2, la valeur la plus récente à ce moment-là. Empiler 5 le place au-dessus du 8 restant : les dépilements suivants renvoient donc 5 et 8. Renvoyer 8 en premier suivrait l’ordre d’une file et ne correspondrait pas au fonctionnement dernier entré, premier sorti d’une pile.

Question 9 sur 10

Un arbre binaire de recherche est construit en insérant 42, 18, 63, 27 et 21. Quels nœuds une recherche de 21 visite-t-elle ?
  1. 42 → 18 → 21
  2. 42 → 63 → 27 → 21
  3. 18 → 27 → 21
  4. 42 → 18 → 27 → 21
Afficher la réponseMasquer la réponse
Réponse : D
À 42, 21 est plus petit : la recherche va donc à gauche vers 18 ; elle va ensuite à droite vers 27, puis à gauche vers 21. L’insertion de 21 l’a placé sous 27, pas directement sous 18. Une recherche suit les liens existants vers les enfants et ne peut pas sauter directement à une clé plus petite simplement parce qu’on sait qu’elle est présente.

Question 10 sur 10

Une table déjà remplie traite les mots ASCII sans tenir compte de la casse, mais hache leurs octets d’origine. Une recherche de « mint » manque une entrée « Mint » existante sans la visiter. Quelle correction complète prend en charge les entrées existantes et futures ?
  1. Conserver le hachage des octets bruts, ignorer la casse pour l’égalité et recalculer les mêmes emplacements.
  2. Ignorer la casse pour le hachage, la laisser ignorée pour l’égalité et recalculer les emplacements.
  3. Ignorer la casse pour le hachage, la laisser ignorée pour l’égalité et conserver les anciens emplacements.
  4. Conserver le hachage des octets bruts, comparer les adresses et agrandir le tableau d’alvéoles.
Afficher la réponseMasquer la réponse
Réponse : B
Le hachage doit traiter de la même façon les graphies considérées comme égales : normalise donc la casse avant le hachage et conserve la comparaison qui ignore la casse. Les entrées existantes doivent aussi être placées selon la règle corrigée. Ne modifier que les hachages futurs laisse les anciennes entrées à des emplacements que leurs recherches corrigées pourraient ne jamais examiner.
Série 2 : Appliquer les règles d’état et de stockage10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

struct tag { char name[8]; int id; }; est utilisé pour a et b. Après b = a; b.name[0] = 'Z';, qu’arrive-t-il à a.name[0] ?
  1. Il devient le caractère Z.
  2. Il devient une valeur indéterminée.
  3. Il conserve son caractère d’origine.
  4. L’affectation de structure ne peut pas compiler.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Avec void cool(reading r) { r.temp_c -= 2.0; }, une fonction appelante définit reading a = {9, 14.0}; et appelle cool(a);. Quelle est ensuite la valeur de a.temp_c dans la fonction appelante ?
  1. 14.0
  2. 12.0
  3. 16.0
  4. Une valeur indéterminée
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Quel texte printf("%d", WED); affiche-t-il pour enum day { MON, TUE, WED, THU }; ?
  1. « WED »
  2. « 3 »
  3. « Wednesday »
  4. « 2 »
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une union étiquetée stocke un double sous l’étiquette K_DOUBLE. Plus tard, le code ne change que l’étiquette en K_INT et une fonction d’affichage lit as.i. Quel est le défaut ?
  1. Changer l’étiquette convertit la valeur stockée en entier.
  2. La nouvelle étiquette sélectionne un membre qui n’a pas été écrit.
  3. L’union conserve les deux valeurs typées jusqu’à sa libération.
  4. La fonction d’affichage doit garder l’ancienne étiquette et changer son format.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une liste simplement chaînée conserve ses pointeurs de tête et de queue ainsi qu’un compteur. Son unique nœud est retiré et libéré. Quel état doit subsister ?
  1. head = NULL; tail = NULL; count = 0;
  2. head = NULL; tail = old; count = 0;
  3. head = old; tail = NULL; count = 0;
  4. head = NULL; tail = NULL; count = 1;
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Quel résultat l’expression postfixée « 15 6 - 3 / » produit-elle avec des opérandes entiers ?
  1. -3
  2. 7
  3. -7
  4. 3
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une file circulaire avec compteur a une capacité de 5, head = 4 et count = 2. Dans quelle case le prochain enfilement écrira-t-il ?
  1. Case 0
  2. Case 2
  3. Case 1
  4. Case 4
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Quel parcours libère chaque sous-arbre enfant avant de libérer son parent ?
  1. Parcours préfixe
  2. Parcours postfixe
  3. Parcours infixe
  4. Parcours de la racine seule
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une table à chaînage séparé à clés entières utilise la clé modulo le nombre d’alvéoles. Elle insère 13, 25 et 23 en tête dans 6 alvéoles vides. Elle passe ensuite à 10 alvéoles, en parcourant les anciennes alvéoles par indice croissant et chaque chaîne depuis sa tête, puis en insérant chaque entrée en tête de sa nouvelle alvéole. Quelles chaînes obtient-on ?
  1. Alvéole 3 : 13 → 23 ; alvéole 5 : 25
  2. Alvéole 1 : 25 → 13 ; alvéole 5 : 23
  3. Alvéole 3 : 23 → 13 ; alvéole 5 : 25
  4. Alvéole 3 : 23 ; alvéole 5 : 25 → 13
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Un compteur de mots à chaînage séparé, initialement vide, utilise une fonction de hachage qui envoie « ash » et « elm » dans la même alvéole. Il lit dans un tampon réutilisé, puis recherche ce texte et incrémente une correspondance ou alloue un nœud avec un compteur à 1. Les nouveaux nœuds stockent incorrectement key = buffer. Il lit « ash », puis « elm ». Que reste-t-il avant le nettoyage ?
  1. Une entrée affichant « elm » avec un compteur à 2
  2. Deux entrées, « ash » et « elm », chacune avec un compteur à 1
  3. Une entrée affichant « ash » avec un compteur à 2
  4. Deux entrées affichant toutes deux « elm », chacune avec un compteur à 1
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 3 : Suivre les liens, le retour au début et les collisions10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une fonction de comparaison de températures finies renvoie x->temp_c - y->temp_c sous forme de int. Deux enregistrements distincts ont des températures de 6,20 et 6,45 °C. Pourquoi le tri peut-il les considérer comme égaux ?
  1. Les arguments const void * forcent les deux entrées à devenir des entiers.
  2. La fonction de comparaison doit renvoyer exactement leur différence de température.
  3. Les deux valeurs finies sont égales après que qsort les a copiées.
  4. La différence fractionnaire devient nulle lors de la conversion.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Quelle plage de valeurs convient à unsigned int mode : 4 ?
  1. De 0 à 4
  2. De 0 à 15
  3. De 1 à 16
  4. De −8 à 7
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Un nœud est inséré en tête d’une liste non vide avec head = n; n->next = head; après l’allocation de n. Vers quoi pointe le next du nouveau nœud ?
  1. Vers l’ancien nœud de tête
  2. Vers l’ancien dernier nœud
  3. Vers le nouveau nœud lui-même
  4. Vers le marqueur nul de fin
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une liste croissante commence par 3 → 8 → 12. Insère 6, supprime le premier nœud, puis insère 8 avec une boucle qui avance tant que la valeur courante est strictement inférieure à la valeur insérée. Où le nouveau 8 est-il relié ?
  1. Après 6 et avant le 8 existant
  2. Après le 8 existant et avant 12
  3. Avant 6 et après le 3 supprimé
  4. Après 12 et avant le lien nul
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un programme lit régulièrement des enregistrements par leur indice et parcourt tous les enregistrements, avec peu d’insertions. Quel stockage convient le mieux à ces opérations ?
  1. Une liste simplement chaînée d’enregistrements
  2. Une liste doublement chaînée d’enregistrements
  3. Une file chaînée de nœuds d’enregistrements
  4. Un tableau contigu d’enregistrements
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une file chaînée vide enfile 12 et 30 avec succès, défile une fois, échoue à allouer lors de l’enfilement de 41, défile à nouveau, puis enfile 44 avec succès. Des opérations correctes préservent l’état en cas d’échec. Quels sont les pointeurs d’extrémité et les valeurs en attente à la fin ?
  1. La tête pointe vers 30, la queue vers 44 ; valeurs 30 → 44.
  2. La tête pointe vers 44, tandis que la queue est nulle.
  3. Les deux pointeurs d’extrémité pointent vers l’unique nœud, 44.
  4. La tête pointe vers 12, la queue vers 44 ; valeurs 12 → 44.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Un arbre a pour racine 36, pour enfant gauche 17 et pour enfant droit de 17 la valeur 41. Pourquoi ne respecte-t-il pas la règle d’un arbre binaire de recherche ?
  1. 17 est inférieur à son parent et se trouve à gauche.
  2. 41 se trouve dans le sous-arbre gauche de 36 mais dépasse 36.
  3. 41 est supérieur à son parent et se trouve à droite.
  4. 36 n’a qu’un seul enfant direct au lieu de deux.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un arbre binaire de recherche est construit à partir de 48, 24, 75, 62, 68, 90. Supprime 48 en utilisant son successeur infixe. Quelle est la nouvelle racine et quel est l’enfant gauche restant de 75 ?
  1. Racine 62 ; enfant gauche 68
  2. Racine 62 ; enfant gauche NULL
  3. Racine 75 ; enfant gauche 62
  4. Racine 68 ; enfant gauche 62
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une table à chaînage séparé contient 21 clés stockées et 14 alvéoles. Quel est son facteur de charge ?
  1. 1.5
  2. 0.67
  3. 7
  4. 35
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une recherche dans une table de hachage atteint une chaîne contenant « elm » et « ash » alors qu’elle cherche « oak ». Les deux comparaisons de texte échouent. Que doit-elle renvoyer après avoir atteint la fin de la chaîne ?
  1. L’entrée de tête pour « elm »
  2. La dernière entrée pour « ash »
  3. NULL
  4. Une nouvelle entrée pour « oak »
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 4 : Diagnostiquer les erreurs de copie et de limites10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Sur une plateforme où char occupe 1 octet, int occupe 4 octets et est aligné sur 4 octets, et où la taille des structures est arrondie à un multiple de 4, A possède les membres char, int, char dans cet ordre ; B possède int, char, char. Quel espace total un tableau de cinq enregistrements A utilise-t-il par rapport à cinq enregistrements B ?
  1. A utilise 60 octets ; B utilise 40 octets.
  2. A utilise 30 octets ; B utilise 30 octets.
  3. A utilise 45 octets ; B utilise 30 octets.
  4. A utilise 60 octets ; B utilise 30 octets.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un paramètre de fonction est const reading *p. Quelle affectation peut modifier le pointeur local sans modifier un enregistrement ?
  1. p->id = 19;
  2. (*p).temp_c = 8.0;
  3. p = another_reading_pointer;
  4. p->temp_c += 0.5;
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

a.name est propriétaire d’une chaîne dans le tas. Après person b = a;, le code alloue avec succès une chaîne temporaire copy, mais ne l’a pas remplie et n’a modifié aucun des deux enregistrements. Les deux enregistrements doivent finalement être propriétaires de chaînes indépendantes. Quelle séquence y parvient sans perdre de données ni d’allocation ?
  1. Affecter a.name = copy ; copier b.name dans a.name ; libérer a.name deux fois.
  2. Copier a.name dans copy ; affecter b.name = copy ; libérer chaque nom une fois.
  3. Libérer a.name ; copier b.name dans copy ; affecter b.name = copy.
  4. Copier a.name dans copy ; laisser b.name partagé ; libérer les deux noms.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une entrée est destinée à enum mode { M_IDLE = 0, M_READ = 4, M_WRITE = 5 };. Quel test accepte exactement ses valeurs nommées ?
  1. x >= 0 && x <= 5
  2. x >= 4 && x <= 5
  3. x == 0 || x == 4 || x == 6
  4. x == 0 || x == 4 || x == 5
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Pendant le nettoyage d’une liste, p est valide et le code exécute free(p); p = p->next;. Quelle modification préserve le parcours prévu en toute sécurité ?
  1. Affecter d’abord p = p->next, puis libérer le nouveau p.
  2. Sauvegarder d’abord next, libérer p, puis affecter p = next.
  3. Libérer d’abord p->next, puis affecter p = p->next.
  4. Définir d’abord p->next = NULL, puis continuer après avoir libéré p.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une liste triée 2 → 6 → 6 → 14 utilise une boucle qui avance tant que number < value. Un nouveau 6 est inséré. Où va-t-il ?
  1. Entre 2 et le premier 6 existant
  2. Entre les deux nœuds 6 existants
  3. Entre le second 6 et 14
  4. Après 14, à la fin de la liste
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une pile sur tableau a un top = 0 signé, et le dépilement lit items[--top] sans vérification. Quel indice essaie-t-il de lire ?
  1. 0
  2. 1
  3. -1
  4. Le dernier indice valide du tableau
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une file circulaire de capacité 6 utilise un compteur. Elle commence avec head = 5, count = 3, et les valeurs P, Q, R dans l’ordre de la file. Elle défile deux fois, puis enfile S et T. Quels sont l’indice de tête, le compteur et la valeur en tête à la fin ?
  1. Tête 0 ; compteur 3 ; valeur en tête Q
  2. Tête 1 ; compteur 5 ; valeur en tête R
  3. Tête 3 ; compteur 3 ; valeur en tête T
  4. Tête 1 ; compteur 3 ; valeur en tête R
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un arbre binaire de recherche contient la racine 31, l’enfant gauche 12 avec l’enfant droit 19, et l’enfant droit 54. Quelle sortie en parcours préfixe reste après la suppression de 12 ?
  1. 19, 31, 54
  2. 31, 54, 19
  3. 31, 19, 54
  4. 31, 12, 19, 54
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une nouvelle entrée de table est allouée, mais l’allocation de sa chaîne de clé échoue avant qu’elle soit reliée. Quel nettoyage préserve la table et évite une fuite de mémoire ?
  1. Libérer la clé de la fonction appelante et renvoyer une réussite.
  2. Libérer la nouvelle entrée et renvoyer un échec.
  3. La relier avec une clé nulle et renvoyer une réussite.
  4. Renvoyer un échec mais conserver la nouvelle entrée.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 5 : Combiner les règles dans des cas nouveaux10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une boucle essaie de supprimer tous les 5 de 5 → 5 → 9 :

node **link = &head;
while (*link != NULL) {
    if ((*link)->number == 5) {
        node *old = *link;
        *link = old->next;
        free(old);
    }
    if (*link != NULL) link = &(*link)->next;
}

Pourquoi reste-t-il un 5, et quelle modification corrige la boucle ?

  1. Elle saute un nœud libéré ; libérer le successeur avant d’avancer.
  2. Elle saute une correspondance conservée ; avancer seulement après une non-correspondance.
  3. Elle perd le lien de tête ; recommencer la boucle après chaque non-correspondance.
  4. Elle supprime le mauvais nœud ; libérer avant de lire le successeur.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un tableau dans le tas contient trois enregistrements utilisés, chacun propriétaire d’une chaîne de nom distincte, et un pointeur vers l’enregistrement 1 a été sauvegardé. Un redimensionnement à une capacité de 6 réussit. Le programme prévoit de retirer l’enregistrement 0 en décalant les deux autres vers la gauche et en réduisant le nombre d’éléments utilisés à 2. Quelle méthode conserve l’accès à l’enregistrement 1 d’origine et libère tout le stockage possédé ?
  1. Libérer le nom retiré ; décaler ; reconstruire le pointeur à l’indice 0 ; libérer ensuite les noms actifs et le tableau.
  2. Libérer le nom retiré ; décaler ; garder l’ancien pointeur ; libérer ensuite les six cases de capacité et le tableau.
  3. Décaler ; reconstruire le pointeur à l’indice 1 ; libérer ensuite seulement les deux noms actifs et le tableau.
  4. Décaler ; reconstruire le pointeur à l’indice 0 ; libérer ensuite les noms des trois anciennes cases et le tableau.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Un champ non signé de 3 bits reçoit à l’exécution la valeur 13. Quelle valeur est stockée ?
  1. 13
  2. 7
  3. 1
  4. 5
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une liste est 4 → 9 → 16. Le code sauvegarde node **link = &head->next;, retire et libère le nœud de tête 4, puis utilise *link pour supprimer 9. Pourquoi cette seconde opération est-elle invalide, et quel lien entrant devrait-elle utiliser maintenant ?
  1. link pointe vers le nœud 9 encore valide ; utiliser l’adresse du champ suivant de 9.
  2. link pointe dans le nœud 16 encore valide ; utiliser l’adresse du champ suivant de 16.
  3. link pointe dans le nœud 4 libéré ; utiliser l’adresse du pointeur de tête actuel.
  4. link est l’adresse du pointeur de tête actuel ; utiliser l’adresse de l’ancien nœud 4.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une liste doublement chaînée est A ↔ B ↔ C, avec A en tête et C en queue. Retire B, ajoute D en fin, puis retire C, en maintenant les liens dans les deux sens. Quels sont le nœud de queue final et son prédécesseur ?
  1. Queue D ; son prédécesseur A
  2. Queue C ; son prédécesseur A
  3. Queue D ; son prédécesseur B
  4. Queue A ; son prédécesseur D
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un évaluateur d’expressions postfixées traite correctement la division, mais inverse les deux opérandes de chaque soustraction. Pour « 20 8 - 3 / 2 - », quels sont le résultat correct et celui de l’évaluateur défectueux ? Toutes les divisions sont ici des divisions entières exactes.
  1. Résultat correct 6 ; résultat défectueux 2
  2. Résultat correct 2 ; résultat défectueux −6
  3. Résultat correct 2 ; résultat défectueux 6
  4. Résultat correct −2 ; résultat défectueux 6
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une file vide enfile 22, puis 7, puis 15. Que renvoie son premier défilement ?
  1. 15
  2. 7
  3. Un échec dû à une file vide
  4. 22
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Insère les clés distinctes 9, 16, 23, 30 dans cet ordre dans un arbre binaire de recherche vide. Si la hauteur est mesurée en arêtes, quelle est sa hauteur ?
  1. 4
  2. 3
  3. 2
  4. 1
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un arbre binaire de recherche n’a initialement que la racine 40. Dans une interface qui renvoie la racine, le code appelle insert(root->left, 20) et ignore l’allocation renvoyée, puis conserve root = insert(root, 60) et enfin root = erase(root, 40). Une insertion réussie relie en interne les résultats pour les enfants ; la suppression reconnecte les enfants conservés. Que reste-t-il ?
  1. Racine 60 avec enfant gauche 20 ; aucune allocation n’est perdue.
  2. Racine 20 avec enfant droit 60 ; aucune allocation n’est perdue.
  3. Racine 40 avec enfant droit 60 ; le nœud 20 est perdu.
  4. Racine 60 seule ; le nœud 20 alloué est perdu.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une table à adressage ouvert de taille 7 utilise le sondage linéaire. Trois clés hachées vers la case 5 occupent les cases 5, 6 et 0. La clé de la case 6 est supprimée. Pourquoi la case 6 doit-elle conserver un marqueur de suppression au lieu de devenir une case inutilisée ?
  1. Une recherche de la clé de la case 0 doit continuer au-delà de la case 6.
  2. Une recherche de la clé de la case 5 doit déplacer sa clé vers la case 6.
  3. Une recherche doit considérer que la clé supprimée est toujours présente.
  4. Une recherche doit reprendre son sondage à la case 0 chaque fois.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Tu bloques sur ton propre exercice ? Aide aux Devoirs

Mots-Clés

72 Mots-Clés, chacun avec sa définition

A

Adressage ouvert
L’adressage ouvert stocke les entrées directement dans les cases d’un tableau et résout les collisions en recherchant selon une séquence définie de cases alternatives.
Une recherche peut examiner plusieurs cases même si elle commence à un seul indice de hachage calculé à partir de la clé.
Alignement
L’alignement est une exigence de placement de l’adresse d’un objet liée à son type, qui peut provoquer des espaces entre les membres ou à la fin d’une structure.
Un membre entier peut commencer après un espace suivant un membre caractère afin que son adresse soit correctement alignée.
Alvéole
Une alvéole est un emplacement sélectionné par un indice de table de hachage ; en chaînage séparé, elle stocke le pointeur de tête d’une liste d’entrées qui lui sont attribuées.
Une table de dix alvéoles utilisant les sommes des codes de caractères ASCII place « dog » dans l’alvéole 4 parce que sa somme est 314.
Arbre binaire
Un arbre binaire est une structure chaînée dans laquelle chaque nœud a au plus deux enfants, distingués comme enfant gauche et enfant droit.
Un nœud peut n’avoir aucun enfant, n’en avoir qu’un seul ou avoir à la fois un enfant gauche et un enfant droit.
Arbre binaire de recherche
Un arbre binaire de recherche ordonne des clés distinctes de sorte que chaque nœud ait des clés plus petites dans tout son sous-arbre gauche et des clés plus grandes dans tout son sous-arbre droit.
Comparer la cible à la clé courante sélectionne le seul sous-arbre qui puisse contenir une correspondance.

B

Bourrage
Le bourrage est constitué d’octets inutilisés qu’une implémentation peut placer entre les membres d’une structure ou après le dernier membre pour satisfaire les exigences de disposition en mémoire.
Une comparaison d’octets peut détecter un bourrage différent même lorsque deux enregistrements contiennent des valeurs de membres égales.

C

Capacité
La capacité est le nombre d’éléments qu’un tableau alloué peut contenir ; elle peut être supérieure au nombre d’éléments actuellement utilisés.
Un tableau de capacité 16 contenant 10 enregistrements utilisés peut en accepter six de plus avant un nouvel agrandissement.
Cas de base
Un cas de base traite la plus petite entrée d’une fonction récursive sans effectuer un autre appel récursif, ce qui arrête la descente vers des sous-problèmes plus petits.
Un nettoyage d’arbre revient immédiatement pour un sous-arbre nul avant de tenter de lire les pointeurs vers les enfants.
Chaînage séparé
Le chaînage séparé gère les collisions de hachage en conservant une liste chaînée d’entrées dans chaque alvéole et en comparant les clés dans la liste choisie.
Les nouvelles entrées de mots peuvent être insérées en tête, car leur ordre dans une alvéole n’influe pas sur la recherche exacte.
Champ de bits
Un champ de bits est un membre de structure déclaré avec une largeur en bits, qui limite les valeurs entières stockées tandis que son regroupement est défini par l’implémentation.
Un champ de mode non signé de 3 bits stocke des valeurs de 0 à 7 et son adresse ne peut pas être obtenue.
Charge utile
Une charge utile est la donnée stockée dont le type est indiqué par une valeur étiquetée ; elle est conservée séparément de l’étiquette qui décrit comment l’interpréter.
Dans un jeton de calculatrice, la charge utile contient soit l’opérande numérique, soit l’opérateur indiqué par son étiquette.
Collision
Une collision se produit lorsque des clés distinctes reçoivent le même indice d’alvéole, ce qui oblige la table à conserver et à distinguer leurs entrées séparées.
Une recherche doit comparer les clés dans une alvéole partagée au lieu de supposer que sa première entrée correspond.
Comportement défini par l’implémentation
Un comportement défini par l’implémentation est un choix que l’implémentation du C doit documenter ; un code portable ne peut donc pas supposer que toutes les implémentations font le même choix.
L’ordre documenté de regroupement des champs de bits peut différer entre les implémentations d’une même déclaration C.
Constante d’énumération
Une constante d’énumération est une constante entière nommée dans une définition d’énumération, dont la valeur est explicite ou déterminée à partir de la précédente.
Dans enum state { IDLE = 3, RUNNING };, la constante d’énumération RUNNING reçoit la valeur 4.
Copie profonde
Une copie profonde crée des copies indépendantes des données pointées dont l’enregistrement est propriétaire ainsi que de l’enregistrement lui-même, avec des responsabilités de nettoyage pour les nouvelles allocations.
Copier un nom dans une nouvelle allocation permet à chaque enregistrement de personne de libérer son propre nom sans double libération.
Copie superficielle
Une copie superficielle duplique les valeurs des membres d’un enregistrement, y compris les adresses des pointeurs, tout en laissant partagées les données pointées allouées séparément.
Après la copie d’un enregistrement de personne, les deux pointeurs de nom donnent encore accès à la même chaîne, sauf si une nouvelle copie est allouée.

D

Débordement inférieur
Un débordement inférieur se produit lorsqu’un programme essaie de retirer un élément d’une collection vide ; il faut alors renvoyer un échec avant tout accès invalide.
Le dépilement d’une pile sur tableau doit vérifier que le nombre d’éléments n’est pas nul avant de le décrémenter et de lire le tableau.
Déchaînage
Le déchaînage retire un nœud d’une structure en reconnectant les références autour de lui avant de libérer le stockage du nœud dont la structure est propriétaire.
Rediriger le next d’un prédécesseur vers le successeur du nœud retiré garde le reste de la chaîne accessible.
Défiler
Défiler consiste à retirer l’élément en tête d’une file, en signalant une file vide avant de changer l’état ou de lire une valeur.
Un défilement circulaire lit l’élément en tête, fait avancer l’indice de tête avec retour au début et diminue le compteur.

É

Égalité des chaînes
L’égalité des chaînes signifie que deux chaînes terminées par un caractère nul contiennent des séquences de caractères identiques, que leurs adresses de stockage diffèrent ou non.
Une recherche dans une table de mots utilise strcmp(e->key, key) == 0 pour reconnaître des textes égaux dans des allocations distinctes.
Énumération
Une énumération définit des constantes entières nommées et un type pour les valeurs associées à ces choix, sans imposer l’appartenance à l’ensemble nommé.
Une énumération d’alerte peut nommer les niveaux faible, moyen, élevé et critique, mais l’entrée doit toujours être validée.
Étiquette de structure
Une étiquette de structure nomme un type structure après le mot-clé struct et peut identifier le type à l’intérieur de sa propre définition.
L’étiquette dans struct node permet à la définition de déclarer un lien vers son propre type avec struct node *next.

E

Enchaînement des cas
L’enchaînement des cas est la poursuite de l’exécution d’un cas switch au suivant parce qu’aucun break, return ou autre transfert ne l’arrête.
Un cas qui affiche un état mais omet break peut aussi exécuter l’action écrite pour le cas suivant.
Enfiler
Enfiler consiste à ajouter un élément en queue d’une file tout en préservant la position en tête des valeurs déjà en attente.
Un tampon circulaire avec compteur écrit le nouvel élément à (head + count) % CAP, puis augmente son compteur.
Entrée
Une entrée est un enregistrement stocké dans une table de hachage, contenant une clé et des données associées, ainsi qu’un pointeur vers l’entrée suivante lorsque le chaînage séparé relie les entrées.
Une entrée de comptage de mots est propriétaire d’une chaîne de clé, stocke le compteur du mot et pointe vers l’entrée suivante de son alvéole.
Expression postfixée
Une expression postfixée écrit chaque opérateur après ses opérandes, permettant à une pile de conserver les valeurs intermédiaires jusqu’à ce que l’opérateur les combine.
Pour « 9 4 - », la première valeur dépilée est l’opérande de droite 4 et le résultat est 5.

F

Facteur de charge
Le facteur de charge est le nombre de clés stockées divisé par le nombre d’alvéoles ; il mesure l’occupation moyenne sans fixer la longueur de chaque chaîne.
Douze clés dans huit alvéoles donnent un facteur de charge de 1,5, même si certaines alvéoles restent vides.
Feuille
Une feuille est un nœud d’arbre sans enfant : ses deux pointeurs vers les enfants sont donc nuls et sa hauteur est de zéro lorsque la hauteur compte les arêtes.
La suppression d’une feuille libère son allocation et remplace son lien entrant par un pointeur nul.
FIFO
FIFO signifie premier entré, premier sorti et décrit l’ordre dans lequel l’élément stocké le plus ancien est traité avant les éléments arrivés plus tard et encore en attente.
Après l’enfilement de A, B et C, un retrait FIFO renvoie A avant chacune des lettres arrivées ensuite.
File
Une file est une collection qui ajoute en queue et retire en tête, en préservant l’ordre d’arrivée des valeurs encore en attente.
Une file chaînée ajoute un nouveau nœud après sa queue et retire le nœud actuellement atteint par son pointeur de tête.
Fonction de comparaison
Une fonction de comparaison indique l’ordre relatif de deux valeurs par un résultat négatif, zéro en cas d’égalité ou un résultat positif.
Une fonction de comparaison de températures renvoie un signe après avoir comparé les valeurs, ce qui évite la troncature d’une petite différence.
Fonction de hachage
Une fonction de hachage transforme de façon déterministe une clé en nombre qui est réduit à un indice d’alvéole, en visant une répartition uniforme des clés attendues.
Un hachage par somme des caractères envoie des permutations comme « cat » et « act » dans la même alvéole.

H

Hauteur
La hauteur d’un arbre non vide est le nombre d’arêtes de son plus long chemin de la racine à une feuille ; la hauteur de l’arbre vide utilisée ici est −1.
Une chaîne contenant cinq nœuds a quatre arêtes sur son plus long chemin : sa hauteur est donc de 4.

I

Initialiseur désigné
Un initialiseur désigné nomme le membre ou l’élément qui reçoit une valeur, ce qui permet d’initialiser les membres d’une structure sans suivre l’ordre de déclaration.
Pour une structure où id est déclaré avant temp_c, l’initialiseur {.temp_c = 21.5, .id = 7} nomme ses membres dans l’ordre inverse de leur déclaration.
Insérer en tête
Insérer en tête consiste à insérer un élément avant le premier élément actuel d’une séquence, ce qui change l’élément atteint depuis le pointeur de tête.
Insérer en tête 1, puis 2, puis 3 crée une liste dont le parcours visite 3 avant 2, puis 1.
Insertion ordonnée
Une insertion ordonnée place une nouvelle valeur de façon à préserver l’ordre existant, en reliant le nouveau nœud entre son prédécesseur et son successeur.
Une boucle sur une liste croissante s’arrête avant la première valeur qui n’est pas inférieure à la nouvelle valeur.
Instruction switch
Une instruction switch sélectionne un cas de départ à partir d’une expression entière et poursuit l’exécution jusqu’à ce que le contrôle quitte l’instruction ou la fonction.
Une instruction switch portant sur une valeur étiquetée choisit l’opération d’affichage selon le type de charge utile actuellement stocké.

L

Lien entrant
Un lien entrant est le pointeur qui atteint un nœud, soit le pointeur de tête de la liste, soit le champ d’un prédécesseur pointant vers le suivant ; il doit changer lorsque ce nœud est contourné.
Supprimer le premier nœud modifie le pointeur de tête, car aucun nœud prédécesseur ne fournit son lien entrant.
LIFO
LIFO signifie dernier entré, premier sorti et décrit l’ordre dans lequel l’élément stocké le plus récent est traité avant tous les éléments plus anciens encore présents.
Après qu’une pile a reçu 4, 7 et 1, ses deux premiers dépilements renvoient 1 et 7, et laissent 4.
Liste doublement chaînée
Une liste doublement chaînée stocke dans chaque nœud des liens vers son successeur et son prédécesseur, ce qui permet un parcours à rebours et la réparation des liens par l’un ou l’autre voisin.
Retirer un nœud intérieur met à jour le lien vers le suivant du nœud précédent et le lien vers le précédent du nœud suivant.
Liste simplement chaînée
Une liste simplement chaînée est une séquence de nœuds reliés par des pointeurs vers le suivant, avec un pointeur de tête au début et un lien nul à la fin.
Insérer un nœud en tête lui donne l’ancienne tête comme successeur et fait de ce nouveau nœud la tête.

M

Marqueur de suppression
Un marqueur de suppression désigne une case supprimée en adressage ouvert sans la faire paraître jamais utilisée, ce qui permet à la recherche de continuer vers les entrées en collision situées plus loin.
Une recherche passe un marqueur de suppression au lieu de s’arrêter avant une clé correspondante stockée plus loin sur le chemin de sondage.
Membre
Un membre est un composant nommé d’une structure ou d’une union, sélectionné dans un objet avec un point ou à travers un pointeur avec une flèche.
L’expression r.temp_c sélectionne le membre de température de la mesure stockée dans r.

N

Nœud
Un nœud est un enregistrement contenant des données et les pointeurs qui le relient à d’autres parties d’une structure chaînée.
Un nœud de liste contient un entier et son pointeur vers le successeur ; un nœud d’arbre possède quant à lui deux pointeurs vers ses enfants.

O

Opérateur flèche
L’opérateur flèche sélectionne un membre de structure ou d’union à travers un pointeur et combine le déréférencement avec la sélection du membre.
Pour un pointeur valide vers un enregistrement p, p->temp_c donne accès au même membre que (*p).temp_c.

P

Parcours
Un parcours visite les nœuds en suivant les liens d’une structure dans un ordre défini, en s’arrêtant à la fin appropriée ou dans le cas d’un sous-arbre vide.
Un pointeur distinct de parcours de liste suit les liens next tandis que le propriétaire conserve le pointeur de tête d’origine.
Parcours infixe
Un parcours infixe visite chaque sous-arbre gauche avant sa racine, puis son sous-arbre droit, ce qui produit des clés triées pour un arbre binaire de recherche.
Afficher un arbre binaire de recherche de comptage de mots en parcours infixe produit les mots stockés dans l’ordre utilisé par ses comparaisons de texte.
Parcours postfixe
Un parcours postfixe visite les sous-arbres gauche et droit avant leur racine, ce qui permet au nettoyage de libérer les enfants avant leur parent.
Une fonction de libération d’arbre lit les liens vers les enfants tant que le parent existe, puis libère ce parent en dernier.
Parcours préfixe
Un parcours préfixe visite la racine d’un sous-arbre avant ses sous-arbres gauche et droit, en plaçant chaque parent avant ses descendants.
Une sortie en parcours préfixe commence par la clé de la racine même lorsque cette clé n’est ni la plus petite ni la plus grande.
Passage par valeur
Le passage par valeur signifie qu’un paramètre de fonction reçoit une copie de la valeur fournie : modifier ce paramètre ne remplace donc pas la valeur de la fonction appelante.
Une modification de température dans un paramètre d’enregistrement passé par valeur laisse inchangé le membre de température de la fonction appelante.
Pile
Une pile est une collection dans laquelle l’insertion et le retrait utilisent le sommet, si bien que la valeur insérée le plus récemment est la prochaine retirée.
Un programme de vérification des délimiteurs stocke le délimiteur ouvrant non apparié le plus récent au sommet de sa pile.
Pointeur de pointeur
Un pointeur de pointeur contient l’adresse d’un objet pointeur, ce qui permet à une fonction de lire ou de remplacer ce pointeur grâce à ce niveau supplémentaire.
Un node **link peut passer de l’adresse du pointeur de tête à l’adresse du champ next d’un nœud.
Pointeur de queue
Le pointeur de queue désigne le dernier nœud d’une séquence chaînée ; il est maintenu à jour afin qu’une insertion en fin évite un parcours complet.
Une file chaînée met les pointeurs de tête et de queue à nul lorsque son unique nœud est retiré et libéré.
Pointeur de tête
Le pointeur de tête désigne le premier nœud d’une séquence chaînée ; un pointeur de tête nul représente une séquence qui ne contient aucun nœud.
Retirer le premier nœud d’une liste doit mettre à jour le pointeur de tête du propriétaire avant que l’allocation de l’ancien nœud soit libérée.
Pointeur pendant
Un pointeur pendant désigne un objet dont la durée de vie est terminée : le déréférencer ne donne donc plus un accès valide à cet objet.
Renvoyer l’adresse d’un enregistrement local laisse à la fonction appelante un pointeur vers un objet qui a cessé d’exister.
Pointeur vers un objet constant
Un pointeur vers un objet constant donne accès à un objet qui ne peut pas être modifié à travers ce pointeur, tandis que le pointeur lui-même peut encore changer.
Une fonction d’affichage peut lire à travers const reading *p, mais ne peut pas affecter une valeur à p->id.
Propriété
La propriété attribue la responsabilité de libérer une allocation, même lorsque plusieurs pointeurs peuvent atteindre le même objet pendant sa durée de vie.
Un nœud de mot est propriétaire de sa chaîne : le nettoyage libère donc cette chaîne avant le nœud qui contient son adresse.

R

Racine
La racine est le nœud où commence un arbre ou un sous-arbre, atteint par le pointeur de racine du propriétaire ou par le lien d’un parent vers son enfant.
Remplacer une racine supprimée qui a un enfant exige de conserver l’enfant restant comme nouvelle racine de l’arbre.
Réallocation
La réallocation modifie la taille d’un bloc alloué et peut le déplacer : en cas de réussite, le propriétaire doit utiliser l’adresse de base renvoyée.
Un pointeur temporaire pour le résultat préserve les anciens enregistrements lorsqu’un redimensionnement de tableau à une taille non nulle échoue.
Rehachage
Le rehachage recalcule les emplacements des clés stockées pour un nouveau nombre d’alvéoles et déplace ou relie à nouveau les entrées afin que les recherches ultérieures utilisent les bons emplacements.
Doubler le tableau d’alvéoles exige de réduire le résultat de hachage de chaque clé avec le nouveau nombre avant de la placer.

S

Sondage linéaire
Le sondage linéaire résout une collision en essayant des cases consécutives du tableau avec retour au début, selon la même séquence pour l’insertion et la recherche.
Un sondage qui commence à la dernière case continue à la case zéro lorsqu’une autre position occupée doit être vérifiée.
Sous-arbre
Un sous-arbre est un nœud accompagné de tous ses descendants, formant un arbre plus petit auquel peuvent s’appliquer les mêmes règles de recherche et de mise à jour.
La restriction sur le sous-arbre gauche d’un arbre binaire de recherche s’applique aux descendants éloignés ainsi qu’à l’enfant gauche direct.
Stockage contigu
Le stockage contigu place les éléments d’un tableau côte à côte dans une même séquence en mémoire, ce qui permet l’accès direct par indice et le parcours d’emplacements voisins.
Un tableau d’entiers évite les pointeurs de liaison dans chaque nœud et les frais d’allocations séparées d’une liste d’entiers.
Structure
Une structure est un type C qui regroupe dans un enregistrement des membres nommés, pouvant être de types différents, avec une zone de stockage distincte pour chaque membre.
Un enregistrement de température associe un identifiant entier de capteur et une température dans un objet de type structure.
Successeur infixe
Le successeur infixe d’un nœud ayant un sous-arbre droit est la plus petite clé de ce sous-arbre ; il sert à remplacer la clé d’un nœud à deux enfants lors de sa suppression.
Suis l’enfant droit puis les liens gauches pour trouver la clé de remplacement lors de la suppression d’un nœud à deux enfants.

T

Tableau de structures
Un tableau de structures stocke de façon contiguë des enregistrements d’un même type structure et permet de sélectionner directement un enregistrement par son indice.
L’expression readings[3].temp_c donne accès à la température du quatrième enregistrement du tableau.
Taille d’un arbre
La taille d’un arbre est son nombre de nœuds ; elle s’obtient en ajoutant un pour sa racine aux tailles des deux sous-arbres enfants, avec zéro pour un arbre vide.
Une racine avec deux enfants feuilles a une taille de 3 même si sa hauteur mesurée en arêtes n’est que de 1.
Tampon circulaire
Un tampon circulaire réutilise un tableau fixe en ramenant ses positions de la dernière case à la première au lieu de décaler les éléments restants.
Après un retrait en tête, une insertion ultérieure peut réutiliser la case libérée une fois que son indice revient au début.
Type abstrait de données
Un type abstrait de données décrit une collection par ses opérations autorisées et son comportement, indépendamment du stockage choisi pour l’implémenter.
Une pile conserve le même comportement d’empilement et de dépilement, que ses valeurs soient dans un tableau ou dans des nœuds chaînés.
Typedef
Une déclaration typedef introduit un alias pour un type existant, permettant au code d’utiliser un nom plus court sans créer un type distinct.
Après typedef struct reading reading;, une déclaration peut utiliser reading *p pour un pointeur vers cette structure.

U

Union
Une union est un type C dont les membres partagent le stockage, permettant à cet espace de contenir une valeur choisie parmi différents types de membres.
Dans une union ayant un membre entier et un membre double, écrire le double remplace la charge utile entière précédente au lieu de préserver deux valeurs.
Union étiquetée
Une union étiquetée associe un stockage partagé pour la charge utile à une étiquette distincte qui indique au code de lecture à quel membre de l’union accéder.
Une valeur étiquetée K_CHAR est affichée à l’aide du membre caractère et d’une conversion de format pour caractère.

À propos de cette unité

Par StudeliSignaler une erreur

Crée ton propre Matériel d'Étude

Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.

Commencer