Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Ejercicios: Tipos de Datos Abstractos

Universidad Nacional de Río Negro

Ejercicios de Tipos de Datos Abstractos

Prerrequisitos y Entorno de Ejecución Requerido

Para compilar y verificar las soluciones de este módulo bajo el estándar C11 estricto de cátedra, se requiere:

Objetivos Pedagógicos y Competencias (Taxonomía de Bloom)

Capítulos de Apunte Correspondientes

Cuestiones de Estilo Aplicables


Lista Enlazada Simple - Operaciones Básicas

Ejercicio 3.02.01 - Inserción al Inicio ⭐⭐☆☆☆

```c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <assert.h> typedef struct nodo { int dato; struct nodo *siguiente; } nodo_t; struct lista { nodo_t *primero; size_t longitud; }; typedef struct lista lista_t; lista_t *lista_crear(void) { lista_t *l = (lista_t *)malloc(sizeof(lista_t)); if (l == NULL) { return NULL; } l->primero = NULL; l->longitud = 0; return l; } void lista_destruir(lista_t *lista) { if (lista == NULL) { return; } nodo_t *act = lista->primero; while (act != NULL) { nodo_t *sig = act->siguiente; free(act); act = sig; } free(lista); } bool lista_insertar_inicio(lista_t *lista, int dato) { if (lista == NULL) { return false; } nodo_t *nuevo = (nodo_t *)malloc(sizeof(nodo_t)); if (nuevo == NULL) { return false; } nuevo->dato = dato; nuevo->siguiente = lista->primero; lista->primero = nuevo; lista->longitud++; return true; } int main(void) { assert(!lista_insertar_inicio(NULL, 10)); lista_t *l = lista_crear(); assert(l != NULL); assert(l->longitud == 0); assert(lista_insertar_inicio(l, 42)); assert(l->longitud == 1); assert(l->primero->dato == 42); assert(lista_insertar_inicio(l, 99)); assert(l->longitud == 2); assert(l->primero->dato == 99); assert(l->primero->siguiente->dato == 42); lista_destruir(l); return 0; } ```

(ej_b3_c02_02)=
### Ejercicio 3.02.02 - Inserción al Final ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_02_insercion_final

Implementá la operación de insertar un elemento al final de la lista.

```c
bool lista_insertar_final(lista_t *lista, int dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista Inicial | Elemento | Retorno | Lista Resultante |
| :--- | :--- | :--- | :--- | :--- |
| Inserción en vacía | `[]` | `10` | `true` | `[10]` |
| Inserción al final existente | `[10]` | `20` | `true` | `[10, 20]` |
| Inserción múltiple | `[10, 20]` | `30` | `true` | `[10, 20, 30]` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    nodo_t *ultimo;
    size_t longitud;
};
typedef struct lista lista_t;

lista_t *lista_crear(void) {
    lista_t *l = (lista_t *)malloc(sizeof(lista_t));
    if (l != NULL) {
        l->primero = NULL;
        l->ultimo = NULL;
        l->longitud = 0;
    }
    return l;
}

void lista_destruir(lista_t *l) {
    if (l == NULL) return;
    nodo_t *act = l->primero;
    while (act != NULL) {
        nodo_t *sig = act->siguiente;
        free(act);
        act = sig;
    }
    free(l);
}

bool lista_insertar_final(lista_t *lista, int dato) {
    if (lista == NULL) {
        return false;
    }
    nodo_t *nuevo = (nodo_t *)malloc(sizeof(nodo_t));
    if (nuevo == NULL) {
        return false;
    }
    nuevo->dato = dato;
    nuevo->siguiente = NULL;

    if (lista->primero == NULL) {
        lista->primero = nuevo;
        lista->ultimo = nuevo;
    } else {
        lista->ultimo->siguiente = nuevo;
        lista->ultimo = nuevo;
    }
    lista->longitud++;
    return true;
}

int main(void) {
    lista_t *l = lista_crear();
    assert(l != NULL);

    assert(lista_insertar_final(l, 10));
    assert(lista_insertar_final(l, 20));
    assert(lista_insertar_final(l, 30));

    assert(l->longitud == 3);
    assert(l->primero->dato == 10);
    assert(l->ultimo->dato == 30);

    lista_destruir(l);
    return 0;
}
```

(ej_b3_c02_03)=
### Ejercicio 3.02.03 - Ver Primero y Último ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_03_ver_extremos

Implementá funciones para consultar el primer y último valor de la lista sin mutarla.

```c
bool ver_primero(const lista_t *lista, int *dato);
bool ver_ultimo(const lista_t *lista, int *dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista | Función | Retorno | Salida (`*dato`) |
| :--- | :--- | :--- | :--- | :--- |
| Lista vacía | `[]` | `ver_primero` | `false` | Inalterado |
| Lista con 1 elemento | `[55]` | `ver_primero` y `ver_ultimo` | `true` | `55` en ambos |
| Lista con 3 elementos | `[10, 20, 30]` | `ver_primero` | `true` | `10` |
| Lista con 3 elementos | `[10, 20, 30]` | `ver_ultimo` | `true` | `30` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    nodo_t *ultimo;
    size_t longitud;
};
typedef struct lista lista_t;

bool ver_primero(const lista_t *lista, int *dato) {
    if (lista == NULL || lista->primero == NULL || dato == NULL) {
        return false;
    }
    *dato = lista->primero->dato;
    return true;
}

bool ver_ultimo(const lista_t *lista, int *dato) {
    if (lista == NULL || lista->ultimo == NULL || dato == NULL) {
        return false;
    }
    *dato = lista->ultimo->dato;
    return true;
}

int main(void) {
    int val = 0;
    assert(!ver_primero(NULL, &val));

    nodo_t n2 = {20, NULL};
    nodo_t n1 = {10, &n2};
    lista_t l = {&n1, &n2, 2};

    assert(ver_primero(&l, &val) && val == 10);
    assert(ver_ultimo(&l, &val) && val == 20);

    lista_t vacia = {NULL, NULL, 0};
    assert(!ver_primero(&vacia, &val));
    assert(!ver_ultimo(&vacia, &val));

    return 0;
}
```

(ej_b3_c02_04)=
### Ejercicio 3.02.04 - Borrar Primero ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_04_borrar_primero

Implementá la remoción del nodo frontal en tiempo $O(1)$ retornando el valor extraído.

```c
bool borrar_primero(lista_t *lista, int *dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista Inicial | Retorno | `*dato` | Lista Final |
| :--- | :--- | :--- | :--- | :--- |
| Lista vacía | `[]` | `false` | Inalterado | `[]` |
| Lista de 1 elemento | `[100]` | `true` | `100` | `[]` |
| Lista de varios elementos | `[1, 2, 3]` | `true` | `1` | `[2, 3]` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

bool borrar_primero(lista_t *lista, int *dato) {
    if (lista == NULL || lista->primero == NULL) {
        return false;
    }
    nodo_t *nodo_a_eliminar = lista->primero;
    if (dato != NULL) {
        *dato = nodo_a_eliminar->dato;
    }
    lista->primero = nodo_a_eliminar->siguiente;
    lista->longitud--;
    free(nodo_a_eliminar);
    return true;
}

int main(void) {
    int extraido = 0;
    assert(!borrar_primero(NULL, &extraido));

    lista_t l = {NULL, 0};
    assert(!borrar_primero(&l, &extraido));

    nodo_t *n1 = (nodo_t *)malloc(sizeof(nodo_t));
    assert(n1 != NULL);
    n1->dato = 100;
    n1->siguiente = NULL;
    l.primero = n1;
    l.longitud = 1;

    assert(borrar_primero(&l, &extraido) && extraido == 100);
    assert(l.primero == NULL && l.longitud == 0);

    return 0;
}
```

---

## Lista Enlazada - Operaciones de Búsqueda

(ej_b3_c02_05)=
### Ejercicio 3.02.05 - Buscar Elemento ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_05_pertenece

Verificá la presencia de un elemento dentro de la lista en tiempo $O(n)$.

```c
bool lista_pertenece(const lista_t *lista, int dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista | Elemento Buscado | Retorno Esperado |
| :--- | :--- | :--- | :--- |
| Elemento intermedio | `[5, 10, 15]` | `10` | `true` |
| Elemento inexistente | `[5, 10, 15]` | `99` | `false` |
| Lista vacía | `[]` | `5` | `false` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

bool lista_pertenece(const lista_t *lista, int dato) {
    if (lista == NULL) {
        return false;
    }
    const nodo_t *actual = lista->primero;
    while (actual != NULL) {
        if (actual->dato == dato) {
            return true;
        }
        actual = actual->siguiente;
    }
    return false;
}

int main(void) {
    assert(!lista_pertenece(NULL, 10));

    nodo_t n3 = {15, NULL};
    nodo_t n2 = {10, &n3};
    nodo_t n1 = {5, &n2};
    lista_t l = {&n1, 3};

    assert(lista_pertenece(&l, 10));
    assert(lista_pertenece(&l, 5));
    assert(lista_pertenece(&l, 15));
    assert(!lista_pertenece(&l, 99));

    lista_t vacia = {NULL, 0};
    assert(!lista_pertenece(&vacia, 5));
    return 0;
}
```

(ej_b3_c02_06)=
### Ejercicio 3.02.06 - Obtener Elemento en Posición ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_06_obtener_pos

Implementá la consulta por índice (0-based) retornando `false` si el índice excede el límite.

```c
bool lista_obtener(const lista_t *lista, size_t posicion, int *dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista | Posición | Retorno | `*dato` |
| :--- | :--- | :--- | :--- | :--- |
| Índice inicial 0 | `[10, 20, 30]` | `0` | `true` | `10` |
| Índice intermedio | `[10, 20, 30]` | `1` | `true` | `20` |
| Índice fuera de rango | `[10, 20, 30]` | `5` | `false` | Inalterado |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

bool lista_obtener(const lista_t *lista, size_t posicion, int *dato) {
    if (lista == NULL || dato == NULL || posicion >= lista->longitud) {
        return false;
    }
    const nodo_t *actual = lista->primero;
    for (size_t i = 0; i < posicion; ++i) {
        if (actual == NULL) {
            return false;
        }
        actual = actual->siguiente;
    }
    if (actual == NULL) {
        return false;
    }
    *dato = actual->dato;
    return true;
}

int main(void) {
    nodo_t n3 = {30, NULL};
    nodo_t n2 = {20, &n3};
    nodo_t n1 = {10, &n2};
    lista_t l = {&n1, 3};

    int valor = 0;
    assert(lista_obtener(&l, 0, &valor) && valor == 10);
    assert(lista_obtener(&l, 1, &valor) && valor == 20);
    assert(lista_obtener(&l, 2, &valor) && valor == 30);
    assert(!lista_obtener(&l, 3, &valor));
    assert(!lista_obtener(&l, 99, &valor));

    return 0;
}
```

(ej_b3_c02_07)=
### Ejercicio 3.02.07 - Contar Ocurrencias ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_07_contar

Contá cuántas veces aparece un entero dado dentro de la lista.

```c
size_t lista_contar(const lista_t *lista, int dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista | Elemento | Ocurrencias Esperadas |
| :--- | :--- | :--- | :--- |
| Elementos repetidos | `[7, 3, 7, 7, 2]` | `7` | `3` |
| Elemento ausente | `[1, 2, 3]` | `9` | `0` |
| Lista vacía | `[]` | `4` | `0` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

size_t lista_contar(const lista_t *lista, int dato) {
    if (lista == NULL) {
        return 0;
    }
    size_t cuenta = 0;
    const nodo_t *actual = lista->primero;
    while (actual != NULL) {
        if (actual->dato == dato) {
            cuenta++;
        }
        actual = actual->siguiente;
    }
    return cuenta;
}

int main(void) {
    assert(lista_contar(NULL, 5) == 0);

    nodo_t n5 = {2, NULL};
    nodo_t n4 = {7, &n5};
    nodo_t n3 = {7, &n4};
    nodo_t n2 = {3, &n3};
    nodo_t n1 = {7, &n2};
    lista_t l = {&n1, 5};

    assert(lista_contar(&l, 7) == 3);
    assert(lista_contar(&l, 3) == 1);
    assert(lista_contar(&l, 99) == 0);

    return 0;
}
```

---

## Lista Enlazada - Operaciones Avanzadas

(ej_b3_c02_08)=
### Ejercicio 3.02.08 - Insertar en Posición Arbitraria ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_08_insertar_en

Insertá un nuevo nodo en un índice arbitrario (`0 <= pos <= longitud`).

```c
bool lista_insertar_en(lista_t *lista, size_t posicion, int dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista Inicial | Posición | Dato | Lista Final |
| :--- | :--- | :--- | :--- | :--- |
| En cabeza (`pos == 0`) | `[10, 20]` | `0` | `5` | `[5, 10, 20]` |
| En medio (`pos == 1`) | `[5, 20]` | `1` | `15` | `[5, 15, 20]` |
| Al final (`pos == longitud`) | `[5, 15, 20]` | `3` | `30` | `[5, 15, 20, 30]` |
| Fuera de rango (`pos > longitud`) | `[5]` | `99` | `1` | Retorna `false` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

bool lista_insertar_en(lista_t *lista, size_t posicion, int dato) {
    if (lista == NULL || posicion > lista->longitud) {
        return false;
    }
    nodo_t *nuevo = (nodo_t *)malloc(sizeof(nodo_t));
    if (nuevo == NULL) {
        return false;
    }
    nuevo->dato = dato;

    if (posicion == 0) {
        nuevo->siguiente = lista->primero;
        lista->primero = nuevo;
    } else {
        nodo_t *ant = lista->primero;
        for (size_t i = 0; i < posicion - 1; ++i) {
            ant = ant->siguiente;
        }
        nuevo->siguiente = ant->siguiente;
        ant->siguiente = nuevo;
    }
    lista->longitud++;
    return true;
}

int main(void) {
    lista_t l = {NULL, 0};

    assert(lista_insertar_en(&l, 0, 20));
    assert(lista_insertar_en(&l, 0, 10));
    assert(lista_insertar_en(&l, 2, 30));
    assert(lista_insertar_en(&l, 1, 15));
    assert(!lista_insertar_en(&l, 10, 99));

    assert(l.longitud == 4);
    assert(l.primero->dato == 10);
    assert(l.primero->siguiente->dato == 15);
    assert(l.primero->siguiente->siguiente->dato == 20);
    assert(l.primero->siguiente->siguiente->siguiente->dato == 30);

    /* Liberación */
    nodo_t *act = l.primero;
    while (act != NULL) {
        nodo_t *sig = act->siguiente;
        free(act);
        act = sig;
    }
    return 0;
}
```

(ej_b3_c02_09)=
### Ejercicio 3.02.09 - Eliminar por Valor ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_09_eliminar_valor

Eliminá todas las ocurrencias del valor provisto y liberá la memoria de sus nodos.

```c
bool lista_eliminar(lista_t *lista, int dato);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Lista Inicial | Valor | Retorno | Lista Resultante |
| :--- | :--- | :--- | :--- | :--- |
| Elemento único | `[10, 20, 30]` | `20` | `true` | `[10, 30]` |
| Elementos repetidos | `[5, 5, 2, 5]` | `5` | `true` | `[2]` |
| Elemento inexistente | `[1, 2]` | `99` | `false` | `[1, 2]` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

bool lista_eliminar(lista_t *lista, int dato) {
    if (lista == NULL || lista->primero == NULL) {
        return false;
    }
    bool eliminado = false;

    /* Eliminar coincidencias en la cabeza */
    while (lista->primero != NULL && lista->primero->dato == dato) {
        nodo_t *borrar = lista->primero;
        lista->primero = borrar->siguiente;
        free(borrar);
        lista->longitud--;
        eliminado = true;
    }

    /* Eliminar coincidencias internas */
    nodo_t *actual = lista->primero;
    while (actual != NULL && actual->siguiente != NULL) {
        if (actual->siguiente->dato == dato) {
            nodo_t *borrar = actual->siguiente;
            actual->siguiente = borrar->siguiente;
            free(borrar);
            lista->longitud--;
            eliminado = true;
        } else {
            actual = actual->siguiente;
        }
    }
    return eliminado;
}

int main(void) {
    /* Lista con repetidos [5, 5, 2, 5] */
    nodo_t *n4 = (nodo_t *)malloc(sizeof(nodo_t));
    nodo_t *n3 = (nodo_t *)malloc(sizeof(nodo_t));
    nodo_t *n2 = (nodo_t *)malloc(sizeof(nodo_t));
    nodo_t *n1 = (nodo_t *)malloc(sizeof(nodo_t));
    assert(n1 && n2 && n3 && n4);

    n1->dato = 5; n1->siguiente = n2;
    n2->dato = 5; n2->siguiente = n3;
    n3->dato = 2; n3->siguiente = n4;
    n4->dato = 5; n4->siguiente = NULL;

    lista_t l = {n1, 4};
    assert(lista_eliminar(&l, 5));
    assert(l.longitud == 1);
    assert(l.primero->dato == 2);
    assert(l.primero->siguiente == NULL);

    assert(!lista_eliminar(&l, 99));

    free(l.primero);
    return 0;
}
```

(ej_b3_c02_10)=
### Ejercicio 3.02.10 - Invertir Lista in-place ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_10_invertir

Reordená destructivamente los punteros de los nodos de la lista para invertirla
in-place con complejidad temporal $O(n)$ y memoria auxiliar $O(1)$.

```c
void lista_invertir(lista_t *lista);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Entrada | Salida |
| :--- | :--- | :--- |
| Lista de 3 elementos | `[1, 2, 3]` | `[3, 2, 1]` |
| Lista de 1 elemento | `[42]` | `[42]` |
| Lista vacía | `[]` | `[]` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

void lista_invertir(lista_t *lista) {
    if (lista == NULL || lista->primero == NULL) {
        return;
    }
    nodo_t *prev = NULL;
    nodo_t *act = lista->primero;
    while (act != NULL) {
        nodo_t *sig = act->siguiente;
        act->siguiente = prev;
        prev = act;
        act = sig;
    }
    lista->primero = prev;
}

int main(void) {
    lista_invertir(NULL);

    nodo_t n3 = {3, NULL};
    nodo_t n2 = {2, &n3};
    nodo_t n1 = {1, &n2};
    lista_t l = {&n1, 3};

    lista_invertir(&l);
    assert(l.primero == &n3);
    assert(l.primero->siguiente == &n2);
    assert(l.primero->siguiente->siguiente == &n1);
    assert(l.primero->siguiente->siguiente->siguiente == NULL);

    return 0;
}
```

(ej_b3_c02_11)=
### Ejercicio 3.02.11 - Concatenar Listas ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_11_concatenar

Anexá todos los nodos de la lista `origen` al final de `destino`, dejando a `origen` vacía.

```c
void lista_concatenar(lista_t *destino, lista_t *origen);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Destino Inicial | Origen Inicial | Destino Final | Origen Final |
| :--- | :--- | :--- | :--- | :--- |
| Dos listas no vacías | `[1, 2]` | `[3, 4]` | `[1, 2, 3, 4]` | `[]` |
| Destino vacío | `[]` | `[10]` | `[10]` | `[]` |
| Origen vacío | `[10]` | `[]` | `[10]` | `[]` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct nodo {
    int dato;
    struct nodo *siguiente;
} nodo_t;

struct lista {
    nodo_t *primero;
    size_t longitud;
};
typedef struct lista lista_t;

void lista_concatenar(lista_t *destino, lista_t *origen) {
    if (destino == NULL || origen == NULL || origen->primero == NULL) {
        return;
    }
    if (destino->primero == NULL) {
        destino->primero = origen->primero;
    } else {
        nodo_t *act = destino->primero;
        while (act->siguiente != NULL) {
            act = act->siguiente;
        }
        act->siguiente = origen->primero;
    }
    destino->longitud += origen->longitud;
    origen->primero = NULL;
    origen->longitud = 0;
}

int main(void) {
    nodo_t b2 = {4, NULL};
    nodo_t b1 = {3, &b2};
    lista_t l2 = {&b1, 2};

    nodo_t a2 = {2, NULL};
    nodo_t a1 = {1, &a2};
    lista_t l1 = {&a1, 2};

    lista_concatenar(&l1, &l2);
    assert(l1.longitud == 4);
    assert(l2.longitud == 0 && l2.primero == NULL);
    assert(l1.primero->dato == 1);
    assert(l1.primero->siguiente->dato == 2);
    assert(l1.primero->siguiente->siguiente->dato == 3);
    assert(l1.primero->siguiente->siguiente->siguiente->dato == 4);

    return 0;
}
```

(ej_b3_c02_12)=
### Ejercicio 3.02.12 - TAD Contador Encapsulado ⭐☆☆☆☆

:::{exercise}
:label: ej_b3_c02_12_tad_contador

Implementá un Tipo de Dato Abstracto (TAD) de un contador entero con encapsulamiento estricto:
- `contador_t *contador_crear(int valor_inicial)`: asigna dinámicamente un contador en el Heap o retorna `NULL` si falla.
- `void contador_incrementar(contador_t *c)`: incrementa el valor en 1.
- `void contador_decrementar(contador_t *c)`: decrementa el valor en 1.
- `int contador_obtener_valor(const contador_t *c)`: retorna el valor actual (o 0 si `c == NULL`).
- `void contador_destruir(contador_t *c)`: libera de forma segura la memoria del Heap.

**Tabla de Vectores de Prueba:**

| Secuencia de Operaciones | Valor Esperado | Estado de Memoria |
| :--- | :--- | :--- |
| `crear(10) -> inc() -> inc()` | `12` | Asignado en Heap |
| `dec() -> dec() -> dec()` | `9` | Asignado en Heap |
| `destruir()` | N/A | Memoria liberada |
| Operación sobre puntero nulo | `0` | Seguro ante `NULL` |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

struct contador {
    int valor;
};
typedef struct contador contador_t;

contador_t *contador_crear(int valor_inicial) {
    contador_t *c = malloc(sizeof(contador_t));
    if (c == NULL) {
        return NULL;
    }
    c->valor = valor_inicial;
    return c;
}

void contador_incrementar(contador_t *c) {
    if (c != NULL) {
        c->valor++;
    }
}

void contador_decrementar(contador_t *c) {
    if (c != NULL) {
        c->valor--;
    }
}

int contador_obtener_valor(const contador_t *c) {
    if (c == NULL) {
        return 0;
    }
    return c->valor;
}

void contador_destruir(contador_t *c) {
    free(c);
}

int main(void) {
    contador_t *c = contador_crear(10);
    assert(c != NULL);
    assert(contador_obtener_valor(c) == 10);

    contador_incrementar(c);
    contador_incrementar(c);
    assert(contador_obtener_valor(c) == 12);

    contador_decrementar(c);
    contador_decrementar(c);
    contador_decrementar(c);
    assert(contador_obtener_valor(c) == 9);

    /* Casos con puntero nulo */
    contador_incrementar(NULL);
    contador_decrementar(NULL);
    assert(contador_obtener_valor(NULL) == 0);

    contador_destruir(c);
    contador_destruir(NULL);

    return 0;
}
```

(ej_b3_c02_13)=
### Ejercicio 3.02.13 - TAD Pila Acotada Encapsulada ⭐⭐☆☆☆

:::{exercise}
:label: ej_b3_c02_13_tad_pila

Implementá un Tipo de Dato Abstracto (TAD) de una Pila LIFO de enteros con capacidad fija en el Heap:
- `pila_t *pila_crear(size_t capacidad)`: reserva la estructura y su búfer dinámico.
- `bool pila_apilar(pila_t *p, int dato)`: inserta en el tope si hay capacidad disponible.
- `bool pila_desapilar(pila_t *p, int *dato)`: extrae el elemento superior si no está vacía.
- `bool pila_ver_tope(const pila_t *p, int *dato)`: consulta el elemento superior sin extraerlo.
- `bool pila_esta_vacia(const pila_t *p)`: verifica si la pila no tiene elementos.
- `void pila_destruir(pila_t *p)`: libera la memoria de forma segura.

**Tabla de Vectores de Prueba:**

| Secuencia de Operaciones | Resultado Operación | Estado de la Pila |
| :--- | :--- | :--- |
| `crear(2) -> apilar(10) -> apilar(20)` | `true, true` | Pila con 2 elementos (llena) |
| `apilar(30)` | `false` (rechazo por capacidad) | Pila inalterada |
| `ver_tope(&val)` | `true (val == 20)` | No altera la pila |
| `desapilar(&val)` | `true (val == 20)` | Pila con 1 elemento |
| `desapilar(&val) -> desapilar(&val)` | `true (val == 10), false` | Pila vacía |

::::{solution}
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

struct pila {
    int *datos;
    size_t capacidad;
    size_t tope;
};
typedef struct pila pila_t;

pila_t *pila_crear(size_t capacidad) {
    if (capacidad == 0) {
        return NULL;
    }
    pila_t *p = malloc(sizeof(pila_t));
    if (p == NULL) {
        return NULL;
    }
    p->datos = malloc(capacidad * sizeof(int));
    if (p->datos == NULL) {
        free(p);
        return NULL;
    }
    p->capacidad = capacidad;
    p->tope = 0;
    return p;
}

bool pila_apilar(pila_t *p, int dato) {
    if (p == NULL || p->tope >= p->capacidad) {
        return false;
    }
    p->datos[p->tope++] = dato;
    return true;
}

bool pila_desapilar(pila_t *p, int *dato) {
    if (p == NULL || dato == NULL || p->tope == 0) {
        return false;
    }
    *dato = p->datos[--p->tope];
    return true;
}

bool pila_ver_tope(const pila_t *p, int *dato) {
    if (p == NULL || dato == NULL || p->tope == 0) {
        return false;
    }
    *dato = p->datos[p->tope - 1];
    return true;
}

bool pila_esta_vacia(const pila_t *p) {
    return (p == NULL || p->tope == 0);
}

void pila_destruir(pila_t *p) {
    if (p != NULL) {
        free(p->datos);
        free(p);
    }
}

int main(void) {
    pila_t *p = pila_crear(2);
    assert(p != NULL);
    assert(pila_esta_vacia(p) == true);

    assert(pila_apilar(p, 10) == true);
    assert(pila_apilar(p, 20) == true);
    assert(pila_esta_vacia(p) == false);

    /* Desborde */
    assert(pila_apilar(p, 30) == false);

    int val = 0;
    assert(pila_ver_tope(p, &val) == true);
    assert(val == 20);

    assert(pila_desapilar(p, &val) == true);
    assert(val == 20);

    assert(pila_desapilar(p, &val) == true);
    assert(val == 10);

    /* Subflujo */
    assert(pila_desapilar(p, &val) == false);
    assert(pila_esta_vacia(p) == true);

    pila_destruir(p);
    pila_destruir(NULL);

    return 0;
}
```

(ej_b3_c02_14)=
### Ejercicio 3.02.14 - TAD Cola FIFO Encapsulada ⭐⭐⭐☆☆

:::{exercise}
:label: ej_b3_c02_14_cola
:enumerator: tad-14

Implementá un Tipo Abstracto de Datos **Cola FIFO** (*First-In, First-Out*) encapsulado mediante una lista simplemente enlazada con punteros a la cabecera (`frente`) y a la cola (`fin`):

```c
typedef struct cola cola_t;

cola_t *cola_crear(void);
bool cola_encolar(cola_t *c, int dato);
bool cola_desencolar(cola_t *c, int *dato);
bool cola_ver_frente(const cola_t *c, int *dato);
bool cola_esta_vacia(const cola_t *c);
size_t cola_tamanio(const cola_t *c);
void cola_destruir(cola_t *c);
```

Todas las operaciones de inserción y extracción deben resolverse en tiempo constante estricto $O(1)$.

**Nivel de Bloom:** Nivel 4 (Análisis) y Nivel 5 (Evaluación).  
**Conceptos requeridos:** Punteros a estructuras opacas, desacoplamiento de interfaz, manejo dinámico con `malloc` y `free`, disciplina FIFO.  
**Techo conceptual:** Prohibido el recorrido $O(N)$ para encolar o desencolar.

#### Contrato de las Funciones
- **Precondiciones:** `c != NULL` para operar; si `c == NULL`, las consultas retornan `false` o `0`.
- **Postcondiciones:** `cola_encolar` inserta al final en $O(1)$; `cola_desencolar` extrae del frente en $O(1)$. Toda la memoria se libera con `cola_destruir`.

#### Tabla de Vectores de Prueba

| Secuencia de Operaciones | Entrada | Salida / Estado | Justificación Técnica |
| :--- | :--- | :--- | :--- |
| `crear` -> `esta_vacia` | - | `true`, tamaño `0` | Inicialización de cola vacía |
| `encolar(10)` -> `encolar(20)` | `10, 20` | `true`, tamaño `2` | Inserción en orden FIFO |
| `ver_frente(&val)` | - | `true`, `val = 10` | Consulta no destructiva del primer elemento |
| `desencolar(&val)` | - | `true`, `val = 10` | Primer encolado es el primero en salir |
| `desencolar(&val)` | - | `true`, `val = 20` | Segundo elemento extraído; cola queda vacía |
| `desencolar(&val)` sobre vacía | - | `false` | Subflujo (*underflow*) controlado |

Ejercicio 3.02.15 - TAD Lista Enlazada ⭐⭐⭐☆☆

Implementá lista enlazada simple:

Orientación:


Ejercicio 3.02.16 - TAD Conjunto de Enteros Encapsulado ⭐⭐⭐⭐☆


Ejercicio 3.02.17 - TAD Diccionario Clave-Valor Asociativo ⭐⭐⭐⭐☆


Ejercicio 3.02.18 - TAD Pila Genérica ⭐⭐⭐⭐☆

Pila que almacena void * (cualquier tipo):

Orientación:


Ejercicio 3.02.19 - TAD Cola de Prioridad ⭐⭐⭐⭐⭐

Cola donde elementos con mayor prioridad salen primero:

Orientación:


Ejercicio 3.02.20 - TAD Árbol Binario de Búsqueda ⭐⭐⭐⭐⭐

ABB con operaciones estándar:

Orientación:


Ejercicio 3.02.21 - TAD Grafo ⭐⭐⭐⭐⭐

Grafo dirigido con listas de adyacencia:

Orientación:


Ejercicio 3.02.22 - TAD Matriz Dispersa ⭐⭐⭐⭐⭐

Matriz que solo almacena elementos no cero:

Orientación:


Ejercicio 3.02.23 - TAD Cadena Dinámica ⭐⭐⭐⭐☆

String que crece automáticamente:

Orientación:


Ejercicio 3.02.24 - TAD Tabla Hash ⭐⭐⭐⭐⭐

Hash table con manejo de colisiones:

Orientación:


Ejercicio 3.02.25 - TAD Buffer Circular ⭐⭐⭐⭐☆

Buffer circular para comunicación productor-consumidor:

Orientación:


Ejercicio 3.02.26 - TAD Iterador ⭐⭐⭐⭐⭐

Iterador externo para lista:

Orientación:


Ejercicio 3.02.27 - TAD Árbol AVL ⭐⭐⭐⭐⭐

Árbol auto-balanceado:

Orientación:


Ejercicio 3.02.28 - TAD Heap (Min/Max) ⭐⭐⭐⭐⭐

Heap binario genérico:

Orientación:


Ejercicio 3.02.29 - TAD Cache LRU ⭐⭐⭐⭐⭐

Cache con política Least Recently Used:

Orientación:


Ejercicio 3.02.30 - TAD Multi-Conjunto (Bag) ⭐⭐⭐⭐⭐

Permite elementos repetidos con conteo:

Orientación:


Ejercicio 3.02.31 - Sistema de TADs Interconectados ⭐⭐⭐⭐⭐

Sistema completo: Biblioteca de libros usando múltiples TADs:

Operaciones:

Orientación:


Notas Finales

Estas consignas cubren el diseño e implementación de TADs desde básicos hasta complejos, enfatizando encapsulamiento, modularidad y reutilización.