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 Rio Negro - Sede Andina

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.

Estructura de pila con operaciones push (apilar) y pop (desapilar). El acceso es únicamente por el tope.

Figure 1:Estructura de pila con operaciones push (apilar) y pop (desapilar). El acceso es únicamente por el tope.

Operaciones Fundamentales

Implementación con Lista Enlazada

Representación en memoria de una pila implementada con lista enlazada. El tope apunta al primer nodo de la lista.

Figure 2:Representación en memoria de una pila implementada con lista enlazada. El tope apunta al primer nodo de la lista.

Estructura de Datos

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

struct pila {
    nodo_t *tope;
    size_t tamanio;
};

Creación de una Pila

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)

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)

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)

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

bool pila_es_vacia(const pila_t *pila)
{
    return (pila == NULL) || (pila->tope == NULL);
}

Destruir Pila

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 (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.

Pila implementada con arreglo. El índice tope indica la posición del último elemento.

Figure 3:Pila implementada con arreglo. El índice tope indica la posición del último elemento.

Estructura de Datos

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

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

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)

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

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

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.

Representación en memoria de una cola implementada con lista enlazada. Se mantienen punteros al frente y al final.

Figure 4:Representación en memoria de una cola implementada con lista enlazada. Se mantienen punteros al frente y al final.

Estructura de Datos

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

struct cola {
    nodo_t *frente;
    nodo_t *final;
    size_t tamanio;
};