Saltar al contenido
ES EN
Tema: automático

Ejercicio resuelto: pila con vector y paréntesis equilibrados en C

Implementa una pila de tamaño fijo sobre un vector en C y úsala para comprobar si una fórmula tiene sus paréntesis, corchetes y llaves equilibrados.

Código de Examen C Estructuras de datos Nivel básico Código probado

Código de Examen: Ejercicio resuelto: pila con vector y paréntesis equilibrados en C
Estructuras de Datos · Examen parcialEjercicio 2 · 2,5 puntos · 25 min

Una calculadora científica muy sencilla guarda las fórmulas como texto y, antes de evaluarlas, comprueba que sus agrupaciones están bien formadas. Las agrupaciones son tres: paréntesis ( ), corchetes [ ] y llaves { }. Cualquier otro carácter de la fórmula (cifras, letras, operadores, espacios) se ignora.

Una fórmula está equilibrada si cada símbolo de cierre corresponde al último símbolo de apertura que sigue abierto y es de su mismo tipo, y al terminar no queda ninguna apertura sin cerrar. Por ejemplo, {[()]} está equilibrada, pero ([)] no, y tampoco (() ni )(.

Parte A. Define una pila de caracteres implementada sobre un vector estático de capacidad MAX_PILA = 32, con las operaciones pila_iniciar, pila_vacia, pila_llena, apilar y desapilar. Las operaciones que pueden fallar (apilar en una pila llena, desapilar de una pila vacía) deben avisar del fallo con su valor de retorno y no pueden escribir ni leer fuera del vector.

Parte B. Escribe la función int equilibrada(const char *formula) que use esa pila y devuelva 1 si la fórmula está equilibrada y 0 si no lo está. Si la pila se llena porque hay demasiadas aperturas abiertas a la vez (más de 32), la fórmula se considera no equilibrada.

Entrada. La primera línea contiene un entero n (0 ≤ n ≤ 100) con el número de fórmulas. Siguen n líneas, cada una con una fórmula de como máximo 200 caracteres. Una línea puede estar vacía (fórmula vacía, que se considera equilibrada). El programa trata cada línea como el texto que hay hasta el primer salto de línea o retorno de carro, de modo que los finales de línea de Windows (\r\n) no afectan al resultado.

Salida. Para cada fórmula, una línea con el número de orden empezando en 1, dos puntos, un espacio y la palabra SI o NO. Si n es 0 no se imprime nada.

Entrada de ejemplo

6
{[()]}
([)]
(()
)(
3*(a+b)-[c/{d}]
x+y

Salida esperada

1: SI
2: NO
3: NO
4: NO
5: SI
6: SI
Pila con vector: estructura, inicialización, apilar, desapilar y consulta de pila vacía o llena, con control de límites
1,0
Algoritmo de emparejamiento correcto (apilar al abrir, comparar al cerrar, ignorar el resto de caracteres)
1,0
Casos límite: cierre sin apertura, aperturas sobrantes, fórmula vacía y desbordamiento de la pila
0,5

Pistas

Pista 1 · ¿Qué estructura recuerda siempre la última apertura pendiente?

Una pila: el último símbolo de apertura que se apila es el primero que debe cerrarse. Recorre la fórmula carácter a carácter y decide qué hacer según sea apertura, cierre u otro carácter.

Pista 2 · ¿Qué ocurre exactamente al encontrar un cierre?

Hay tres posibilidades de fallo: la pila está vacía (cierre sin apertura), el símbolo desapilado no es el de apertura del mismo tipo, o se acaba la fórmula con la pila no vacía. Conviene escribir una función auxiliar que, dado un cierre, devuelva su apertura correspondiente.

Pista 3 · ¿Cómo representas la pila en el vector?

Guarda un entero cima con el número de elementos almacenados: la pila está vacía si vale 0 y llena si vale MAX_PILA. Apilar escribe en datos[cima] y luego incrementa; desapilar decrementa y lee datos[cima]. Comprueba antes los límites y devuelve 0 si no se puede.

Solución

Solución explicada

La solución separa dos niveles: la pila, que no sabe nada de paréntesis, y la función equilibrada, que solo usa la interfaz de la pila. La pila es una estructura con un vector datos y un contador cima que indica cuántos elementos hay; el siguiente hueco libre es siempre datos[cima].

apilar y desapilar devuelven 1 si la operación se realizó y 0 si no se pudo, y desapilar entrega el carácter por un puntero de salida. Así el llamador puede reaccionar a los fallos sin que la pila escriba fuera de sus límites.

#include <stdio.h>
#include <string.h>

#define MAX_PILA 32
#define MAX_LINEA 256

typedef struct {
    char datos[MAX_PILA];
    int cima; /* numero de elementos almacenados */
} Pila;

static void pila_iniciar(Pila *p) {
    p->cima = 0;
}

static int pila_vacia(const Pila *p) {
    return p->cima == 0;
}

static int pila_llena(const Pila *p) {
    return p->cima == MAX_PILA;
}

static int apilar(Pila *p, char c) {
    if (pila_llena(p)) {
        return 0;
    }
    p->datos[p->cima] = c;
    p->cima++;
    return 1;
}

static int desapilar(Pila *p, char *c) {
    if (pila_vacia(p)) {
        return 0;
    }
    p->cima--;
    *c = p->datos[p->cima];
    return 1;
}

static char apertura_de(char cierre) {
    switch (cierre) {
    case ')': return '(';
    case ']': return '[';
    case '}': return '{';
    default:  return '\0';
    }
}

static int equilibrada(const char *formula) {
    Pila p;
    char c;
    char tope;
    pila_iniciar(&p);
    for (size_t i = 0; formula[i] != '\0'; i++) {
        c = formula[i];
        if (c == '(' || c == '[' || c == '{') {
            if (!apilar(&p, c)) {
                return 0; /* demasiadas aperturas abiertas a la vez */
            }
        } else if (c == ')' || c == ']' || c == '}') {
            if (!desapilar(&p, &tope)) {
                return 0; /* cierre sin apertura */
            }
            if (tope != apertura_de(c)) {
                return 0; /* tipos distintos */
            }
        }
    }
    return pila_vacia(&p);
}

int main(void) {
    char linea[MAX_LINEA];
    int n;

    if (fgets(linea, sizeof linea, stdin) == NULL) {
        return 0;
    }
    if (sscanf(linea, "%d", &n) != 1) {
        return 0;
    }
    for (int k = 1; k <= n; k++) {
        if (fgets(linea, sizeof linea, stdin) == NULL) {
            linea[0] = '\0';
        }
        linea[strcspn(linea, "\r\n")] = '\0';
        printf("%d: %s\n", k, equilibrada(linea) ? "SI" : "NO");
    }
    return 0;
}

La estructura Pila contiene datos[MAX_PILA] y cima. pila_iniciar pone cima a 0. pila_vacia y pila_llena son comparaciones simples, pero usarlas en lugar de repetir la condición evita errores de límites.

En apilar se comprueba primero pila_llena; solo si hay hueco se escribe en datos[cima] y se incrementa cima. En desapilar el orden se invierte: se comprueba pila_vacia, se decrementa cima y entonces se lee datos[cima], que es el último elemento guardado. Ese orden es la clave: si primero se leyera y luego se decrementara, se estaría leyendo el hueco libre.

En equilibrada se recorre la cadena con el índice i. Si el carácter c es una apertura, se apila, y si apilar devuelve 0 (pila llena) se responde 0 directamente. Si es un cierre, se desapila en tope; si no había nada, es un cierre sin apertura (caso )(), y si tope no coincide con apertura_de(c) los tipos se cruzan (caso ([)]). Cualquier otro carácter no hace nada. Al acabar, la fórmula es correcta solo si la pila está vacía, lo que detecta (().

En main se lee cada línea con fgets en un buffer de 256 bytes y se corta en el primer \r o \n con strcspn; por eso un final de línea de Windows no altera el resultado. La fórmula más larga permitida (200 caracteres) cabe de sobra. Sobre los tests: la fórmula de 33 aperturas seguidas devuelve NO por desbordamiento (en la apertura número 33 apilar devuelve 0), mientras que la de 32 aperturas y 32 cierres devuelve SI, porque llena la pila exactamente sin pasarse. La línea vacía devuelve SI porque no se apila nada y la pila acaba vacía, y con n = 0 el bucle no se ejecuta y no se imprime nada.

Código y tests en GitLab

Casos de prueba

CasoEntradaSalida esperadaSalida realResultado
Ejemplo con casos variados6 {[()]} ([)] (() )( 3*(a+b)-[c/{d}] x+y1: SI 2: NO 3: NO 4: NO 5: SI 6: SI1: SI 2: NO 3: NO 4: NO 5: SI 6: SIOK
Cero fórmulas0(vacía)(vacía)OK
Fórmula vacía y un solo símbolo3 ( }1: SI 2: NO 3: NO1: SI 2: NO 3: NOOK
Desbordamiento de la pila3 ((((((((((((((((((((((((((((((((( ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) (((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))1: NO 2: NO 3: SI1: NO 2: NO 3: SIOK
Finales de línea de Windows2 {[(a)]} [(]) 1: SI 2: NO1: SI 2: NOOK

Salidas reales: código compilado con gcc 14.4.0 (C17) y ejecutado en un contenedor aislado el 13 de octubre de 2026.

Complejidad

El tiempo es O(L) por fórmula, siendo L su longitud: cada carácter se examina una vez y apilar y desapilar son O(1) porque solo tocan datos[cima] y el contador. Para n fórmulas, O(n · L).

La memoria extra es O(1) respecto a la entrada, porque la pila ocupa siempre MAX_PILA caracteres más un entero, con independencia de la longitud de la fórmula. Esa cota fija es la razón de que haya que definir qué pasa cuando se supera: aquí, la fórmula se declara no equilibrada.

Errores típicos

  • Apilar sin comprobar si la pila está llena. Al apilar en la posición 32 se escribe fuera del vector: es comportamiento indefinido, y según la disposición de la estructura en memoria puede corromper el campo cima o cualquier otro dato, o parecer que funciona.
  • Desapilar sin comprobar si la pila está vacía, con lo que cima pasa a ser negativa y se lee fuera del vector. Un cierre sin apertura, como en )(, lo provoca fácilmente.
  • Devolver 1 al llegar al final de la cadena sin comprobar que la pila está vacía: la fórmula (() pasaría por equilibrada.
  • Contar los símbolos en lugar de usar una pila: con tres contadores, ([)] parece correcta, porque cada tipo tiene tantas aperturas como cierres, pero el orden de cierre es incorrecto.
  • Olvidar quitar el salto de línea que deja fgets: no afecta a las agrupaciones porque \n se ignora, pero es mala costumbre, y con \r\n conviene eliminar ambos caracteres.
  • Leer datos[cima] antes de decrementar cima en desapilar: se obtiene el hueco libre, no el último elemento.

Variantes

Devolver la posición del primer error

En lugar de SI o NO, imprime la posición (empezando en 1) del primer carácter que rompe el equilibrio. Para ello la pila debe guardar también el índice de cada apertura, por ejemplo con un segundo vector paralelo pos[MAX_PILA]. Un cierre inválido se reporta en su propia posición; si al final quedan aperturas abiertas, se reporta la de la más antigua que sigue en la pila.

Pila dinámica con crecimiento automático

Sustituye el vector estático por un puntero con malloc y un campo capacidad. Cuando cima == capacidad se duplica la capacidad con realloc, comprobando que no devuelva NULL. Así desaparece el límite de 32 aperturas, aunque hay que añadir una función pila_destruir que libere la memoria. Puedes repasar el manejo de memoria dinámica en este ejercicio sobre malloc y free.

En los temarios

Las pilas y las colas son un tema habitual en los cursos de estructuras de datos. Por ejemplo, el curso ISIS1206 Estructuras de datos de la Universidad de los Andes (Colombia) incluye «pilas y colas» en su cronograma, tras las listas y antes de los algoritmos de ordenamiento básicos. La página consultada solo lista el tema y no detalla cómo se implementa ni qué ejercicios se proponen, de modo que esta práctica debe leerse como un ejercicio genérico sobre el tema, no como un ejercicio de ese curso.

Artículo generado mediante AI.larebelion · Revisado por Kernel

Kernel

· Dirección de tecnología · España

«Una pila sin control de desbordamiento no es una estructura de datos, es un fallo esperando a que alguien escriba la fórmula número 33.»