Estructuras y listas enlazadas en C: pilas, colas y árboles

Aprende a agrupar datos relacionados mediante estructuras y typedef, a dar nombre a conjuntos de valores mediante enumeraciones y a almacenar alternativas en uniones. Después, construye las estructuras de datos enlazadas clásicas a partir de nodos reservados con malloc: listas enlazadas, pilas, colas, árboles binarios de búsqueda y tablas de dispersión. En cada una insertarás, buscarás, eliminarás y recorrerás elementos, gestionarás los casos vacíos y los casos límite, y liberarás todos los nodos sin fugas.

PrincipianteUnidad 6 de 8 del curso Curso de Programación en C

Lo que aprenderás

  1. Define tipos de estructura, dales nombres cortos con typedef, accede a sus miembros mediante variables y punteros, y almacena muchos registros en arrays de estructuras.
    • Definir una estructura y trabajar con sus miembros
      Declara un tipo de estructura, crea e inicializa variables de estructura, lee y modifica sus miembros y comprende cómo funcionan la copia, la comparación y la distribución en memoria.
    • Dar nombre a tipos con typedef y pasar estructuras a funciones
      Crea alias de tipos con typedef, pasa estructuras por valor o mediante punteros, usa el operador flecha y reserva estructuras en el montículo.
    • Almacenar y procesar registros en arrays de estructuras
      Almacena muchos registros en un array de estructuras, recórrelos con bucles, búscalos y ordénalos, amplía el array en el montículo y gestiona estructuras que poseen otros bloques de memoria.
  2. Sustituye los números mágicos por enumeraciones y usa uniones y uniones etiquetadas para almacenar en la misma memoria un valor de entre varios tipos.
    • Utilizar enumeraciones para definir conjuntos de valores con nombre
      Define enumeraciones, controla sus valores, úsalas en condiciones y sentencias switch, y conoce qué comprueba C sobre ellas y qué no.
    • Superponer alternativas mediante uniones y uniones etiquetadas
      Declara uniones, conoce el efecto de leer y escribir sus miembros, combina una unión con una etiqueta de enumeración para almacenar valores de distintos tipos de forma segura y agrupa indicadores pequeños en campos de bits.
  3. Enlaza nodos reservados dinámicamente en listas, recórrelas, inserta y elimina nodos en cualquier posición, libéralos por completo y decide cuándo una lista se adapta mejor a una tarea que un array.
    • Crear, recorrer y liberar una lista simplemente enlazada
      Define un tipo de nodo, añade nodos al inicio y al final de una lista, visita cada nodo, escribe funciones que cambien el puntero al inicio y libera todos los nodos.
    • Buscar nodos, insertarlos en orden y eliminarlos
      Busca un valor en una lista, inserta un nodo en el lugar correcto de una lista ordenada y desenlaza y libera un nodo, teniendo en cuenta el primer nodo, el último nodo y los valores ausentes.
    • Comparar las listas con los arrays y ampliar las listas
      Evalúa las ventajas y los inconvenientes de las listas enlazadas frente a los arrays, mantén un puntero al final y un contador, construye listas doblemente enlazadas y libera nodos que poseen otros bloques de memoria.
  4. Implementa pilas de último en entrar, primero en salir y colas de primero en entrar, primero en salir con arrays y con nodos enlazados, protege las estructuras frente a los casos en que estén vacías o llenas y aplícalas a tareas reales.
    • Construir una pila y utilizarla
      Describe las operaciones de una pila, implementa una pila con un array y con nodos enlazados, gestiona pilas vacías y llenas y usa pilas para comprobar delimitadores y evaluar expresiones posfijas.
    • Construir una cola con nodos enlazados o un búfer circular
      Describe las operaciones de una cola, implementa una cola con punteros al inicio y al final y con un array circular de tamaño fijo, y distingue una cola vacía de una llena.
  5. Representa árboles binarios con nodos enlazados, busca, inserta y elimina claves en un árbol binario de búsqueda, visita los nodos en distintos órdenes y libera un árbol completo.
    • Representar un árbol binario y realizar búsquedas en un árbol binario de búsqueda
      Usa el vocabulario de los árboles binarios, enuncia la propiedad de los árboles binarios de búsqueda y busca en un árbol de forma recursiva y con un bucle.
    • Insertar, recorrer, eliminar y liberar nodos de un árbol
      Inserta claves en un BST, visita los nodos en inorden, preorden y postorden, calcula el tamaño y la altura, elimina claves y libera todos los nodos.
  6. Convierte claves en índices de cubetas con una función de dispersión, gestiona las colisiones con cadenas de nodos e implementa, usa y libera una tabla de dispersión en C.
    • Asignar claves a cubetas mediante una función de dispersión
      Explica qué hace una función de dispersión, calcula índices de cubetas para claves de cadena, reconoce colisiones y distribuciones desiguales y usa el factor de carga para decidir cuándo ampliar una tabla.
    • Implementar una tabla de dispersión con encadenamiento en C
      Construye una tabla de dispersión a partir de un array de cubetas con listas enlazadas: busca, inserta y cuenta claves, copia claves de cadena de forma segura y libera toda la memoria reservada.

Guía de estudio de Estructuras y listas enlazadas en C

6 capítulos

1

Agrupar datos relacionados mediante estructuras

Los valores relacionados son más fáciles de manejar cuando viajan como un solo registro. Las estructuras dan nombre a esos valores y los alias de tipos facilitan la lectura de las declaraciones. Las llamadas a funciones plantean una elección práctica: pasar una copia o pasar una dirección que permita acceder al original. Los arrays extienden esa elección a muchos registros, donde la ordenación, el cambio de tamaño y la copia de miembros de tipo puntero requieren tener claro qué objetos siguen existiendo y qué código es responsable de ellos.

1.1 Miembros, copias y distribución de estructuras

Una estructura agrupa valores relacionados en un solo registro. Sus miembros tienen nombres y pueden tener distintos tipos. Una lectura de un sensor, por ejemplo, necesita tanto un identificador como una temperatura.

Define e inicializa un registro
struct reading {
    int id;
    double temp_c;
};
struct reading r = {.temp_c = 21.5, .id = 7};
r.temp_c += 0.5;

La definición introduce el tipo struct reading; reading es su etiqueta. El punto y coma final va después de la llave de cierre. Definir el tipo no reserva ningún objeto. La declaración de r crea un objeto y . selecciona sus miembros. Aquí la temperatura pasa a ser 22,0 °C. Si solo se ha declarado una etiqueta, el nombre del tipo incluye struct: reading r; necesita un typedef independiente.

Un inicializador como {7, 21.5} sigue el orden de declaración. Los designadores como .id = 7 permiten nombrar miembros en otro orden. Los miembros omitidos en un inicializador reciben valores cero, por lo que {0} inicializa todo el registro. Una declaración local struct reading r; sin inicializador deja sus miembros indeterminados hasta que se les asignan valores.

Un miembro tiene su propio tipo y dirección. Para leer una temperatura con scanf, usa scanf("%lf", &r.temp_c) y comprueba que el valor de retorno sea 1. El punto tiene mayor precedencia que &, por lo que esto pasa la dirección del miembro double.

Copia y compara los valores
struct reading s = r;
s.temp_c = -4.0;
int same = r.id == s.id && r.temp_c == s.temp_c;

La asignación copia todos los miembros. Modificar s.temp_c deja r.temp_c en 22,0. Los arrays fijos dentro de estructuras también se copian: un miembro char name[32] pertenece al registro. Esto funciona aunque los arrays independientes de C no puedan asignarse con =. Los miembros de tipo puntero requieren más cuidado porque el puntero copiado sigue apuntando al mismo objeto.

C no define == para estructuras completas. Compara los miembros que definen la igualdad para tu tarea. memcmp compara los bytes de memoria, incluido el relleno, y puede indicar una diferencia entre registros cuyos valores de los miembros son iguales.

Sigue la distribución en memoria

Los miembros aparecen en orden de declaración, sin relleno antes del primero. El compilador puede añadir bytes sin usar entre los miembros y al final para cumplir los requisitos de alineación. En una plataforma habitual de 64 bits con int de 4 bytes y alineación de 4 bytes, struct { char c; int i; char d; } ocupa 12 bytes. Sus miembros empiezan en los desplazamientos 0, 4 y 8. Cambiar el orden a int i; char c; char d; suele reducir el tamaño a 8 bytes. Usa sizeof para el tamaño real y offsetof de <stddef.h> para obtener un desplazamiento.

Las estructuras pueden anidarse. Con struct point { double x_m, y_m; }; y struct rect { struct point corner; double width_m, height_m; };, box.corner.x_m accede a la coordenada x de la esquina en metros. Una estructura no puede contener un miembro completo de su propio tipo: eso requeriría un objeto de tamaño infinito. Un puntero a su propio tipo tiene un tamaño fijo y hace posibles los nodos enlazados.

Puntos clave

  • Usa . para seleccionar un miembro de un objeto de tipo estructura.
  • Un inicializador pone a cero los miembros omitidos; una estructura local sin inicializar tiene miembros indeterminados.
  • La asignación de una estructura completa copia sus miembros, incluidos los arrays que contiene.
  • Compara los valores de los miembros y usa sizeof para incluir el relleno.
  • Una estructura puede contener un puntero a su propio tipo.

1.2 Nombres de tipos, llamadas a funciones y punteros a estructuras

Un typedef da otro nombre a un tipo existente. Cambia cómo escribes el tipo, no cómo se comportan sus objetos. typedef double meters; convierte meters en otro nombre para double.

Da nombre a una estructura y pásala por valor
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;
}

Ahora puedes declarar reading r = make_reading(7, 21.5);. Devolver una estructura devuelve su valor. Llamar a warm_copy(r) proporciona a la función una copia, por lo que la temperatura de la función llamadora sigue siendo 21,5 °C. Una copia de un registro pequeño suele ser la interfaz más clara cuando el objeto de la función llamadora debe permanecer sin cambios.

Un tipo autorreferencial necesita su etiqueta dentro de la definición:

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

El alias node está disponible después de la declaración. Dentro de estas llaves, struct node ya da nombre al tipo incompleto y puede usarse para un puntero. La etiqueta y el alias pueden escribirse igual porque las etiquetas usan su propio espacio de nombres.

Accede al objeto de la función llamadora mediante un puntero
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 resta cambia r.temp_c a 20,0; después, warm lo cambia a 21,0. La función recibe una copia de la dirección y ambos punteros siguen permitiendo acceder al mismo registro. Usa -> con un puntero y . con un objeto de tipo estructura. p->temp_c significa (*p).temp_c. Los paréntesis son necesarios porque el acceso a miembros tiene mayor precedencia que la desreferenciación: *p.temp_c intenta seleccionar un miembro de p antes de aplicar *.

Para leer sin copiar la estructura, recibe const reading *p. Se rechaza la asignación a p->temp_c mediante ese puntero. La variable de tipo puntero aún puede cambiar, por ejemplo, cuando recorre un array. En cambio, reading *const p hace que el propio puntero sea fijo. En estructuras con miembros de tipo puntero, un puntero a datos constantes no convierte en datos de solo lectura los datos reservados por separado a los que apuntan esos miembros.

Asigna un propietario a un registro en el montículo

Incluye <stdlib.h> para la reserva de memoria y <stdbool.h> para este resultado de éxito:

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 función escribe en *out solo después de que la reserva tenga éxito. La función llamadora puede declarar reading *item = NULL;, llamar a make_heap_reading(&item, 8, -2.0), gestionar un resultado falso y llamar más tarde a free(item). sizeof *p mide el tipo al que se apunta incluso antes de que p apunte a un registro inicializado; esta expresión no lee ese registro.

Un puntero a una estructura local automática se vuelve colgante cuando la función retorna. Devuelve el valor de la estructura o un bloque reservado en el montículo con un propietario claramente definido. Devolver &r desde una función cuya variable local es r no puede prolongar su tiempo de vida.

Puntos clave

  • typedef crea un alias y una definición autorreferencial usa su etiqueta de estructura.
  • Pasa una estructura por valor para obtener una copia o pasa su dirección para modificar el original.
  • p->member equivale a (*p).member.
  • Un puntero a datos constantes impide modificar miembros mediante ese puntero.
  • Comprueba la reserva antes de inicializar y asigna un propietario a cada registro del montículo.
  • Devolver una estructura local por valor es válido; devolver su dirección deja un puntero colgante.

1.3 Arrays de registros y cadenas de su propiedad

Un array de estructuras mantiene registros uno junto a otro. Usa un índice para seleccionar un registro y después un punto para seleccionar un miembro. Esto permite recorrer, buscar y ordenar de forma muy parecida a como se hace con un array de números.

Recorre registros con un número de elementos explícito
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;
}

Aquí reading es el alias de la estructura con identificador y temperatura. El mejor índice pasa de 0 a 3, por lo que el registro más cálido tiene id 4 y temperatura 30,0 °C. Empezar con un elemento real también funciona cuando todas las temperaturas son negativas; empezar con un supuesto máximo de cero puede fallar. Este recorrido requiere al menos un elemento. Una búsqueda reutilizable debe informar de que el array está vacío antes de leer el elemento 0.

El cálculo del número de elementos con sizeof funciona donde readings es un array. En un parámetro de función, una declaración de array se convierte en un puntero, por lo que debes pasar n por separado. (items + i)->temp_c e items[i].temp_c acceden al mismo miembro.

Proporciona una función de comparación para qsort

Incluye <stdlib.h>. Para valores finitos de temperatura, esta función de comparación devuelve un signo sin convertir una pequeña diferencia de temperatura en un entero:

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 función de comparación recibe direcciones de elementos. Un resultado negativo significa que el primero va antes, cero significa que son iguales y uno positivo, que va después. El orden de los id tras la ordenación es 2, 3, 1, 4. Devolver directamente la diferencia de tipo double trunca una diferencia como −0,25 a cero. Restar claves enteras puede causar un desbordamiento. Comparar primero evita esos errores. qsort no conserva el orden original de los elementos iguales.

Amplía un array sin perder su espacio de almacenamiento

Mantén por separado el número de elementos utilizados y la capacidad. Para un array reservado con cap positivo, usa un puntero temporal al duplicar la capacidad. Incluye <stdint.h> para 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;

Estas sentencias pertenecen a una función que devuelve bool; items es su puntero al array y cap, su capacidad positiva. Si falla, el bloque de memoria anterior sigue disponible. Si tiene éxito, el bloque puede moverse, por lo que hay que sustituir los punteros antiguos a sus elementos. Una capacidad inicial de cero necesita su propia reserva inicial.

Decide de qué es propietaria una copia

Con typedef struct { char *name; int age; } person;, person b = a; copia la dirección en name. Ambos registros acceden a la misma cadena. Liberar ambas copias como propietarias liberaría dos veces el mismo bloque de memoria. Para obtener una copia independiente, reserva strlen(a.name) + 1 bytes, comprueba que la reserva haya tenido éxito y usa strcpy de <string.h>. Un miembro fijo char name[32] se copia junto con la estructura. strdup realiza la reserva y la copia, pero pertenece a POSIX y C23, no a la biblioteca estándar de C17.

Puntos clave

  • Usa items[i].member y pasa el número de elementos a las funciones que reciben arrays.
  • Inicializa la búsqueda de un máximo a partir de un elemento existente después de comprobar si el array está vacío.
  • Una función de comparación de qsort devuelve un signo; restar claves puede truncar el resultado o causar un desbordamiento.
  • Comprueba la aritmética del tamaño de la reserva y guarda el resultado de realloc en un puntero temporal.
  • Un miembro de tipo puntero copiado sigue apuntando al bloque de memoria original.
2

Dar nombre a opciones con enumeraciones y compartir almacenamiento con uniones

Un programa suele necesitar una opción con un nombre fácil de leer o un valor que pueda adoptar varias formas diferentes. Las enumeraciones dan nombre a las opciones; las uniones comparten almacenamiento entre alternativas. Al combinarlas, cada valor almacenado describe su propio tipo. La clave es mantener la coherencia entre el nombre y los datos de la variante, reconociendo que C deja la validación de la entrada y las decisiones sobre la representación bajo el control del programa.

2.1 Valores de enumeración y opciones comprobadas

Una enumeración da nombres fáciles de leer a opciones enteras. Nombres como HIGH muestran el propósito de una condición sin obligar a quien lee el código a recordar qué representa el número 10.

Sigue la numeración
enum weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
enum level { LOW = 1, MEDIUM, HIGH = 10, CRITICAL };
enum level alert = HIGH;

Si se omite el primer valor, es cero. Los valores omitidos posteriores aumentan en uno el valor anterior. Así, MON es 0 y SUN es 6; los niveles son 1, 2, 10 y 11. Un valor explícito reinicia la numeración en ese punto. Distintas constantes pueden compartir un valor, pero los valores idénticos no pueden aparecer como etiquetas case separadas en el mismo switch.

Un alias puede acortar una declaración: typedef enum { RED, YELLOW, GREEN } light; permite escribir light signal = RED;. Las constantes de enumeración comparten el espacio de nombres de los identificadores ordinarios en su ámbito. Dos enumeraciones diferentes no pueden declarar ambas RED en el mismo ámbito. Los prefijos como LIGHT_RED y PAINT_RED mantienen los nombres diferenciados.

Gestiona cada opción
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";
}

Cada return termina su caso. Si, en cambio, un caso realiza una acción y después debe detenerse, añade un break; llegar a la siguiente etiqueta continúa allí la ejecución. Con GCC, -Wall activa las advertencias por casos de enumeración ausentes cuando no hay default; Clang también diagnostica esas omisiones de forma predeterminada. Una advertencia puede revelar un caso ausente cuando añadas una constante más adelante.

Valida la entrada como un conjunto de opciones

El tipo de enumeración no exige que el valor pertenezca al conjunto de constantes con nombre. C17 permite la asignación desde un entero, por lo que enum level x = 42; puede compilar. Una comprobación de que el número está entre 1 y 11 sigue admitiendo valores sin nombre como 6. Comprueba los valores permitidos concretos:

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

Solo después de esa comprobación debe convertirse la entrada en un nivel. Para los días consecutivos de la semana, basta con una comprobación de rango desde MON hasta SUN. En una enumeración ordinaria de C17, las constantes de enumeración tienen tipo int. C23 también permite especificar un tipo subyacente, como enum color : unsigned char; esa sintaxis no es de C17.

Muestra nombres y usa recuentos

printf("%d\n", SUN) muestra «6», no «SUN». Para valores consecutivos que empiezan en cero, un array de nombres proporciona el texto: const char *day_names[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};. Después de validar un índice, day_names[SUN] es «Sun». Usar un valor de nivel como HIGH para indexar un array de este tipo requeriría otra correspondencia porque los valores de nivel tienen huecos.

Añadir DAY_COUNT inmediatamente después de SUN le da el valor 7, útil como límite de un bucle o tamaño de un array. Cuenta los días, pero no es en sí mismo un día válido. Los nombres de enumeración siguen las reglas de ámbito de C y los depuradores pueden mostrarlos; en cambio, #define realiza una sustitución de texto mediante el preprocesador.

Puntos clave

  • La numeración de una enumeración empieza en cero salvo que se proporcione un valor, y continúa después de cada valor explícito.
  • Una variable de enumeración puede contener un valor sin nombre, por lo que debes validar la entrada externa.
  • Usa break o return para terminar un caso cuando no quieras que la ejecución continúe en el siguiente.
  • Un array que asigna nombres a índices necesita índices validados y consecutivos que empiecen en cero.

2.2 Uniones con una etiqueta correspondiente

Una unión proporciona el mismo espacio de almacenamiento a varios miembros. Es adecuada para un valor que puede ser de varios tipos. Una estructura proporciona a cada miembro su propio espacio de almacenamiento y es adecuada para un registro que necesita todos esos valores juntos.

Almacena una alternativa
union value {
    int i;
    double d;
    char c;
};
union value v = {.d = 2.5};

Todos los miembros empiezan en la misma dirección. La unión debe poder contener su miembro más grande y cumplir los requisitos de alineación; con un double de 8 bytes en plataformas habituales, esta unión ocupa 8 bytes. Un inicializador simple {42} inicializa su primer miembro, i; .d = 2.5 elige d explícitamente.

Escribir v.i = 42 después del inicializador sustituye la alternativa almacenada. Leer v.d después no convierte 42 en un double: interpreta los bytes almacenados usando otra representación. Esa representación puede ser inválida. Lee el miembro correspondiente al valor que almacenaste.

Registra qué miembro es válido

Combina la unión con una enumeración. La enumeración es la etiqueta y la unión contiene los datos de la variante:

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

Una función auxiliar establece ambas partes juntas. El código que escribe mantiene sincronizados los dos campos. Si el código escribe t.as.i pero deja t.kind como K_DOUBLE, una lectura posterior elige la representación incorrecta.

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

Llamar a esta función para celdas que contienen 42, 2,5 y 'x', con espacios entre las llamadas, produce «42 2.5 x». Cada caso elige tanto el miembro como el formato correspondientes. Un token de calculadora puede contener de forma similar un número o un operador.

En plataformas habituales de 64 bits, una enumeración de 4 bytes, el relleno y esta unión de 8 bytes hacen que struct tagged ocupe 16 bytes. Una estructura que almacena la enumeración y las tres alternativas por separado suele ocupar 24 bytes. Estos son ejemplos de distribución; consulta los tipos reales con sizeof.

Agrupa indicadores pequeños en campos de bits
struct flags {
    unsigned int ready : 1;
    unsigned int mode : 3;
};

El ancho sigue a los dos puntos. Un campo sin signo de 1 bit contiene 0 o 1; un campo de 3 bits contiene valores de 0 a 7. Al almacenar 9 en mode se conservan los tres bits menos significativos y se almacena 1. Esto pierde información, por lo que debes validar un modo antes de almacenarlo cuando los valores mayores indican un error.

Un campo de bits no tiene una ubicación cuya dirección pueda obtenerse por separado, por lo que &flags.mode no es válido para una variable llamada flags. Su orden de asignación y su distribución dependen de la implementación. No uses una declaración de campo de bits como garantía portable de una distribución exacta de archivo o dispositivo. Un ancho especificado determina el rango de valores del campo, no la distribución de cada byte a su alrededor.

Puntos clave

  • Los miembros de una unión se solapan, mientras que los de una estructura tienen espacios de almacenamiento separados.
  • Una etiqueta debe corresponder al miembro de la unión que contiene el valor almacenado.
  • Un switch selecciona el miembro y el formato de salida correspondientes.
  • Los anchos de los campos de bits sin signo limitan los valores almacenados; los bits superiores que exceden el ancho se pierden.
  • La distribución de los campos de bits está definida por la implementación y los campos no tienen una dirección propia.
3

Construir y mantener listas enlazadas

Las estructuras enlazadas crecen conectando bloques de memoria reservados por separado. Una lista hace visibles esas conexiones: un puntero inicia la cadena y cada nodo conduce al siguiente. El trabajo interesante ocurre cuando cambian los enlaces, especialmente en el primer o el último nodo. Mantener el enlace entrante adecuado permite que una misma operación gestione varias posiciones, mientras que los punteros al final y los enlaces en ambos sentidos cambian las operaciones que necesitan una búsqueda.

3.1 Crea una lista y mantén accesibles todos sus nodos

Una lista simplemente enlazada almacena cada elemento en un nodo reservado por separado. Cada nodo contiene su valor y la dirección del nodo siguiente. El puntero al inicio de la lista apunta al primer nodo; NULL marca tanto una lista vacía como el final de una cadena.

Define el enlace e inserta al inicio
#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;
}

Empieza con node *list = NULL;. Llama a prepend(&list, value) y gestiona un resultado falso. La función recibe la dirección del puntero al inicio de la función llamadora. Por tanto, *head designa el puntero que debe cambiar. Recibir solo node *head y asignar un valor a ese puntero local dejaría sin cambios el puntero al inicio de la función llamadora.

El orden de las asignaciones finales conserva la lista anterior. Primero conecta el nodo nuevo con el inicio anterior; después sustituye el inicio. Invertir esas sentencias establece n->next en el propio n y hace perder la cadena anterior. Para las llegadas 1, 2 y 3, las inserciones sucesivas al inicio producen 1, después 2 → 1 y después 3 → 2 → 1 → NULL. La inserción al inicio invierte el orden de llegada.

Recorre sin mover el puntero propietario
#include <stdio.h>
void print_list(const node *head) {
    for (const node *p = head; p != NULL; p = p->next)
        printf("%d ", p->number);
}

El puntero de recorrido p avanza mientras head sigue disponible. El ejemplo muestra «3 2 1 » con un espacio final. Para una lista vacía, el cuerpo del bucle se ejecuta cero veces. No hay índice de array: cada paso lee un enlace para encontrar el nodo siguiente. Los bloques reservados pueden estar dispersos por la memoria, e insertar un nodo no mueve los nodos existentes.

Añade al final
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 empieza en el puntero al inicio de la función llamadora. Después pasa a la dirección de cada miembro next hasta que designa el enlace nulo final. Asignar ahí también funciona para una lista vacía. Sin un puntero guardado al final, cada inserción al final debe seguir primero la cadena existente hasta su extremo. Una función también puede devolver un nuevo puntero al inicio, siempre que la función llamadora lo guarde y gestione el fallo de reserva sin perder la lista anterior.

Libera los nodos en un orden seguro
void clear_list(node **head) {
    while (*head != NULL) {
        node *old = *head;
        *head = old->next;
        free(old);
    }
}

Lee el sucesor mientras old aún existe, mueve el puntero al inicio y después libera el nodo antiguo. Tras el bucle, el puntero al inicio de la función llamadora es NULL. Leer old->next después de liberar old es un uso después de la liberación aunque la memoria parezca no haber cambiado. Un comprobador de memoria puede ayudar a detectar lecturas inválidas y bloques de memoria olvidados; la liberación de recursos también debe ejecutarse si falla una operación posterior.

Puntos clave

  • Una lista vacía tiene un puntero nulo al inicio y el último nodo tiene un enlace siguiente nulo.
  • Reserva e inicializa un nodo nuevo antes de modificar la cadena existente.
  • Proporciona a las funciones que cambian el puntero al inicio su dirección o guarda el puntero al inicio que devuelven.
  • Recorre la lista mediante un puntero independiente para que siga siendo accesible.
  • Sin un puntero al final, añadir un elemento requiere recorrer la lista hasta el final.
  • Lee el sucesor antes de liberar un nodo.

3.2 Modificar el enlace entrante para insertar y eliminar

La búsqueda en una lista sigue un enlace a la vez. La inserción y la eliminación necesitan algo más: la dirección del enlace que debe cambiar. Ese enlace puede ser el puntero al inicio o un miembro next dentro de un nodo anterior.

Busca por valor

Con el tipo node, que tiene los miembros number y next:

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

Una coincidencia devuelve la dirección del propio nodo. Si el valor no está presente, devuelve NULL. Llegar al nodo número 1000 requiere seguir 999 enlaces desde el inicio, incluso cuando conoces su posición de antemano.

Mantén un puntero al enlace que cambiará

Un node ** puede apuntar al puntero al inicio o al campo next de un nodo. Llámalo link: *link es el nodo al que se llega actualmente mediante ese enlace. Avanzar con link = &(*link)->next cambia el enlace que examinas; no modifica la lista.

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

Incluye <stdbool.h> y <stdlib.h>. La lista de entrada ya debe estar en orden ascendente. Detente antes del primer valor que no sea menor que el nuevo, de modo que los duplicados se sitúen antes de los valores iguales. El lado izquierdo de && comprueba si es NULL antes de que el lado derecho desreferencie el nodo. Intercambiar esas pruebas desreferenciaría un puntero nulo al llegar al final.

Para las inserciones 5, 2, 8 y 4, la lista final es 2 → 4 → 5 → 8. Al insertar 4 en 2 → 5 → 8, link pasa del puntero al inicio a 2.next. El nuevo nodo apunta a 5 y 2.next se redirige al nuevo nodo. Si el nuevo elemento es el menor, link sigue designando el puntero al inicio. Al final, designa el enlace nulo final.

Desenlaza antes de liberar
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;
}

El bucle encuentra el enlace entrante. Guardar su destino como old mantiene el nodo disponible mientras *link se cambia para que apunte a su sucesor. Solo entonces se libera el nodo eliminado. Eliminar 5 de 2 → 4 → 5 → 8 da 2 → 4 → 8. Eliminar después 2 cambia el puntero al inicio y deja 4 → 8. Si el valor está ausente, la función devuelve falso sin modificar la lista.

Una versión con dos punteros mantiene prev y cur. Cuando prev es nulo, la coincidencia es el primer nodo y debe cambiarse el puntero al inicio; en otro caso, actualiza prev->next. Olvidar el caso del primer nodo deja un puntero a memoria liberada. La versión con la dirección del enlace gestiona ambos casos mediante la misma asignación.

Comprueba una entrada vacía, una lista de un nodo, coincidencias en el primer y el último nodo y un valor ausente. Para cada cambio, identifica el enlace entrante, el nodo afectado y su sucesor antes de escribir una asignación. Ningún nodo que deba conservarse debería quedar inaccesible, y ningún enlace debería apuntar a un nodo liberado.

Puntos clave

  • La búsqueda devuelve un nodo coincidente o NULL al llegar al final.
  • Un puntero a un enlace puede designar el puntero al inicio o un campo next.
  • La inserción ordenada enlaza el nuevo nodo con su sucesor antes de redirigir el enlace entrante.
  • La eliminación redirige el enlace entrante antes de liberar el nodo eliminado.
  • Prueba los casos del primer y último nodo, la lista vacía, la lista de un nodo y el valor ausente.

3.3 Elegir un array, un puntero al final o enlaces en ambos sentidos

La pregunta útil es qué operaciones realiza tu programa. Los arrays y las listas pueden contener los mismos valores, pero acceden a ellos y los mueven de forma diferente.

Compara las operaciones
TareaArrayLista simplemente enlazada
Llegar a la posición 500Usar a[500] directamenteSeguir enlaces desde el inicio
Insertar después de una posición conocidaDesplazar los elementos posterioresCambiar dos enlaces después de reservar memoria
Recorrer todos los valoresLeer elementos contiguosSeguir nodos que pueden estar dispersos
Eliminar un nodo conocidoDesplazar los elementos posterioresObtener también su enlace entrante

Una lista no hace que encontrar una posición sea gratuito. La inserción después de un nodo del que ya tienes un puntero es directa, pero la eliminación también necesita el predecesor o el enlace que apunta al nodo. Si solo se dispone de la dirección del nodo, una lista simplemente enlazada generalmente requiere un recorrido desde el inicio para encontrar ese enlace entrante.

El almacenamiento también importa. En una plataforma típica de 64 bits, un nodo con un int de 4 bytes y un puntero de 8 bytes ocupa 16 bytes después del relleno. Para 1000 enteros, los nodos usan 16 000 bytes más los datos de gestión de las reservas; un array contiguo de enteros usa 4000 bytes. Recorrer el array suele ser más rápido porque los valores próximos se almacenan juntos.

Mantén juntos los metadatos de la lista
typedef struct {
    node *head;
    node *tail;
    size_t count;
} list_t;

Inicializa una estructura contenedora con list_t list = {0};. Las reglas de su estado vacío son head == NULL, tail == NULL y count == 0. Cada inserción o eliminación que tenga éxito debe actualizar el contador además de los enlaces. Un fallo de reserva no modifica ninguno de ellos.

Con un puntero al final, añadir un nodo después de comprobar la reserva requiere estas asignaciones:

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

Aquí n es un nodo inicializado. Para el primer nodo, los punteros al inicio y al final pasan a ser iguales. Eliminar el último nodo exige mover el puntero al final hacia atrás; en una lista simplemente enlazada esto requiere el predecesor. Eliminar el único nodo establece ambos extremos en NULL y el contador en cero.

Enlaza en ambas direcciones
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);
}

Esta función recibe un nodo existente que pertenece a la lista. Los enlaces de ambos vecinos se actualizan antes de liberarlo. En los extremos se cambia el puntero al inicio o al final en lugar del enlace de un vecino ausente. Una lista doblemente enlazada puede recorrerse hacia atrás y permite eliminar un nodo del que se tiene un puntero sin buscar su predecesor, a costa de otro puntero por nodo y más enlaces que mantener.

Libera los datos que posee el nodo antes de liberarlo

Un word_node puede ser propietario tanto del bloque del nodo como de un bloque para una cadena mediante word. Guarda next, llama a free(n->word), después a free(n) y avanza al sucesor guardado. Liberar solo el nodo provoca una fuga de la cadena. Leer n->word después de liberar el nodo no es válido. La propiedad indica qué código debe liberar cada bloque reservado, incluso cuando varios punteros permiten acceder a él.

Puntos clave

  • Los arrays proporcionan acceso directo por índice y almacenamiento contiguo compacto.
  • Las listas permiten cambios locales en los enlaces una vez que se conoce la posición o el enlace entrante necesario.
  • Un puntero al final permite añadir directamente, pero cada operación debe mantener ambos extremos.
  • Los nodos doblemente enlazados almacenan el predecesor y el sucesor.
  • Libera los datos de los que es propietario un nodo antes de liberar el nodo que almacena su dirección.
  • Un contador almacenado debe cambiar con cada inserción y eliminación que tenga éxito.
4

Implementar pilas y colas

Las pilas y las colas describen el orden en que se procesa el trabajo. Una pila mantiene listo el elemento pendiente más reciente, lo que resulta adecuado para el anidamiento y la evaluación de expresiones. Una cola mantiene listo el elemento que lleva más tiempo esperando, lo que se adapta al orden de llegada. Ambas pueden usar arrays o nodos enlazados. Sus implementaciones deben expresar claramente los estados vacío y lleno y mantener la coherencia de cada contador, extremo e índice después de un cambio.

4.1 Operaciones de pila y trabajo anidado

Una pila elimina primero el elemento añadido más recientemente: último en entrar, primero en salir (LIFO). Sus operaciones definen un tipo abstracto de datos independientemente de que los valores se almacenen en un array o en nodos enlazados. push añade en la cima, pop elimina el elemento de la cima y peek lo lee sin eliminarlo.

Usa un array y un contador
#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;
}

Inicializa con stack s = {0};. Aquí top es el número de elementos almacenados, por lo que el índice ocupado de la cima es top - 1. Una inserción que tiene éxito escribe en la siguiente posición libre e incrementa el contador. Una extracción que tiene éxito primero reduce el contador y después lee ese índice. La función llamadora proporciona un puntero out válido y comprueba el resultado antes de usar su valor.

Apilar 4, 7 y 1 y después extraer dos veces devuelve 1 y 7, y deja 4. Sin una comprobación de capacidad, insertar en una pila llena escribe en items[STACK_CAP] después del final. Sin la comprobación de pila vacía, el top con signo baja de 0 a −1 y la extracción lee items[-1]. Ambos accesos tienen comportamiento indefinido.

Usa el inicio de una lista como cima

Una pila enlazada inserta al inicio un nodo cuya reserva se ha comprobado. La extracción elimina el primer nodo:

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 tiene un number entero y un puntero next. Una pila enlazada no tiene la capacidad fija de un array, pero una inserción puede fallar si falla la reserva. Sigue necesitando comprobaciones de pila vacía y la liberación de los nodos que queden al final.

Comprueba delimitadores anidados

Recorre la entrada de izquierda a derecha. Apila cada paréntesis, corchete o llave de apertura. Para cada delimitador de cierre, extrae uno y comprueba que los tipos coincidan. Un delimitador de cierre sin uno de apertura falla de inmediato. Termina comprobando que la pila esté vacía: los delimitadores de apertura restantes también indican una falta de correspondencia.

El texto «( [ ] { } )» está equilibrado. En «( ]», el delimitador de cierre no corresponde al de apertura extraído. En «( ( )», queda un delimitador de apertura después del recorrido. Un simple recuento de delimitadores de apertura y cierre no detectaría un anidamiento incorrecto como «( [ ) ]».

Evalúa expresiones posfijas

Apila los números a medida que llegan. Para un operador binario, extrae primero el operando derecho y después el izquierdo, calcula left operator right y apila el resultado. Para «3 4 + 2 *», + combina 3 y 4 para obtener 7, después * combina 7 y 2 para obtener 14. Para «9 4 -», extrae 4 como operando derecho y 9 como izquierdo, lo que produce 5. Invertirlos produce −5. Rechaza los casos con operandos insuficientes y exige un único resultado final; la división también necesita un operando derecho distinto de cero.

Las llamadas a funciones tienen tiempos de vida organizados como una pila: una función llamada retorna antes de que continúe la función llamadora. Cada llamada activa necesita un marco, por lo que una recursión muy profunda puede agotar la pila de llamadas incluso cuando las reservas en el montículo tienen éxito.

Puntos clave

  • LIFO significa que el elemento más reciente se elimina primero.
  • En la implementación con array, top cuenta los elementos; comprueba si está vacía o llena antes de acceder.
  • Una pila enlazada inserta y extrae en el inicio de la lista.
  • La comprobación de delimitadores requiere que los tipos coincidan y que la pila esté vacía al final.
  • En una resta o división posfija, el primer valor extraído es el operando derecho.

4.2 Extremos de una cola y almacenamiento circular

Una cola elimina primero el elemento más antiguo: primero en entrar, primero en salir (FIFO). Encolar añade al final; desencolar elimina del inicio. Después de encolar A, B y C, las dos siguientes extracciones devuelven A y B. Esto se adapta al trabajo que debe procesarse en orden de llegada.

Mantén ambos extremos de una cola enlazada

Con nodos que tienen los miembros number y next, e incluyendo <stdbool.h> y <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;
}

Inicializa con queue q = {0};. Encolar establece el enlace del antiguo nodo final antes de mover el puntero al final. Para el primer nodo, también establece el puntero al inicio. Desencolar mueve el puntero al inicio hacia delante. Cuando eso elimina el único nodo, el puntero al final también debe pasar a ser NULL. De lo contrario, la siguiente inserción escribe mediante un puntero al final colgante. Ninguna de las dos operaciones necesita recorrer la lista.

Reutiliza un array fijo como anillo

Una cola en un array simple que siempre elimina del índice 0 debe desplazar los valores restantes. Un búfer circular mantiene, en cambio, un índice del inicio y un contador de elementos:

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

Inicializa un anillo con {0}. head siempre identifica el elemento inicial cuando la cola no está vacía. El siguiente índice de inserción es el inicio más el número de elementos, con vuelta al principio mediante % CAP. Lee el elemento inicial antes de mover head. El contador distingue entre vacío y lleno sin renunciar a una posición.

Sigue la vuelta al principio

Encola A, B y C en las posiciones 0, 1 y 2. Desencola A: el inicio pasa a ser 1 y el contador pasa a ser 2. D entra en la posición 3; E entra en la posición 0. El array físico contiene ahora E, B, C, D, pero el orden lógico de extracción es B, C, D, E. El anillo está lleno porque el contador es 4, aunque el inicio esté en el índice 1.

Otro diseño mantiene el índice del inicio y el índice de la siguiente posición libre al final sin un contador. En ese diseño, índices iguales podrían significar vacío o lleno, por lo que una política habitual deja una posición sin usar. Vacío es head == tail; lleno es (tail + 1) % CAP == head. No mezcles esa política con las reglas de capacidad de la versión basada en un contador. Las colas también permiten visitar árboles nivel por nivel al mantener los nodos que esperan su turno.

Puntos clave

  • FIFO elimina el valor que lleva más tiempo en la cola.
  • Una cola enlazada añade al final y elimina del inicio.
  • Eliminar el único nodo debe restablecer tanto el puntero al inicio como el puntero al final.
  • Una cola circular hace volver los índices al principio y mantiene el orden lógico independientemente del orden físico.
  • Usa un contador o una política de posición sin usar para distinguir entre vacío y lleno.
  • Lee el elemento inicial antes de avanzar el puntero al inicio.
5

Construir y recorrer árboles binarios

Los árboles sustituyen una sola cadena por ramas. Un árbol de búsqueda usa el orden de las claves para elegir una rama, pero su forma determina a cuántos nodos llega esa búsqueda. La misma idea de subárbol guía la inserción, el recorrido, el recuento y la eliminación. Cada operación necesita un caso vacío, una regla para gestionar el nodo actual y una decisión clara sobre qué puntero del padre debe recibir la raíz modificada de un subárbol.

5.1 Buscar en un árbol siguiendo la regla de ordenación

Un árbol binario da a cada nodo como máximo dos hijos, llamados izquierdo y derecho. La raíz es el nodo superior. Una hoja no tiene hijos y un nodo, junto con todos sus descendientes, es un subárbol. Un hijo ausente y un árbol vacío se representan mediante NULL.

Representa las ramas
typedef struct tnode {
    int key;
    struct tnode *left;
    struct tnode *right;
} tnode;

Cada puntero a un hijo conduce a la raíz de otro árbol. Por tanto, la misma definición describe una hoja, un árbol grande con ramas o una cadena larga. La altura cuenta las aristas del camino más largo desde la raíz hasta una hoja: una raíz sola tiene altura 0. Usa −1 para un árbol vacío, de modo que los dos subárboles vacíos de una hoja den naturalmente altura 0. Contar niveles daría uno más.

Aplica la regla a subárboles completos

Un árbol binario de búsqueda, o BST, ordena las claves. Para cada nodo, todas las claves de su subárbol izquierdo son menores y todas las de su subárbol derecho son mayores. Almacena cada clave una sola vez. Esta condición incluye a los descendientes lejanos. Una raíz 50 con hijo izquierdo 30 y un hijo derecho 60 bajo ese 30 no es válida: 60 sigue estando en el subárbol izquierdo de 50.

Inserta 50, 30, 70, 20, 40, 60 y 80 en un BST vacío. La raíz es 50; 30 y 70 son sus hijos. Los hijos de 30 son 20 y 40, y los de 70 son 60 y 80. Cada elección de rama conserva los límites heredados de los antepasados.

Elige una rama en cada paso
#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;
}

Buscar 60 en el ejemplo visita 50, va a la derecha hasta 70 y después a la izquierda hasta 60: tres nodos. Buscar 65 sigue el mismo camino y después intenta acceder al hijo derecho de 60. Ese hijo es NULL, por lo que 65 está ausente. No necesitas examinar el subárbol izquierdo de 50 porque todas sus claves son menores que 50.

La forma recursiva gestiona primero t == NULL y una clave coincidente, y después busca en el hijo seleccionado. La comprobación del subárbol vacío es el caso base. Sin ella, una búsqueda fallida acaba desreferenciando un puntero nulo. La versión con bucle se detiene en el mismo límite sin añadir llamadas recursivas.

Usa la forma para explicar el trabajo

Un árbol perfectamente equilibrado con 20 niveles contiene 1 048 575 claves y una búsqueda visita como máximo 20 nodos. En cambio, insertar las claves ya ordenadas 10, 20, 30, 40, 50 crea una cadena hacia la derecha. Su altura es 4 y encontrar 50 visita los cinco nodos. La regla de ordenación de un BST por sí sola no mantiene el árbol bajo. Los árboles autoequilibrados, como los árboles AVL, reorganizan las ramas para controlar la altura.

Para encontrar un mínimo, sigue los hijos izquierdos hasta que el siguiente enlace izquierdo sea NULL. Para encontrar un máximo, sigue los hijos derechos. En el ejemplo de siete nodos estas claves son 20 y 80. Las mismas elecciones de dirección funcionan en la raíz de cualquier subárbol porque la regla de ordenación también se aplica allí.

Puntos clave

  • Un nodo binario tiene como máximo dos hijos; los hijos ausentes son punteros nulos.
  • La ordenación de un BST se aplica a todos los descendientes de cada subárbol.
  • La búsqueda sigue a la izquierda para una clave menor y a la derecha para una clave mayor.
  • Aquí la altura cuenta aristas, con una altura de −1 para el árbol vacío.
  • Un árbol bajo reduce las visitas; un BST puede llegar a convertirse en una cadena.

5.2 Actualizar, recorrer y liberar un árbol de búsqueda

Las funciones sobre árboles trabajan sobre un subárbol y conectan su resultado con el padre. Mantén explícito el caso del subárbol vacío y distingue las operaciones que modifican enlaces de las que solo visitan nodos.

Inserta mediante el enlace al subárbol

Con tnode que contiene key, left y right, incluye <stdbool.h> y <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;
}

Llama con insert(&root, key) y comprueba su resultado. Una clave existente deja el árbol sin cambios y se considera un éxito. La dirección del enlace permite que la inserción actualice la raíz de la función llamadora o un puntero a un hijo. En una interfaz que devuelve la raíz, guarda cada resultado: t->left = insert(t->left, key) y root = insert(root, key). Ignorar un bloque reservado devuelto cuando el subárbol original estaba vacío deja ese nodo sin enlazar y provoca una fuga.

Elige cuándo visitar la raíz
RecorridoOrden de visitaPropósito habitual
InordenIzquierda, raíz, derechaMostrar ordenadas las claves de un BST
PreordenRaíz, izquierda, derechaVisitar los padres antes que los hijos
PostordenIzquierda, derecha, raízTerminar con los hijos antes de liberar un padre

Para las inserciones 50, 30, 70, 20, 40, 60, 80, la salida en inorden es «20 30 40 50 60 70 80». El preorden da «50 30 20 40 70 60 80»; el postorden da «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);
}

Después de free_tree(root), establece root = NULL en el código propietario. La función libera primero los hijos para poder leer sus direcciones desde el padre, que aún existe. El tamaño es cero para un subárbol vacío y, en otro caso, uno más los tamaños de ambos hijos. La altura es −1 para un árbol vacío y, en otro caso, uno más la mayor altura de los hijos. El ejemplo tiene tamaño 7 y altura 2.

Elimina sin romper el orden

Una hoja pasa a ser NULL. Un nodo con un hijo se sustituye por ese hijo. Para dos hijos, copia la clave menor del subárbol derecho en el nodo y después elimina ese sucesor de su posición original:

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

Llama como root = erase(root, key). Eliminar 50 del ejemplo coloca 60 en la raíz y elimina el antiguo nodo 60. La salida en inorden pasa a ser «20 30 40 60 70 80»; en preorden pasa a ser «60 30 20 40 70 80». Un sucesor puede tener un hijo derecho, por lo que su eliminación debe volver a conectar ese hijo.

Para claves de palabras, usa el signo de strcmp, no la igualdad de punteros, y reserva un bloque propio para cada palabra de la que el árbol sea propietario. Libera la palabra antes que su nodo. La eliminación de registros de este tipo con dos hijos también necesita un plan de propiedad para la palabra trasladada y cualquier valor asociado; copiar un puntero propietario y después liberar la palabra de su nodo original dejaría una clave colgante.

Puntos clave

  • Una inserción debe establecer una nueva raíz de subárbol e informar de los fallos de reserva.
  • El inorden muestra ordenadas las claves de un BST; el preorden empieza por el padre.
  • La liberación en postorden libera ambos hijos antes que su padre.
  • El tamaño suma los tamaños de ambos hijos; la altura toma la mayor altura de los hijos.
  • La eliminación reconecta cero o un hijo, o sustituye la clave de un nodo con dos hijos por la de su sucesor.
  • Guarda la raíz devuelta después de la eliminación, incluida la eliminación de la raíz original.
  • Los árboles con claves de cadena comparan texto y gestionan cada bloque reservado para una cadena de su propiedad.
6

Almacenar y buscar claves en tablas de dispersión

Las tablas de dispersión convierten una clave en una ubicación inicial y después resuelven las colisiones que se produzcan allí. La función de dispersión y la prueba de igualdad deben coincidir sobre qué claves se consideran iguales. Una implementación con encadenamiento combina arrays, inserción en listas, copias de cadenas y liberación de recursos. Su utilidad depende de distribuir las claves entre las cubetas y reconstruir esas ubicaciones cuando cambia el número de cubetas.

6.1 Asignar claves a cubetas con una función de dispersión y gestionar colisiones

Una tabla de dispersión elige una ubicación inicial a partir de una clave. Una función de dispersión produce un número; calcular el resto de dividir ese número entre un número positivo de cubetas da un índice. El encadenamiento separado mantiene una lista enlazada en cada cubeta, por lo que la búsqueda recorre la cadena seleccionada.

Separa la dispersión de la igualdad

Una función de dispersión útil es determinista, rápida de calcular y distribuye las claves esperadas entre las cubetas. Las claves iguales deben producir valores de dispersión iguales. Las claves diferentes aún pueden compartir un índice: esto es una colisión, y toda tabla debe gestionarla. Cuando hay más claves posibles que cubetas, algunas colisiones son inevitables.

Para palabras que empiezan por una letra en un sistema compatible con ASCII, toupper((unsigned char)word[0]) - 'A' asigna 26 cubetas de letras; «Luigi» empieza en la cubeta 11. Muchas palabras inglesas empiezan por «s» y pocas por «x», por lo que esta regla puede dejar cadenas muy desiguales. La conversión explícita de tipo es necesaria porque las funciones de <ctype.h> requieren un valor de carácter sin signo o EOF. Valida la letra inicial y el supuesto sobre el conjunto de caracteres antes de usar esta regla de indexación.

Una función de dispersión basada en la suma de caracteres también tiene debilidades. Con códigos ASCII y diez cubetas, «cat» suma 99 + 97 + 116 = 312 y llega a la cubeta 2; «dog» suma 314 y llega a la cubeta 4. «act» también suma 312. La suma pierde el orden de las letras, por lo que todas sus reorganizaciones colisionan.

Incorpora el orden de los caracteres al valor de dispersión
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;
}

Este patrón djb2 usa aritmética sin signo, cuyo desbordamiento se reduce modularmente de forma definida. El desbordamiento con signo tendría comportamiento indefinido. Para las cadenas ASCII «cat» y «act», los valores son 193 488 125 y 193 486 013. Calcula el índice con hash_word(word) % N, donde N es el número positivo de cubetas. El orden de las letras cambia estos valores de dispersión, aunque calcular el resto aún puede producir una colisión.

Si una tabla trata «Cat» y «cat» como iguales, tanto la función de dispersión como la comparación deben usar la misma regla para las mayúsculas y minúsculas. Ignorarlas solo durante la comparación puede enviar claves iguales a cubetas distintas, donde la búsqueda no las comparará. Un resto negativo de un valor de dispersión con signo tampoco es adecuado como índice de array.

Elige una estrategia para las colisiones

El encadenamiento separado almacena cada colisión en la lista enlazada de una cubeta. La búsqueda compara claves hasta encontrar una coincidencia o llegar al final. El direccionamiento abierto almacena las entradas en el propio array. Con sondeo lineal, una colisión lleva a probar la posición siguiente y a volver al principio al llegar al final. La búsqueda sigue la misma secuencia de sondeo, por lo que puede examinar varias posiciones del array.

Reconstruye los índices cuando la tabla crece

El factor de carga es el número de claves almacenadas dividido entre el número de cubetas. Doce claves en ocho cubetas dan 1,5 entradas por cubeta de media. Dieciséis cubetas reducen esa media a 0,75. Esta media no garantiza que todas las cadenas sean cortas; una función de dispersión deficiente puede colocar todas las claves juntas.

Un índice de cubeta depende del número de cubetas. Después de reservar e inicializar un array de cubetas más grande, vuelve a calcular el índice de cada clave almacenada para el nuevo número de cubetas y enlázala allí. Copiar los antiguos punteros al inicio en las nuevas cubetas con los mismos números deja las claves en lugares donde la nueva búsqueda no las buscará. Una tabla con encadenamiento puede contener más entradas que cubetas; el direccionamiento abierto necesita posiciones sin usar para las nuevas entradas y para un sondeo fiable.

Puntos clave

  • Calcula el resto de dividir un valor de dispersión determinista entre un número positivo de cubetas para obtener un índice.
  • Las claves iguales necesitan valores de dispersión iguales; las claves distintas pueden colisionar.
  • La aritmética sin signo evita el desbordamiento indefinido con signo en el cálculo de la dispersión.
  • El encadenamiento recorre una lista de una cubeta; el direccionamiento abierto sigue una secuencia de sondeo.
  • El factor de carga mide las entradas por cubeta, no la cadena más larga.
  • Vuelve a calcular el valor de dispersión de cada clave cuando cambie el número de cubetas.

6.2 Construir una tabla de recuento de palabras propietaria de sus claves

Una tabla con encadenamiento combina un array, enlaces de lista, comparación de cadenas y propiedad de la memoria. Cada entrada es propietaria de su clave copiada. El array de cubetas es propietario de las cadenas y cada cadena termina en NULL.

Inicializa cubetas y busca texto
#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];

El almacenamiento estático empieza con punteros nulos en todas las cubetas. Un array local necesita, en cambio, un inicializador como entry *table[N] = {NULL};. Usa la función hash_word sin signo que empieza en 5381 e incorpora cada byte con el multiplicador 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 compara texto. e->key == key comprobaría si los punteros designan la misma ubicación. Dos búferes separados pueden contener palabras iguales, por lo que la igualdad de punteros no sirve para buscar una palabra.

Cuenta una clave existente o instala una nueva
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 función llamadora proporciona una cadena terminada en nulo. La comprobación de INT_MAX informa de un fallo antes de que el contador desborde su almacenamiento int. El byte adicional de la reserva de la cadena contiene '\0'. Si falla la segunda reserva, libera la entrada que aún no está enlazada. El puntero al inicio de la cubeta solo cambia después de que ambas reservas y la inicialización tengan éxito.

Guardar e->key = key conservaría, en cambio, el espacio de almacenamiento de la función llamadora. Si la entrada reutiliza repetidamente un búfer, cada puntero guardado podría mostrar la palabra más reciente. Copiar el texto da a cada entrada un tiempo de vida independiente de ese búfer. strdup proporciona esa operación en POSIX y C23; la reserva explícita y strcpy funcionan en C17.

En un ejemplo con diez cubetas y una suma de caracteres, insertar «cat», «dog», «act» deja «act» → «cat» en la cubeta 2 y «dog» en la cubeta 4. Buscar «tac» recorre la cubeta 2, compara ambas palabras y llega a NULL. Una cubeta coincidente solo es una ubicación candidata, no una prueba de que haya una clave igual.

Libera los datos, los nodos y los punteros al inicio de las cubetas
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;
    }
}

Guardar next y liberar la clave ocurre mientras la entrada aún existe. Restablecer cada cubeta permite reutilizarla. Las herramientas de comprobación de memoria ayudan a verificar las rutas ejecutadas; las rutas de fallo de reserva también necesitan liberar recursos. El orden de dispersión no es un orden clasificado. Para mostrar las claves alfabéticamente, recógelas para ordenarlas o almacénalas en un BST.

En el direccionamiento abierto, la eliminación necesita una marca de borrado para que la búsqueda continúe a través de una posición eliminada hasta las claves posteriores que colisionaron. Una posición realmente sin usar puede terminar una búsqueda fallida. Las secuencias de sondeo deben estar limitadas y la tabla debe ampliarse antes de que todas las posiciones estén ocupadas.

Puntos clave

  • Inicializa cada puntero al inicio de cubeta a NULL.
  • La función de dispersión selecciona una cadena; strcmp confirma si una clave coincide.
  • Incrementa el contador de una clave existente en vez de añadir una entrada duplicada.
  • Copia las cadenas de entrada y libera la entrada si falla la reserva para la clave.
  • Libera la clave antes que su entrada, guarda el sucesor y restablece cada cubeta.
  • La eliminación en direccionamiento abierto conserva las rutas de búsqueda mediante marcas de borrado.

Ejemplos de Estructuras y listas enlazadas en C

6 Lecciones Interactivas

1

Seguir los registros a través de copias y punteros

15 pasos
Construye un registro de lectura, pásalo a funciones, recorre un array y distingue los datos integrados de los punteros propietarios.
  1. 1
    IntroducciónMantén unida una lectura

    Una lectura combina un identificador y una temperatura. Mantenerlos en un solo registro, un struct de C, permite al código pasar o almacenar el par como un único valor.

    Sigue ese registro desde su definición hasta una llamada a una función y después hasta un array. En cada paso, distingue el objeto de los punteros que permiten acceder a él: esa distinción determina si un cambio afecta a una copia o al original, y si una copia superficial de un miembro puntero comparte almacenamiento.

  2. 2
    EjemploDefine el registro

    La definición introduce un tipo; la siguiente declaración crea un objeto de ese tipo.

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

    El punto selecciona un miembro de r, por lo que su temperatura es ahora 17,0 °C. Cada miembro tiene su propio tipo y el punto y coma después de la llave de cierre termina la definición del tipo.

  3. 3
    Idea claveCopia los valores de los miembros

    Ahora crea un segundo registro con struct reading s = r;. Esa asignación copia los valores de ambos miembros. Modificar después s.id o s.temp_c deja r sin cambios.

    Un array almacenado dentro de una estructura también se copia. El relleno aún puede diferir entre objetos, por lo que comprobar la igualdad implica comparar los miembros pertinentes para la tarea. C no proporciona un operador == para estructuras completas.

  4. 4
    DesafíoLee un miembro omitido

    ¿Qué valor coloca struct reading a = {.id = 31}; en a.temp_c y puede el código leer ese valor?

    ---reveal---

    El miembro omitido se inicializa a cero, por lo que la temperatura es 0.0 y puede leerse. Hay un inicializador aunque solo nombre id. En cambio, una declaración de un a local sin inicializador requeriría asignaciones antes de leer sus miembros.

  5. 5
    ExplicaciónDa al tipo un nombre corto

    Un typedef proporciona un alias sin cambiar el comportamiento de los objetos.

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

    Una llamada puede devolver el registro completo por valor. Si un nodo debe referirse a su propio tipo dentro de una definición, dale una etiqueta y escribe ese enlace a sí mismo con struct node *next.

  6. 6
    EjemploElige una copia o una dirección

    Compara dos interfaces de función:

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

    warm_copy(r) modifica una copia local. warm(&r) accede al registro de la función llamadora y modifica su miembro. La flecha significa (*p).temp_c; sus paréntesis hacen que la desreferenciación ocurra antes de que el punto seleccione el miembro.

  7. 7
    ConsejoReserva un registro con un propietario definido

    Incluye <stdlib.h> y reserva memoria con reading *p = malloc(sizeof *p);. Antes de escribir mediante p, comprueba si es NULL y gestiona el fallo.

    Si tiene éxito, inicializa los campos y mantén un propietario responsable de free(p). Devolver el valor de una estructura es seguro; devolver la dirección de una estructura local deja un puntero colgante porque ese objeto local deja de existir cuando su función retorna.

  8. 8
    DesafíoPredice qué temperatura cambia

    Una función llamadora empieza con reading r = {24, 17.0};, llama a warm_copy(r) y después a warm(&r) usando las dos funciones mostradas. ¿Qué temperatura contiene r?

    ---reveal---

    Contiene 18,0 °C. La primera llamada solo modifica su copia privada, por lo que la función llamadora sigue teniendo 17,0 cuando empieza la segunda llamada. La segunda recibe la dirección del registro de la función llamadora y suma uno mediante ese puntero.

  9. 9
    EjemploRecorre un conjunto de registros

    Supón que un array contiene las temperaturas −5,5; −1,0 y −7,5 °C. Después de comprobar que el número de elementos no es cero, inicia best en el índice 0 y compara cada registro posterior con el mejor hasta ese momento.

    El índice 1 sustituye al índice 0 porque −1,0 es más cálido que −5,5. El índice 2 no lo sustituye. Empezar por un elemento real funciona cuando todos los valores están por debajo de cero; inventar un máximo inicial de cero haría que no se encontraran.

  10. 10
    ConsejoDevuelve un signo desde la función de comparación

    Para temperaturas finitas, una función de comparación puede devolver (x->temp_c > y->temp_c) - (x->temp_c < y->temp_c). El resultado es negativo, cero o positivo.

    Devolver la diferencia de temperatura como int puede convertir una diferencia pequeña en cero. La resta de enteros puede desbordarse para claves extremas. Un signo basado en comparaciones evita ambos errores, y qsort sigue sin garantizar que se conserve el orden de los elementos iguales.

  11. 11
    ExplicaciónPermite recuperarse de un fallo al cambiar el tamaño

    Para un array en el montículo, mantén el número de elementos utilizados separado de la capacidad. Comprueba que el número de bytes solicitado quepa en el tipo correspondiente y después asigna el resultado de realloc con tamaño distinto de cero a un puntero temporal.

    Si falla, el array original sigue disponible. Si tiene éxito, establece la nueva dirección base y la capacidad juntas. Reconstruye los punteros a elementos a partir de sus índices guardados porque el bloque reservado puede haberse movido o haber sustituido su espacio de almacenamiento anterior.

  12. 12
    ConexiónObserva qué comparte una copia de un puntero

    Un registro de persona con char *name contiene una dirección, por lo que person b = a; deja ambos nombres apuntando a una misma cadena. Eso es una copia superficial.

    Si cada registro debe ser propietario de una cadena independiente, da a b.name su propio bloque reservado, comprueba la reserva y copia el texto, incluido el byte nulo de terminación. Cada propietario puede entonces liberar su propia cadena una vez. Copiar un array de caracteres integrado ya proporciona caracteres separados.

  13. 13
    DesafíoCorrige la copia del nombre

    El name de un registro es una cadena del montículo de su propiedad. El código copia el registro y después planea liberar ambos nombres de forma independiente. ¿Qué debe ocurrir antes de esa liberación?

    ---reveal---

    Reserva un nombre separado para la copia, comprueba que la reserva tenga éxito y copia el texto original antes de liberar cualquiera de las dos cadenas. Los dos registros comparten actualmente una dirección. Liberar ambos punteros compartidos liberaría el mismo bloque dos veces; copiar de nuevo el puntero no ayudaría.

  14. 14
    DesafíoDevuelve un registro con una etiqueta integrada

    Una función devuelve por valor una estructura que contiene char label[12]. ¿Queda colgante la etiqueta porque la estructura original era local? Explica tu respuesta usando tanto el tiempo de vida como la copia.

    ---reveal---

    No. Devolver por valor copia los valores de los miembros de la estructura, incluido el array integrado, en el valor devuelto. La función llamadora es propietaria del registro resultante. En cambio, devolver un puntero a la estructura local pediría a la función llamadora que accediera a un objeto cuyo tiempo de vida ha terminado.

  15. 15
    RepasoSigue los valores y sus propietarios

    Un objeto de tipo estructura almacena valores de miembros. Un puntero designa un objeto y un puntero copiado sigue designando el mismo destino. Los parámetros y valores de retorno de las funciones siguen esa distinción.

    Para los arrays, mantén explícitos los números de elementos y conserva el propietario durante el cambio de tamaño. Para los registros con punteros propietarios, decide si una copia comparte almacenamiento o recibe bloques independientes. Esas decisiones determinan tanto las actualizaciones como la liberación de recursos.

2

Mantener la coherencia entre las opciones y los datos de las uniones

11 pasos
Sigue los valores de una enumeración, rechaza los huecos, construye una celda etiquetada y elige entre campos simultáneos y alternativos.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónDa nombre a la opción e identifica sus datos
  2. 2
    EjemploSigue los valores explícitos de una enumeración
  3. 3
    Idea claveComprueba el conjunto permitido
  4. 4
    ConsejoImpide que la ejecución continúe en el caso siguiente
  5. 5
    DesafíoValida un valor de etapa
  6. 6
    ExplicaciónSuperpón los valores alternativos
  7. 7
    EjemploEstablece ambas partes de un valor etiquetado
  8. 8
    DesafíoDetecta la actualización que no coincide
  9. 9
    ConsejoAlmacena un pequeño rango de indicadores
  10. 10
    DesafíoElige el almacenamiento para un registro
  11. 11
    RepasoMantén cada descripción en consonancia con sus datos
3

Seguir la creación, inserción y eliminación de una lista

16 pasos
Usa enlaces entrantes para conservar una cadena y después mantén los extremos, los contadores y los datos de su propiedad.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónModifica una cadena un enlace a la vez
  2. 2
    ExplicaciónEmpieza con un puntero al inicio nulo
  3. 3
    EjemploInserta al inicio sin perder la cadena
  4. 4
    Idea clavePermite que la función llamadora conserve el nuevo puntero al inicio
  5. 5
    DesafíoPredice el orden de inserción al inicio
  6. 6
    ExplicaciónSigue la dirección de un enlace
  7. 7
    EjemploEncuentra un punto de inserción ordenada
  8. 8
    DesafíoInserta antes del primer nodo
  9. 9
    EjemploElimina un nodo intermedio de forma segura
  10. 10
    ConsejoDetente de forma segura cuando falte un valor
  11. 11
    ExplicaciónAñade un puntero al final y un contador
  12. 12
    ConexiónUsa un enlace hacia atrás cuando la eliminación lo necesite
  13. 13
    DesafíoElige almacenamiento para lecturas repetidas por índice
  14. 14
    ConsejoLibera los datos antes que su nodo
  15. 15
    DesafíoCombina la eliminación y los metadatos
  16. 16
    RepasoMantén válida la ruta entrante
4

Seguir el trabajo de una pila y la vuelta al principio de una cola

14 pasos
Evalúa operaciones anidadas, mantén los extremos de una cola enlazada y sigue el orden lógico de una cola circular.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónElige qué elemento en espera va después
  2. 2
    ExplicaciónUsa top como contador de elementos
  3. 3
    EjemploSigue el orden de la pila
  4. 4
    EjemploRelaciona el delimitador de cierre con el de apertura de la cima
  5. 5
    EjemploMantén el orden de los operandos posfijos
  6. 6
    DesafíoComprueba una expresión posfija modificada
  7. 7
    ExplicaciónDistingue el inicio del final
  8. 8
    ConsejoCorrige el caso de un solo nodo
  9. 9
    ExplicaciónUsa un anillo en vez de desplazar elementos
  10. 10
    EjemploSigue una distribución que vuelve al principio
  11. 11
    DesafíoEncuentra la siguiente posición de inserción
  12. 12
    Idea claveMantén una sola política para el estado lleno
  13. 13
    DesafíoSelecciona el orden de trabajo
  14. 14
    RepasoHaz que el orden y el estado concuerden
5

Construir, buscar, recorrer y eliminar en un BST

15 pasos
Sigue un árbol durante la selección de ramas, la inserción, los órdenes de recorrido, la sustitución por el sucesor y la liberación de recursos.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue un árbol desde la raíz hasta un subárbol
  2. 2
    Idea claveAplica la regla por debajo de cada antepasado
  3. 3
    EjemploConstruye el árbol del ejemplo
  4. 4
    EjemploBusca una rama a la vez
  5. 5
    DesafíoDiagnostica una infracción en un descendiente
  6. 6
    ConexiónMide la forma
  7. 7
    EjemploEnlaza una inserción donde se detiene la búsqueda
  8. 8
    EjemploElige un orden de visita
  9. 9
    ExplicaciónCuenta las ramas y la longitud del camino
  10. 10
    ExplicaciónGestiona cero o un hijo
  11. 11
    EjemploSustituye la clave de un nodo con dos hijos
  12. 12
    DesafíoConserva el hijo del sucesor
  13. 13
    ConsejoLibera los descendientes antes que su padre
  14. 14
    DesafíoComprueba el recorrido y la altura juntos
  15. 15
    RepasoVuelve a conectar cada subárbol modificado
6

Construir un contador de palabras con una tabla de dispersión

14 pasos
Sigue las colisiones, la igualdad, las claves copiadas, la ampliación, los fallos de reserva y la liberación completa de recursos.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónUsa una clave para acotar la búsqueda
  2. 2
    EjemploCalcula una cubeta inicial
  3. 3
    Idea claveMantén la coherencia entre igualdad y dispersión
  4. 4
    DesafíoDistingue una colisión de una coincidencia
  5. 5
    ExplicaciónUsa el factor de carga sin perder de vista la distribución
  6. 6
    EjemploVuelve a calcular la ubicación después de ampliar
  7. 7
    ExplicaciónBusca texto en una cadena
  8. 8
    EjemploInstala una nueva clave propia
  9. 9
    DesafíoGestiona una reserva parcial
  10. 10
    ConsejoNo conserves un búfer de entrada reutilizado
  11. 11
    ExplicaciónConserva las rutas de sondeo después de eliminar
  12. 12
    DesafíoCombina la gestión de colisiones y el recuento de palabras
  13. 13
    ConexiónLibera cada clave y cada nodo
  14. 14
    RepasoHaz que la ubicación, la igualdad y el tiempo de vida concuerden

Ejercicios de Estructuras y listas enlazadas en C

50 Preguntas de Práctica en 5 sets

Set 1: Leer registros y seguir operaciones básicas10 Preguntas de Práctica

Pregunta 1 de 10

¿Qué valor tiene r.temp_c después de struct reading { int id; double temp_c; }; struct reading r = {.id = 12};?
  1. Un valor indeterminado
  2. 0.0
  3. 12.0
  4. Un error de inicialización
Mostrar respuestaOcultar respuesta
Respuesta: B
El inicializador designado establece id e inicializa a cero el miembro omitido temp_c, por lo que su valor es 0.0. Esto difiere de declarar una estructura local sin ningún inicializador. El inicializador proporcionado permite usar el miembro omitido sin una asignación posterior.

Pregunta 2 de 10

Un reading *p válido apunta a un registro de temperatura. ¿Qué expresión accede a su miembro temp_c?
  1. p.temp_c
  2. *p.temp_c
  3. &p.temp_c
  4. p->temp_c
Mostrar respuestaOcultar respuesta
Respuesta: D
El operador flecha selecciona un miembro mediante un puntero y equivale a (*p).temp_c. Un punto necesita un objeto de tipo estructura a su izquierda, por lo que p.temp_c no es válido aquí. Escribir *p.temp_c no corrige ese problema porque el punto se aplica antes de la desreferenciación.

Pregunta 3 de 10

Unos registros tienen temperaturas finitas de −8,0; −2,5 y −6,0 °C en los índices 0, 1 y 2. Un recorrido en busca del máximo empieza con best = 0 y se actualiza cuando un registro es estrictamente más cálido. ¿Qué índice queda?
  1. 1
  2. 0
  3. 2
  4. Ningún índice; el máximo sigue en cero
Mostrar respuestaOcultar respuesta
Respuesta: A
El recorrido empieza con el registro del índice 0 y después lo sustituye por el índice 1 porque −2,5 es más cálido que −8,0. El último, −6,0, no es más cálido que −2,5, por lo que queda el índice 1. Las temperaturas negativas no causan ningún problema especial cuando el mejor inicial es un registro real.

Pregunta 4 de 10

¿Qué valor tiene PAUSED en enum state { IDLE = 3, RUNNING, FAILED = 9, PAUSED };?
  1. 5
  2. 4
  3. 10
  4. 9
Mostrar respuestaOcultar respuesta
Respuesta: C
Un valor implícito de enumeración es uno más que el enumerador inmediatamente anterior. FAILED establece explícitamente la secuencia en 9, por lo que PAUSED es 10. Contar PAUSED desde el principio de la declaración ignoraría el reinicio introducido por el valor explícito.

Pregunta 5 de 10

Una unión etiquetada tiene la etiqueta de enumeración kind y la unión as con los miembros int i y double d. Un valor existente tiene la etiqueta K_DOUBLE. ¿Qué actualización almacena el entero 18 de forma coherente?
  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;
Mostrar respuestaOcultar respuesta
Respuesta: C
La etiqueta debe indicar qué miembro contiene el valor almacenado. Asignar K_INT y escribir en as.i hace que ambas partes describan el entero 18. Escribir en el miembro entero mientras se conserva la etiqueta de double dirigiría al código posterior a leer la representación incorrecta; la etiqueta no cambia automáticamente.

Pregunta 6 de 10

En una lista vacía se insertan al inicio 6, 11 y después 3. ¿Qué valores aparecen al recorrerla desde su inicio?
  1. 6 → 11 → 3
  2. 3 → 11 → 6
  3. 11 → 6 → 3
  4. 3 → 6 → 11
Mostrar respuestaOcultar respuesta
Respuesta: B
Cada inserción al inicio coloca el nuevo nodo antes del inicio actual. Por tanto, los tres inicios son 6, después 11 y después 3, con los nodos anteriores enlazados detrás. El orden de llegada 6 → 11 → 3 resultaría de añadir al final, no de las inserciones al inicio indicadas.

Pregunta 7 de 10

Una lista ordenada contiene 1 → 7 → 12. Un bucle de inserción avanza mientras el valor actual es menor que 9. ¿En qué enlace entrante se detiene?
  1. El puntero al inicio que apunta al nodo que contiene 1
  2. El campo next del nodo que contiene 1
  3. El campo next del nodo que contiene 12
  4. El campo next del nodo que contiene 7
Mostrar respuestaOcultar respuesta
Respuesta: D
El bucle pasa por 1 y 7 y después se detiene antes de 12 porque 12 no es menor que 9. El enlace que llega actualmente a 12 es el miembro next del nodo 7. Detenerse en el enlace posterior a 12 añadiría 9 al final e incumpliría el orden ascendente de la lista.

Pregunta 8 de 10

Una pila vacía ejecuta: apilar 8, apilar 2, extraer, apilar 5, extraer, extraer. ¿Qué valores devuelven las tres extracciones?
  1. 2, 5, 8
  2. 8, 2, 5
  3. 2, 8, 5
  4. 5, 2, 8
Mostrar respuestaOcultar respuesta
Respuesta: A
La primera extracción elimina 2, el valor más reciente en ese momento. Apilar 5 lo coloca encima del 8 restante, por lo que las siguientes extracciones devuelven 5 y 8. Devolver 8 primero seguiría el orden de una cola y no correspondería al comportamiento de último en entrar, primero en salir de la pila.

Pregunta 9 de 10

Se construye un BST insertando 42, 18, 63, 27 y 21. ¿Qué nodos visita una búsqueda de 21?
  1. 42 → 18 → 21
  2. 42 → 63 → 27 → 21
  3. 18 → 27 → 21
  4. 42 → 18 → 27 → 21
Mostrar respuestaOcultar respuesta
Respuesta: D
En 42, 21 es menor, por lo que la búsqueda va a la izquierda hasta 18; después va a la derecha hasta 27 y a la izquierda hasta 21. La inserción de 21 lo colocó bajo 27, no directamente bajo 18. Una búsqueda sigue los enlaces a hijos existentes y no puede saltar a una clave menor solo porque se sepa que está presente.

Pregunta 10 de 10

Una tabla con entradas trata las palabras ASCII sin distinguir mayúsculas de minúsculas, pero calcula su dispersión a partir de los bytes originales. Una búsqueda de «mint» no encuentra una entrada existente «Mint» sin llegar a visitarla. ¿Qué corrección completa sirve tanto para las entradas existentes como para las futuras?
  1. Mantener la dispersión de bytes originales, ignorar mayúsculas en la igualdad y reconstruir las mismas ubicaciones.
  2. Ignorar mayúsculas en la dispersión, mantener la igualdad sin distinción de mayúsculas y reconstruir las ubicaciones.
  3. Ignorar mayúsculas en la dispersión, mantener la igualdad sin distinción de mayúsculas y conservar las ubicaciones antiguas.
  4. Mantener la dispersión de bytes originales, comparar direcciones y ampliar el array de cubetas.
Mostrar respuestaOcultar respuesta
Respuesta: B
La función de dispersión debe tratar las formas iguales de la misma manera, por lo que hay que normalizar las mayúsculas y minúsculas antes de calcularla y mantener la comparación que no las distingue. Las entradas existentes también necesitan ubicarse según la regla corregida. Cambiar solo los cálculos futuros deja las entradas antiguas en lugares que sus búsquedas corregidas podrían no examinar nunca.
Set 2: Aplicar reglas de estado y almacenamiento10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Se usa struct tag { char name[8]; int id; }; para a y b. Después de b = a; b.name[0] = 'Z';, ¿qué ocurre con a.name[0]?
  1. Cambia al carácter Z.
  2. Pasa a ser un valor indeterminado.
  3. Conserva su carácter original.
  4. La asignación de la estructura no compila.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Dada void cool(reading r) { r.temp_c -= 2.0; }, una función llamadora establece reading a = {9, 14.0}; y llama a cool(a);. ¿Cuál es después el valor de a.temp_c en la función llamadora?
  1. 14.0
  2. 12.0
  3. 16.0
  4. Un valor indeterminado
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Qué texto muestra printf("%d", WED); para enum day { MON, TUE, WED, THU };?
  1. «WED»
  2. «3»
  3. «Wednesday»
  4. «2»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Una unión etiquetada almacena un double bajo la etiqueta K_DOUBLE. Más adelante, el código cambia solo la etiqueta a K_INT y una función de impresión lee as.i. ¿Cuál es el defecto?
  1. Cambiar la etiqueta convierte el valor almacenado en un entero.
  2. La nueva etiqueta selecciona un miembro que no se almacenó.
  3. La unión conserva ambos valores tipados hasta que se libera.
  4. La función de impresión debe mantener la etiqueta antigua y cambiar su formato.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una lista simplemente enlazada almacena punteros al inicio y al final y un contador. Se elimina y libera su único nodo. ¿Qué estado debe quedar?
  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;
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

¿Qué resultado produce la expresión posfija «15 6 - 3 /» con operandos enteros?
  1. -3
  2. 7
  3. -7
  4. 3
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una cola circular basada en un contador tiene capacidad 5, head = 4 y count = 2. ¿En qué posición escribirá la siguiente inserción?
  1. Posición 0
  2. Posición 2
  3. Posición 1
  4. Posición 4
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Qué recorrido libera cada subárbol hijo antes de liberar su padre?
  1. Preorden
  2. Postorden
  3. Inorden
  4. Recorrido solo de la raíz
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una tabla con encadenamiento y claves enteras usa el resto de dividir la clave entre el número de cubetas. Inserta 13, 25 y 23 al inicio de sus cadenas en 6 cubetas vacías. Después se amplía a 10 cubetas, recorriendo las antiguas por índice creciente y cada cadena desde su inicio, e insertando cada entrada al inicio de su nueva cubeta. ¿Qué cadenas resultan?
  1. Cubeta 3: 13 → 23; cubeta 5: 25
  2. Cubeta 1: 25 → 13; cubeta 5: 23
  3. Cubeta 3: 23 → 13; cubeta 5: 25
  4. Cubeta 3: 23; cubeta 5: 25 → 13
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un contador de palabras con encadenamiento, inicialmente vacío, usa una función de dispersión que envía «ash» y «elm» a la misma cubeta. Lee en un único búfer reutilizado, busca ese texto y, si encuentra una coincidencia, incrementa su contador; si no, reserva un nodo con contador 1. Los nodos nuevos almacenan incorrectamente key = buffer. Lee «ash» y después «elm». ¿Qué queda antes de liberar los recursos?
  1. Una entrada que muestra «elm» con contador 2
  2. Dos entradas, «ash» y «elm», cada una con contador 1
  3. Una entrada que muestra «ash» con contador 2
  4. Dos entradas que muestran «elm», cada una con contador 1
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Seguir enlaces, vueltas al principio y colisiones10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Una función de comparación de temperaturas finitas devuelve x->temp_c - y->temp_c como int. Dos registros distintos tienen temperaturas de 6,20 y 6,45 °C. ¿Por qué la ordenación puede tratarlos como iguales?
  1. Los argumentos const void * convierten ambas entradas en enteros.
  2. La función de comparación debe devolver exactamente su diferencia de temperatura.
  3. Los dos valores finitos son iguales después de que qsort los copia.
  4. La diferencia fraccionaria se convierte en cero durante la conversión.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué rango cabe en unsigned int mode : 4?
  1. De 0 a 4
  2. De 0 a 15
  3. De 1 a 16
  4. De −8 a 7
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Se inserta al inicio de una lista no vacía con head = n; n->next = head; después de reservar n. ¿A qué apunta el next del nuevo nodo?
  1. Al antiguo primer nodo
  2. Al antiguo último nodo
  3. Al propio nodo nuevo
  4. Al marcador nulo del final
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Una lista ascendente empieza como 3 → 8 → 12. Se inserta 6, se elimina el primer nodo y después se inserta 8 mediante un bucle que avanza mientras el valor actual sea estrictamente menor que el valor insertado. ¿Dónde se enlaza el nuevo 8?
  1. Después de 6 y antes del 8 existente
  2. Después del 8 existente y antes de 12
  3. Antes de 6 y después del 3 eliminado
  4. Después de 12 y antes del enlace nulo
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un programa lee repetidamente registros por índice numérico y recorre todos los registros, con pocas inserciones. ¿Qué almacenamiento se adapta mejor a estas operaciones?
  1. Una lista simplemente enlazada de registros
  2. Una lista doblemente enlazada de registros
  3. Una cola enlazada de nodos de registros
  4. Un array contiguo de registros
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una cola enlazada vacía encola correctamente 12 y 30, desencola una vez, sufre un fallo de reserva al encolar 41, desencola de nuevo y después encola correctamente 44. Las operaciones correctas conservan el estado si fallan. ¿Cuáles son los extremos y los valores en espera al final?
  1. El inicio apunta a 30, el final a 44; valores 30 → 44.
  2. El inicio apunta a 44 mientras que el final es nulo.
  3. Ambos extremos apuntan al único nodo, 44.
  4. El inicio apunta a 12, el final a 44; valores 12 → 44.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un árbol tiene raíz 36, hijo izquierdo 17 y 41 como hijo derecho de 17. ¿Por qué incumple la regla de BST?
  1. 17 es menor que su padre y está a la izquierda.
  2. 41 está en el subárbol izquierdo de 36, pero supera 36.
  3. 41 es mayor que su padre y está a la derecha.
  4. 36 tiene solo un hijo directo en vez de dos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Se construye un BST a partir de 48, 24, 75, 62, 68, 90. Se elimina 48 usando su sucesor en inorden. ¿Cuáles son la nueva raíz y el hijo izquierdo restante de 75?
  1. Raíz 62; hijo izquierdo 68
  2. Raíz 62; hijo izquierdo NULL
  3. Raíz 75; hijo izquierdo 62
  4. Raíz 68; hijo izquierdo 62
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una tabla con encadenamiento tiene 21 claves almacenadas y 14 cubetas. ¿Cuál es su factor de carga?
  1. 1.5
  2. 0.67
  3. 7
  4. 35
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una búsqueda en una tabla de dispersión llega a una cadena que contiene «elm» y «ash» mientras busca «oak». Ambas comparaciones de texto fallan. ¿Qué debe devolver después de que termine la cadena?
  1. La primera entrada de «elm»
  2. La última entrada de «ash»
  3. NULL
  4. Una entrada nueva para «oak»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Diagnosticar errores de copia y de límites10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

En una plataforma con char de 1 byte, int de 4 bytes alineado a 4 bytes y tamaños de estructura redondeados a un múltiplo de 4, A tiene los miembros char, int, char en ese orden; B tiene int, char, char. ¿Cuánto almacenamiento total usa un array de cinco registros A frente a cinco registros B?
  1. A usa 60 bytes; B usa 40 bytes.
  2. A usa 30 bytes; B usa 30 bytes.
  3. A usa 45 bytes; B usa 30 bytes.
  4. A usa 60 bytes; B usa 30 bytes.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un parámetro de función es const reading *p. ¿Qué asignación puede modificar el puntero local sin modificar un registro?
  1. p->id = 19;
  2. (*p).temp_c = 8.0;
  3. p = another_reading_pointer;
  4. p->temp_c += 0.5;
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

a.name es propietario de una cadena en el montículo. Después de person b = a;, el código reserva correctamente una cadena temporal copy, pero no la ha rellenado ni ha modificado ninguno de los registros. Ambos registros deben terminar como propietarios independientes. ¿Qué secuencia lo consigue sin perder datos ni un bloque reservado?
  1. Establecer a.name = copy; copiar b.name en a.name; liberar a.name dos veces.
  2. Copiar a.name en copy; establecer b.name = copy; liberar cada nombre una vez.
  3. Liberar a.name; copiar b.name en copy; establecer b.name = copy.
  4. Copiar a.name en copy; dejar b.name compartido; liberar ambos nombres.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

La entrada está destinada a enum mode { M_IDLE = 0, M_READ = 4, M_WRITE = 5 };. ¿Qué prueba acepta exactamente sus valores con nombre?
  1. x >= 0 && x <= 5
  2. x >= 4 && x <= 5
  3. x == 0 || x == 4 || x == 6
  4. x == 0 || x == 4 || x == 5
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Durante la liberación de una lista, p aún existe y el código ejecuta free(p); p = p->next;. ¿Qué cambio conserva el recorrido previsto de forma segura?
  1. Asignar primero p = p->next y después liberar el nuevo p.
  2. Guardar primero next, liberar p y después asignar p = next.
  3. Liberar primero p->next y después asignar p = p->next.
  4. Establecer primero p->next = NULL y continuar después de liberar p.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una lista ordenada 2 → 6 → 6 → 14 usa un bucle que avanza mientras number < value. Se inserta un nuevo 6. ¿Dónde se coloca?
  1. Entre 2 y el primer 6 existente
  2. Entre los dos nodos 6 existentes
  3. Entre el segundo 6 y 14
  4. Después de 14, al final de la lista
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una pila basada en un array tiene un top = 0 con signo y la extracción lee items[--top] sin comprobarlo. ¿Qué índice intenta leer?
  1. 0
  2. 1
  3. -1
  4. El último índice válido del array
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una cola circular de capacidad 6 usa un contador. Empieza con head = 5, count = 3, con los valores P, Q, R en orden de cola. Desencola dos veces y después encola S y T. ¿Cuáles son el inicio, el contador y el valor inicial al final?
  1. Inicio 0; contador 3; primer valor Q
  2. Inicio 1; contador 5; primer valor R
  3. Inicio 3; contador 3; primer valor T
  4. Inicio 1; contador 3; primer valor R
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un BST contiene una raíz 31, un hijo izquierdo 12 con hijo derecho 19 y un hijo derecho 54. ¿Qué salida en preorden queda después de eliminar 12?
  1. 19, 31, 54
  2. 31, 54, 19
  3. 31, 19, 54
  4. 31, 12, 19, 54
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Se reserva una entrada nueva para una tabla, pero falla la reserva de su cadena de clave antes de que se enlace. ¿Qué liberación de recursos conserva la tabla y evita una fuga?
  1. Liberar la clave de la función llamadora y devolver éxito.
  2. Liberar la entrada nueva y devolver un fallo.
  3. Enlazarla con una clave nula y devolver éxito.
  4. Devolver un fallo pero conservar la nueva entrada.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 5: Combinar reglas en casos poco habituales10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un bucle intenta eliminar todos los 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;
}

¿Por qué queda un 5 y qué cambio corrige el bucle?

  1. Omite un nodo liberado; libera el sucesor antes de avanzar.
  2. Omite una coincidencia conservada; avanza solo si no coincide.
  3. Pierde el enlace al inicio; reinicia el bucle tras cada falta de coincidencia.
  4. Elimina el nodo incorrecto; libera antes de leer el sucesor.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un array en el montículo tiene tres registros utilizados, cada uno propietario de una cadena de nombre distinta, y un puntero guardado al registro 1. Un cambio de tamaño a capacidad 6 tiene éxito. El programa planea eliminar el registro 0 desplazando los otros dos registros a la izquierda y reduciendo a 2 el número de elementos utilizados. ¿Qué plan conserva el acceso al registro 1 original y libera todo el almacenamiento de su propiedad?
  1. Liberar el nombre eliminado; desplazar; reconstruir el puntero en el índice 0; después liberar los nombres activos y el array.
  2. Liberar el nombre eliminado; desplazar; conservar el puntero antiguo; después liberar las seis posiciones de capacidad y el array.
  3. Desplazar; reconstruir el puntero en el índice 1; después liberar solo los dos nombres activos y el array.
  4. Desplazar; reconstruir el puntero en el índice 0; después liberar los nombres de las tres posiciones antiguas y el array.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

A un campo sin signo de 3 bits se le asigna el valor 13 en tiempo de ejecución. ¿Qué valor se almacena?
  1. 13
  2. 7
  3. 1
  4. 5
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Una lista es 4 → 9 → 16. El código guarda node **link = &head->next;, elimina y libera el primer nodo 4 y después usa *link para eliminar 9. ¿Por qué es inválida esa segunda operación y qué enlace entrante debería usar ahora?
  1. link apunta al nodo 9, que existe; usa la dirección del campo next de 9.
  2. link apunta al interior del nodo 16, que existe; usa la dirección del campo next de 16.
  3. link apunta al interior del nodo 4 liberado; usa la dirección del puntero al inicio actual.
  4. link es la dirección del puntero al inicio actual; usa la dirección del antiguo nodo 4.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una lista doblemente enlazada es A ↔ B ↔ C, con A al inicio y C al final. Se elimina B, se añade D al final y después se elimina C, manteniendo ambas direcciones. ¿Cuál es el nodo final y cuál es su predecesor?
  1. Nodo final D; su predecesor A
  2. Nodo final C; su predecesor A
  3. Nodo final D; su predecesor B
  4. Nodo final A; su predecesor D
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un evaluador de expresiones posfijas realiza correctamente la división, pero invierte los dos operandos de cada resta. Para «20 8 - 3 / 2 -», ¿cuáles son el resultado correcto y el resultado del evaluador defectuoso? Todas las divisiones aquí son divisiones enteras exactas.
  1. Resultado correcto 6; resultado defectuoso 2
  2. Resultado correcto 2; resultado defectuoso −6
  3. Resultado correcto 2; resultado defectuoso 6
  4. Resultado correcto −2; resultado defectuoso 6
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una cola vacía encola 22, después 7 y después 15. ¿Qué devuelve su primera extracción?
  1. 15
  2. 7
  3. Un fallo por cola vacía
  4. 22
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Inserta las claves distintas 9, 16, 23, 30 en un BST vacío en ese orden. Si la altura se mide en aristas, ¿cuál es su altura?
  1. 4
  2. 3
  3. 2
  4. 1
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un BST tiene inicialmente solo la raíz 40. En una interfaz que devuelve la raíz, el código llama a insert(root->left, 20) e ignora el nodo reservado que se devuelve; después guarda root = insert(root, 60) y finalmente root = erase(root, 40). Una inserción que tiene éxito enlaza internamente los resultados de los hijos; erase vuelve a conectar los hijos conservados. ¿Qué queda?
  1. Raíz 60 con hijo izquierdo 20; no hay fugas de memoria.
  2. Raíz 20 con hijo derecho 60; no hay fugas de memoria.
  3. Raíz 40 con hijo derecho 60; el nodo 20 se pierde en una fuga.
  4. Solo la raíz 60; el nodo reservado 20 se pierde en una fuga.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una tabla de direccionamiento abierto de tamaño 7 usa sondeo lineal. Tres claves asignadas a la posición 5 ocupan las posiciones 5, 6 y 0. Se elimina la clave de la posición 6. ¿Por qué debe conservar la posición 6 una marca de borrado en vez de quedar como posición sin usar?
  1. Una búsqueda de la clave de la posición 0 debe continuar más allá de la posición 6.
  2. Una búsqueda de la clave de la posición 5 debe mover su clave a la posición 6.
  3. Una búsqueda debe tratar la clave eliminada como si aún estuviera presente.
  4. Una búsqueda debe reiniciar siempre su sondeo desde la posición 0.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

72 Términos Clave, cada uno con su definición

A

Alineación
La alineación es un requisito sobre la posición de la dirección de un objeto según su tipo que puede causar espacios entre miembros o al final de una estructura.
Un miembro entero puede empezar después de un espacio que sigue a un miembro de carácter para que su dirección esté adecuadamente alineada.
Almacenamiento contiguo
El almacenamiento contiguo coloca los elementos de un array uno junto a otro en una secuencia de memoria, lo que permite el acceso directo por índice y los recorridos por ubicaciones próximas.
Un array de enteros evita los punteros de enlace por nodo y el coste de las reservas separadas de una lista de enteros.
Altura
La altura de un árbol no vacío es el número de aristas de su camino más largo desde la raíz hasta una hoja; la altura del árbol vacío utilizada aquí es −1.
Una cadena que contiene cinco nodos tiene cuatro aristas en su camino más largo, por lo que su altura es 4.
Array de estructuras
Un array de estructuras almacena de forma contigua registros de un mismo tipo de estructura, lo que permite seleccionar directamente un registro por su índice numérico.
La expresión readings[3].temp_c accede a la temperatura del cuarto registro del array.

Á

Árbol binario
Un árbol binario es una estructura enlazada en la que cada nodo tiene como máximo dos hijos, distinguidos como su hijo izquierdo y su hijo derecho.
Un nodo puede no tener hijos, tener solo uno o tener tanto un hijo izquierdo como uno derecho.
Árbol binario de búsqueda
Un árbol binario de búsqueda ordena claves distintas de modo que cada nodo tiene claves menores en todo su subárbol izquierdo y claves mayores en todo su subárbol derecho.
Comparar la clave buscada con la clave actual selecciona el único subárbol que puede contener una coincidencia.

B

Búfer circular
Un búfer circular reutiliza un array fijo haciendo que sus posiciones vuelvan de la última a la primera en vez de desplazar los elementos restantes.
Después de una extracción del inicio, una inserción posterior puede reutilizar la posición liberada cuando su índice vuelve al principio.

C

Campo de bits
Un campo de bits es un miembro de una estructura declarado con un ancho en bits, lo que limita los valores enteros que almacena mientras su distribución queda definida por la implementación.
Un campo de modo sin signo de 3 bits almacena valores de 0 a 7 y no permite obtener su dirección.
Capacidad
La capacidad es el número de elementos que puede contener un array reservado, que puede ser mayor que el número de elementos utilizados actualmente.
Un array con capacidad 16 y 10 registros utilizados puede aceptar seis registros más antes de otra ampliación.
Caso base
Un caso base gestiona la entrada más pequeña de una función recursiva sin realizar otra llamada recursiva, deteniendo el descenso hacia subproblemas más pequeños.
Una función de liberación de árboles retorna de inmediato ante un subárbol nulo antes de intentar leer cualquier puntero a hijo.
Cola
Una cola es una colección que añade al final y elimina del inicio, conservando el orden de llegada entre los valores que aún esperan.
Una cola enlazada añade un nodo nuevo después de su nodo final y elimina el nodo al que apunta actualmente su puntero al inicio.
Colisión
Una colisión ocurre cuando claves distintas reciben el mismo índice de cubeta, lo que exige que la tabla conserve y distinga sus entradas separadas.
Una búsqueda debe comparar las claves dentro de una cubeta compartida en vez de suponer que su primera entrada coincide.
Comportamiento definido por la implementación
El comportamiento definido por la implementación es una elección que la implementación de C debe documentar, por lo que el código portable no puede suponer que todas las implementaciones eligen lo mismo.
El orden documentado para distribuir campos de bits puede diferir entre implementaciones de la misma declaración de C.
Continuación al siguiente caso
La continuación al siguiente caso es la ejecución que pasa de un caso de switch al siguiente porque no hay un break, return u otra transferencia de control que la detenga.
Un caso que muestra un estado pero omite break también puede ejecutar la acción escrita para el caso siguiente.
Copia profunda
Una copia profunda crea copias independientes de los datos apuntados de su propiedad además del registro exterior, con responsabilidades de liberación para los nuevos bloques reservados.
Copiar un nombre en un bloque recién reservado permite que cada registro de persona libere su propio nombre sin una doble liberación.
Copia superficial
Una copia superficial duplica los valores de los miembros de un registro, incluidas las direcciones de los punteros, mientras que los datos apuntados y reservados por separado siguen compartidos.
Después de copiar un registro de persona, ambos punteros a nombres siguen accediendo a la misma cadena salvo que se reserve una nueva copia.
Cubeta
Una cubeta es una ubicación seleccionada por un índice de tabla de dispersión; en el encadenamiento separado almacena el puntero al inicio de una lista de entradas asignadas a ella.
Una tabla de diez cubetas que usa sumas de caracteres ASCII coloca «dog» en la cubeta 4 porque su suma es 314.

D

Datos de la variante
Los datos de la variante son los datos almacenados que corresponden al tipo de un valor etiquetado y se mantienen separados de la etiqueta que describe cómo interpretarlos.
En un token de calculadora, los datos de la variante contienen el operando numérico o el operador identificado por su etiqueta.
Desencolar
Desencolar consiste en eliminar el elemento del inicio de una cola e informar de que está vacía antes de cambiar el estado o leer un valor.
Una extracción circular lee el inicio, avanza el puntero al inicio volviendo al principio cuando corresponde y reduce el contador.
Desenlace de un nodo
El desenlace de un nodo lo retira de una estructura reconectando las referencias a su alrededor antes de liberar el almacenamiento del nodo.
Redirigir el next de un predecesor al sucesor del nodo eliminado mantiene accesible la cadena restante.
Direccionamiento abierto
El direccionamiento abierto almacena entradas directamente en posiciones de un array y resuelve las colisiones buscando en una secuencia definida de posiciones alternativas.
Una búsqueda puede examinar varias posiciones aunque empiece en un índice de dispersión derivado de una clave.

E

Encadenamiento separado
El encadenamiento separado gestiona las colisiones de dispersión manteniendo una lista enlazada de entradas en cada cubeta y comparando las claves dentro de la lista elegida.
Las nuevas entradas de palabras pueden insertarse al inicio porque su orden dentro de una cubeta no afecta a la búsqueda exacta.
Encolar
Encolar consiste en añadir un elemento al final de una cola conservando la posición inicial de los valores que ya esperan.
Un anillo basado en un contador escribe el nuevo elemento en (head + count) % CAP y después incrementa su contador.
Enlace entrante
Un enlace entrante es el puntero que llega a un nodo, ya sea el puntero al inicio de la lista o el campo next de un predecesor, y debe cambiar cuando se excluye ese nodo de la cadena.
Eliminar el primer nodo cambia el puntero al inicio porque ningún nodo predecesor proporciona su enlace entrante.
Entrada de la tabla
Una entrada de la tabla es un registro almacenado en una tabla de dispersión que contiene una clave y datos asociados, además de un puntero next cuando el encadenamiento separado conecta las entradas.
Una entrada de recuento de palabras es propietaria de una cadena de clave, almacena el recuento de la palabra y enlaza con la siguiente entrada de su cubeta.
Enumeración
Una enumeración define constantes enteras con nombre y un tipo para los valores asociados a esas opciones, sin exigir que los valores pertenezcan al conjunto de constantes con nombre.
Una enumeración de alertas puede dar nombre a los niveles bajo, medio, alto y crítico, aunque la entrada siga necesitando validación.
Enumerador
Un enumerador es una constante entera con nombre en una definición de enumeración, con un valor explícito o determinado a partir de su predecesor.
En enum state { IDLE = 3, RUNNING };, el enumerador RUNNING recibe el valor 4.
Estructura
Una estructura es un tipo de C que agrupa miembros con nombre, que pueden tener distintos tipos, en un solo registro con almacenamiento separado para cada miembro.
Un registro de temperatura combina un identificador entero de sensor y una temperatura en un objeto de tipo estructura.
Etiqueta de estructura
Una etiqueta de estructura da nombre a un tipo de estructura después de la palabra clave struct y puede identificar el tipo dentro de su propia definición.
La etiqueta de struct node permite que la definición declare un enlace a sí misma struct node *next.
Expresión posfija
Una expresión posfija escribe cada operador después de sus operandos, lo que permite que una pila almacene valores intermedios hasta que el operador los combine.
Para «9 4 -», el primer valor extraído es el operando derecho 4 y el resultado es 5.
Extracción de una colección vacía (underflow)
La extracción de una colección vacía ocurre cuando un programa intenta eliminar un elemento de una colección vacía y requiere un resultado de fallo antes de un acceso inválido.
Una extracción de una pila basada en un array debe comprobar que el contador de elementos no sea cero antes de reducirlo y leer el array.

F

Factor de carga
El factor de carga es el número de claves almacenadas dividido entre el número de cubetas; mide la ocupación media sin determinar la longitud de cada cadena.
Doce claves en ocho cubetas dan un factor de carga de 1,5, incluso si algunas cubetas permanecen vacías.
Función de comparación
Una función de comparación indica el orden relativo de dos valores mediante un resultado negativo, cero si son iguales o un resultado positivo.
Una función de comparación de temperaturas devuelve un signo después de comparar los valores, evitando truncar una pequeña diferencia.
Función de dispersión
Una función de dispersión convierte de forma determinista una clave en un número que se reduce a un índice de cubeta, con el objetivo de distribuir uniformemente las claves esperadas.
Una función de dispersión basada en la suma de caracteres envía reorganizaciones como «cat» y «act» a la misma cubeta.

H

Hoja
Una hoja es un nodo de árbol sin hijos, por lo que ambos punteros a hijos son nulos y su altura es cero cuando la altura cuenta aristas.
La eliminación de una hoja libera su bloque de memoria y sustituye su enlace entrante por un puntero nulo.

I

Igualdad de cadenas
La igualdad de cadenas significa que dos cadenas terminadas en nulo contienen secuencias de caracteres coincidentes, independientemente de que sus direcciones de almacenamiento sean diferentes.
Una búsqueda en una tabla de palabras usa strcmp(e->key, key) == 0 para reconocer texto igual en bloques reservados por separado.
Inicializador designado
Un inicializador designado nombra el miembro o elemento que recibe un valor, lo que permite inicializar miembros de una estructura sin seguir el orden de declaración.
Para un struct declarado con id antes de temp_c, el inicializador {.temp_c = 21.5, .id = 7} nombra sus miembros en orden inverso al de declaración.
Inserción ordenada
La inserción ordenada coloca un nuevo valor donde conserva el orden existente, conectando el nuevo nodo entre su predecesor y su sucesor.
Un bucle sobre una lista ascendente se detiene antes del primer valor que no sea menor que el nuevo.
Insertar al inicio
Insertar al inicio consiste en insertar un elemento antes del primer elemento actual de una secuencia, cambiando el elemento al que se llega desde el puntero al inicio.
Insertar al inicio 1, después 2 y después 3 crea una lista cuyo recorrido visita 3 antes que 2 y después 1.

L

Lista doblemente enlazada
Una lista doblemente enlazada almacena en cada nodo enlaces al sucesor y al predecesor, lo que permite recorrerla hacia atrás y actualizar los enlaces mediante cualquiera de sus vecinos.
Eliminar un nodo interior actualiza el enlace next del nodo anterior y el enlace prev del nodo siguiente.
Lista simplemente enlazada
Una lista simplemente enlazada es una secuencia de nodos conectados por punteros next, con un puntero al inicio y un enlace nulo al final.
Insertar un nodo al inicio le asigna el inicio anterior como sucesor y convierte el nuevo nodo en el inicio.

M

Marca de borrado (tombstone)
Una marca de borrado señala una posición eliminada de direccionamiento abierto sin hacer que parezca que nunca se usó, lo que permite que la búsqueda continúe hacia entradas posteriores que colisionaron.
Una búsqueda pasa por una marca de borrado en vez de detenerse antes de una clave coincidente almacenada más adelante en la ruta de sondeo.
Miembro
Un miembro es un componente con nombre de una estructura o unión, seleccionado de un objeto con un punto o mediante un puntero con una flecha.
La expresión r.temp_c selecciona el miembro de temperatura de la lectura almacenada en r.

N

Nodo
Un nodo es un registro que contiene datos y los punteros que lo conectan con otras partes de una estructura enlazada.
Un nodo de lista contiene un entero y su puntero al sucesor; un nodo de árbol, en cambio, tiene dos punteros a hijos.

O

Operador flecha
El operador flecha selecciona un miembro de una estructura o unión mediante un puntero y combina la desreferenciación con la selección de miembros.
Para un puntero p a un registro que existe, p->temp_c accede al mismo miembro que (*p).temp_c.

P

Paso por valor
El paso por valor significa que un parámetro de función recibe una copia del valor proporcionado, por lo que modificar ese parámetro no sustituye el valor de la función llamadora.
Un cambio de temperatura en un parámetro de registro pasado por valor deja sin cambios el miembro de temperatura de la función llamadora.
Pila
Una pila es una colección en la que la inserción y la eliminación usan la cima, de modo que el valor insertado más recientemente es el siguiente que se elimina.
Un comprobador de delimitadores almacena en la cima de su pila el delimitador de apertura sin pareja más reciente.
Primero en entrar, primero en salir (FIFO)
FIFO significa primero en entrar, primero en salir y describe el orden en el que el elemento almacenado más antiguo se procesa antes que los elementos posteriores que aún esperan.
Después de encolar A, B y C, una extracción FIFO devuelve A antes que cualquiera de las letras posteriores.
Propiedad de la memoria
La propiedad de la memoria asigna la responsabilidad de liberar un bloque reservado, incluso cuando varios punteros pueden acceder al mismo objeto durante su tiempo de vida.
Un nodo de palabra es propietario de su cadena, por lo que la liberación de recursos libera esa cadena antes que el nodo que contiene su dirección.
Puntero a datos constantes
Un puntero a datos constantes permite acceder a un objeto que no puede modificarse mediante ese puntero, mientras que el propio puntero aún puede cambiar.
Una función de impresión puede leer mediante const reading *p, pero no puede asignar un valor a p->id.
Puntero a puntero
Un puntero a puntero contiene la dirección de un objeto de tipo puntero, lo que permite a una función leer o sustituir ese puntero mediante el nivel adicional.
Un node **link puede pasar de la dirección del puntero al inicio a la dirección del campo next de un nodo.
Puntero al final de la lista
El puntero al final de la lista es el puntero que designa el último nodo de una secuencia enlazada y se mantiene para que la inserción al final pueda evitar un recorrido completo.
Una cola enlazada establece tanto el puntero al inicio como el puntero al final a nulo cuando se elimina y libera su único nodo.
Puntero al inicio de la lista
El puntero al inicio de la lista es el puntero que designa el primer nodo de una secuencia enlazada; si es nulo, representa una secuencia que no contiene nodos.
Eliminar el primer nodo de una lista exige actualizar el puntero al inicio del propietario antes de liberar el bloque del nodo anterior.
Puntero colgante
Un puntero colgante se refiere a un objeto cuyo tiempo de vida ha terminado, por lo que desreferenciarlo ya no proporciona acceso válido a ese objeto.
Devolver la dirección de un registro local deja a la función llamadora con un puntero a un objeto que ha dejado de existir.

R

Raíz
La raíz es el nodo donde empieza un árbol o subárbol, al que se llega mediante el puntero a la raíz del propietario o el enlace a hijo de un padre.
Sustituir una raíz eliminada que tiene un hijo requiere guardar el hijo que sobrevive como nueva raíz del árbol.
Reasignación de memoria
La reasignación de memoria cambia el tamaño de un bloque reservado y puede moverlo, por lo que su propietario debe usar la dirección base devuelta cuando la operación tiene éxito.
Un puntero temporal para el resultado conserva los registros anteriores cuando falla un cambio de tamaño de un array a un tamaño distinto de cero.
Recorrido
Un recorrido visita nodos siguiendo los enlaces de una estructura en un orden definido y se detiene en el final o el caso de subárbol vacío correspondiente.
Un puntero independiente de recorrido de lista avanza por los enlaces next mientras el propietario conserva el puntero al inicio original.
Recorrido en inorden
Un recorrido en inorden visita cada subárbol izquierdo antes que su raíz y después su subárbol derecho, lo que produce claves ordenadas en un árbol binario de búsqueda.
Mostrar en inorden un BST de recuento de palabras produce sus palabras almacenadas en el orden utilizado por sus comparaciones de texto.
Recorrido en postorden
Un recorrido en postorden visita los subárboles izquierdo y derecho antes que su raíz, lo que permite liberar los hijos antes que su padre.
Una función de liberación de árboles lee los enlaces a hijos mientras el padre existe y después libera ese padre al final.
Recorrido en preorden
Un recorrido en preorden visita la raíz de un subárbol antes de visitar sus subárboles izquierdo y derecho, colocando cada padre antes que sus descendientes.
Una salida en preorden empieza con la clave de la raíz incluso cuando esa clave no es ni la menor ni la mayor.
Redispersión (rehashing)
La redispersión vuelve a calcular las ubicaciones de las claves almacenadas para un nuevo número de cubetas y mueve o vuelve a enlazar las entradas para que las búsquedas posteriores usen las ubicaciones correctas.
Duplicar el array de cubetas requiere reducir el valor de dispersión de cada clave con el nuevo número antes de colocarla.
Relleno
El relleno consiste en bytes sin usar que una implementación puede colocar entre los miembros de una estructura o después del último miembro para cumplir los requisitos de distribución.
Una comparación de bytes puede detectar distinto relleno incluso cuando dos registros contienen valores de miembros iguales.

S

Sentencia switch
Una sentencia switch selecciona un caso inicial a partir de una expresión entera y continúa hasta que el control sale de la sentencia o de la función.
Una sentencia switch sobre un valor etiquetado elige la operación de salida según el tipo de datos de la variante almacenados en ese momento.
Sondeo lineal
El sondeo lineal resuelve una colisión probando posiciones consecutivas del array y volviendo al principio, usando la misma secuencia para insertar y buscar.
Un sondeo que empieza en la última posición continúa en la posición cero cuando debe comprobarse otra posición ocupada.
Subárbol
Un subárbol es un nodo junto con todos sus descendientes, que forman un árbol más pequeño sobre el que pueden actuar las mismas reglas de búsqueda y actualización.
La restricción del subárbol izquierdo de un BST se aplica tanto a los descendientes lejanos como al hijo izquierdo directo.
Sucesor en inorden
El sucesor en inorden de un nodo con subárbol derecho es la clave menor de ese subárbol, utilizada para sustituir la clave de un nodo con dos hijos durante la eliminación.
Sigue el hijo derecho y después los enlaces izquierdos para encontrar la clave de sustitución al eliminar un nodo con dos hijos.

T

Tamaño del árbol
El tamaño del árbol es su número de nodos, que se obtiene sumando uno por su raíz a los tamaños de ambos subárboles hijos, con cero para un árbol vacío.
Una raíz con dos hijos hoja tiene tamaño 3 aunque su altura medida en aristas sea solo 1.
Tipo abstracto de datos
Un tipo abstracto de datos describe una colección mediante sus operaciones y comportamientos permitidos, independientemente del almacenamiento elegido para implementarla.
Una pila mantiene el mismo comportamiento de inserción y extracción tanto si sus valores están en un array como en nodos enlazados.
typedef
Una declaración typedef introduce un alias para un tipo existente, lo que permite al código usar un nombre más corto sin crear un tipo distinto.
Después de typedef struct reading reading;, una declaración puede usar reading *p para un puntero a esa estructura.

U

Unión
Una unión es un tipo de C cuyos miembros comparten almacenamiento, lo que permite que ese espacio contenga una alternativa almacenada elegida entre distintos tipos de miembros.
En una unión con un miembro entero y un miembro double, escribir en el double sustituye los datos enteros anteriores en vez de conservar dos valores.
Unión etiquetada
Una unión etiquetada combina almacenamiento compartido para los datos de la variante con una etiqueta separada que identifica a qué miembro de la unión debe acceder el código que lee.
Un valor con la etiqueta K_CHAR se muestra usando el miembro de carácter y una conversión de formato de carácter.

Ú

Último en entrar, primero en salir (LIFO)
LIFO significa último en entrar, primero en salir y describe el orden en el que el elemento almacenado más reciente se procesa antes que cualquier elemento anterior que aún esté presente.
Después de que una pila reciba 4, 7 y 1, sus dos primeras extracciones devuelven 1 y 7, y dejan 4.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar