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.
Figure 1:Estructura de pila con operaciones push (apilar) y pop (desapilar). El acceso es únicamente por el tope.
Operaciones Fundamentales¶
push(elemento): Agrega un elemento al tope de la pila.
pop(): Extrae y retorna el elemento del tope.
peek() o top(): Retorna el elemento del tope sin extraerlo.
es_vacia(): Verifica si la pila está vacía.
Implementación con Lista Enlazada¶
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ón | Complejidad Temporal | Complejidad Espacial |
|---|---|---|
| push | ||
| pop | ||
| peek | ||
| es_vacia |
Implementación con Arreglo Dinámico¶
Una alternativa es implementar la pila usando un arreglo, donde el tope es el último elemento ocupado.
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ón | Complejidad Temporal | Complejidad Espacial |
|---|---|---|
| push | amortizado | |
| pop | ||
| peek | ||
| es_vacia |
Aplicaciones de Pilas¶
Las pilas aparecen naturalmente en numerosos contextos de programación:
Gestión de llamadas a funciones: La pila de ejecución (call stack) mantiene los registros de activación.
Evaluación de expresiones: Conversión de notación infija a postfija, evaluación de expresiones postfijas.
Backtracking: Algoritmos de búsqueda en profundidad, resolución de laberintos.
Deshacer/Rehacer: Editores de texto mantienen pilas de operaciones.
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.
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;
};