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.

Pilas, Colas y Estructuras Lineales Restringidas

TAD Pila, Cola y Deque

Universidad Nacional de Río Negro

Referencias y Lecturas Complementarias

Introducción

Desarrollo

Pilas (Stacks)

Una pila es una estructura de datos lineal que sigue el principio LIFO (Last In, First Out): el último elemento en entrar es el primero en salir. Es análogo a una pila de platos donde solo podés agregar o quitar platos desde la parte superior.

Operaciones de apilado (push) and desapilado (pop) en una Pila.

Figure 1:Operaciones de apilado (push) and desapilado (pop) en una Pila.

Operaciones Fundamentales
Implementación con Lista Enlazada
Estructura de una Pila implementada dinámicamente mediante nodos enlazados en el
heap.

Figure 2:Estructura de una Pila implementada dinámicamente mediante nodos enlazados en el heap.

Estructura de Datos
1
2
3
4
5
6
7
8
9
10
typedef struct nodo
{
    void *dato;
    struct nodo *siguiente;
} nodo_t;
struct pila
{
    nodo_t *tope;
    size_t tamanio;
};
Creación de una Pila
1
2
3
4
5
6
7
8
9
10
11
pila_t *pila_crear(void)
{
    pila_t *pila = malloc(sizeof(*pila));
    if (pila == NULL)
    {
        return NULL;
    }
    pila->tope = NULL;
    pila->tamanio = 0;
    return pila;
}
Apilar (Push)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool pila_push(pila_t *pila, void *dato)
{
    if (pila == NULL)
    {
        return false;
    }
    nodo_t *nuevo = malloc(sizeof(*nuevo));
    if (nuevo == NULL)
    {
        return false;
    }
    nuevo->dato = dato;
    nuevo->siguiente = pila->tope;
    pila->tope = nuevo;
    pila->tamanio++;
    return true;
}
Desapilar (Pop)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool pila_pop(pila_t *pila, void **dato)
{
    if (pila == NULL || pila->tope == NULL)
    {
        return false;
    }
    nodo_t *nodo_a_eliminar = pila->tope;
    if (dato != NULL)
    {
        *dato = nodo_a_eliminar->dato;
    }
    pila->tope = nodo_a_eliminar->siguiente;
    free(nodo_a_eliminar);
    pila->tamanio--;
    return true;
}
Ver Tope (Peek)
1
2
3
4
5
6
7
8
9
10
11
12
bool pila_peek(const pila_t *pila, void **dato)
{
    if (pila == NULL || pila->tope == NULL)
    {
        return false;
    }
    if (dato != NULL)
    {
        *dato = pila->tope->dato;
    }
    return true;
}
Verificar si está Vacía
1
2
3
4
bool pila_es_vacia(const pila_t *pila)
{
    return (pila == NULL) || (pila->tope == NULL);
}
Destruir Pila
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void pila_destruir(pila_t *pila, destruir_dato_fn destruir_dato)
{
    if (pila == NULL)
    {
        return;
    }
    while (pila->tope != NULL)
    {
        nodo_t *nodo_actual = pila->tope;
        pila->tope = nodo_actual->siguiente;
        if (destruir_dato != NULL && nodo_actual->dato != NULL)
        {
            destruir_dato(nodo_actual->dato);
        }
        free(nodo_actual);
    }
    free(pila);
}
Análisis de Complejidad de la Pila (Lista Enlazada)
OperaciónComplejidad TemporalComplejidad Espacial
pushO(1)O(1)O(1)O(1)
popO(1)O(1)O(1)O(1)
peekO(1)O(1)O(1)O(1)
es_vaciaO(1)O(1)O(1)O(1)
Implementación con Arreglo Dinámico

Una alternativa es implementar la pila usando un arreglo, donde el tope es el último elemento ocupado.

Estructura de una Pila implementada estáticamente mediante un arreglo y un
índice de tope.

Figure 3:Estructura de una Pila implementada estáticamente mediante un arreglo y un índice de tope.

Estructura de Datos
1
2
3
4
5
6
struct pila
{
    void **elementos;
    size_t tope;      // Próximo índice libre / Cantidad de elementos
    size_t capacidad; // Capacidad total del arreglo
};
Creación con Capacidad Inicial
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pila_t *pila_crear_arreglo(size_t capacidad_inicial)
{
    if (capacidad_inicial == 0)
    {
        return NULL;
    }
    pila_t *pila = malloc(sizeof(*pila));
    if (pila == NULL)
    {
        return NULL;
    }
    pila->elementos = malloc(capacidad_inicial * sizeof(*(pila->elementos)));
    if (pila->elementos == NULL)
    {
        free(pila);
        return NULL;
    }
    pila->tope = 0;
    pila->capacidad = capacidad_inicial;
    return pila;
}
Apilar con Redimensionamiento
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static bool pila_redimensionar(pila_t *pila)
{
    size_t nueva_capacidad = pila->capacidad * 2;
    void **nuevo_arreglo =
        realloc(pila->elementos, nueva_capacidad * sizeof(*nuevo_arreglo));
    if (nuevo_arreglo == NULL)
    {
        return false;
    }
    pila->elementos = nuevo_arreglo;
    pila->capacidad = nueva_capacidad;
    return true;
}
bool pila_push_arreglo(pila_t *pila, void *dato)
{
    if (pila == NULL)
    {
        return false;
    }
    if (pila->tope >= pila->capacidad)
    {
        if (!pila_redimensionar(pila))
        {
            return false;
        }
    }
    pila->elementos[pila->tope] = dato;
    pila->tope++;
    return true;
}
Desapilar (Arreglo)
1
2
3
4
5
6
7
8
9
10
11
12
13
bool pila_pop_arreglo(pila_t *pila, void **dato)
{
    if (pila == NULL || pila->tope == 0)
    {
        return false;
    }
    pila->tope--;
    if (dato != NULL)
    {
        *dato = pila->elementos[pila->tope];
    }
    return true;
}
Análisis de Complejidad (Arreglo)
OperaciónComplejidad TemporalComplejidad Espacial
pushO(1)O(1) amortizadoO(1)O(1)
popO(1)O(1)O(1)O(1)
peekO(1)O(1)O(1)O(1)
es_vaciaO(1)O(1)O(1)O(1)
Aplicaciones de Pilas

Las pilas aparecen naturalmente en numerosos contextos de programación:

  1. Gestión de llamadas a funciones: La pila de ejecución (call stack) mantiene los registros de activación.

  2. Evaluación de expresiones: Conversión de notación infija a postfija, evaluación de expresiones postfijas.

  3. Backtracking: Algoritmos de búsqueda en profundidad, resolución de laberintos.

  4. Deshacer/Rehacer: Editores de texto mantienen pilas de operaciones.

  5. Parsing: Análisis sintáctico de lenguajes de programación (verificación de paréntesis balanceados).

Ejemplo: Verificación de Paréntesis Balanceados
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
bool parentesis_balanceados(const char *expresion)
{
    if (expresion == NULL)
    {
        return false;
    }
    pila_t *pila = pila_crear();
    if (pila == NULL)
    {
        return false;
    }
    for (size_t i = 0; expresion[i] != '\0'; i++)
    {
        char caracter = expresion[i];
        if (caracter == '(')
        {
            if (!pila_push(pila, caracter))
            {
                pila_destruir(pila);
                return false;
            }
        }
        else if (caracter == ')')
        {
            if (pila_es_vacia(pila))
            {
                pila_destruir(pila);
                return false;
            }
            int temporal;
            pila_pop(pila, &temporal);
        }
    }
    bool resultado = pila_es_vacia(pila);
    pila_destruir(pila);
    return resultado;
}
Ejercicios de Pilas
Ejercicio 1: Inversión de una cadena con pila
Ejercicio 2: Validar expresiones con múltiples delimitadores
Ejercicio 3: Evaluación de expresiones postfijas

Colas (Queues)

Una cola es una estructura de datos lineal que sigue el principio FIFO (First In, First Out): el primer elemento en entrar es el primero en salir. Es análogo a una fila de personas donde quien llega primero es atendido primero.

Estructura de una Cola implementada dinámicamente mediante nodos enlazados en el
heap con punteros a inicio y fin.

Figure 4:Estructura de una Cola implementada dinámicamente mediante nodos enlazados en el heap con punteros a inicio y fin.

Estructura de Datos
1
2
3
4
5
6
7
8
9
10
11
typedef struct nodo
{
    void *dato;
    struct nodo *siguiente;
} nodo_t;
struct cola
{
    nodo_t *frente;
    nodo_t *final;
    size_t tamanio;
};