10 - Ejemplo práctico 1: validación de paréntesis

10.1 Idea general

Queremos saber si una cadena posee paréntesis, llaves y corchetes correctamente equilibrados. La regla clave es que cada apertura debe cerrarse en el orden inverso al que fue abierta, lo que convierte a la pila en la herramienta perfecta.

10.2 Caracteres a evaluar

Consideraremos tres pares:

  • () para paréntesis.
  • [] para corchetes.
  • {} para llaves.

Es sencillo ampliar la lista si la aplicación requiere otros delimitadores (por ejemplo, <> en lenguajes propios).

10.3 Uso de la pila para apertura/cierre

  1. Recorrer la cadena caracter por caracter.
  2. Cuando aparece una apertura, se apila.
  3. Cuando aparece un cierre, se compara con el tope:
    • Si coincide (por ejemplo . ), se desapila.
    • Si no coincide o la pila está vacía, la cadena es inválida.
  4. Al finalizar, la pila debe quedar vacía.

10.4 Casos especiales

  • Caracteres que no son delimitadores se ignoran.
  • Se puede permitir texto vacío o considerar que no hay paréntesis para validar.
  • Si se desea detectar la posición del error, se guarda el índice del caracter actual al fallar.
  • En lenguajes con comentarios o cadenas literales, conviene filtrar esas secciones antes de validar para evitar falsos positivos.

10.5 Cómo implementarlo en C

Usaremos una pila basada en arrays para simplicidad. El siguiente snippet funciona como utilidad general:

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

#define STACK_MAX 256

typedef struct {
  char datos[STACK_MAX];
  int top;
} StackChar;

void stack_init(StackChar *s) { s->top = 0; }
int stack_is_empty(const StackChar *s) { return s->top == 0; }
int stack_push(StackChar *s, char c) {
  if (s->top == STACK_MAX) return 0;
  s->datos[s->top++] = c;
  return 1;
}
int stack_pop(StackChar *s, char *c) {
  if (stack_is_empty(s)) return 0;
  *c = s->datos[--s->top];
  return 1;
}

static int coincide(char apertura, char cierre) {
  return (apertura == '(' && cierre == ')') ||
         (apertura == '[' && cierre == ']') ||
         (apertura == '{' && cierre == '}');
}

int validar_parentesis(const char *texto) {
  StackChar stack;
  stack_init(&stack);

  for (size_t i = 0; i < strlen(texto); ++i) {
    char c = texto[i];
    if (c == '(' || c == '[' || c == '{') {
      if (!stack_push(&stack, c)) return 0;
    } else if (c == ')' || c == ']' || c == '}') {
      char apertura;
      if (!stack_pop(&stack, &apertura) || !coincide(apertura, c)) {
        return 0;
      }
    }
  }

  return stack_is_empty(&stack);
}

int main(void) {
  const char *tests[] = {
    "(a + b) * (c + d)",
    "([{}])",
    "( [ ) ]",
    "funcion(x[2] + arr{1})"
  };

  for (size_t i = 0; i < sizeof(tests)/sizeof(tests[0]); ++i) {
    printf("%s => %s\n", tests[i], validar_parentesis(tests[i]) ? "OK" : "ERROR");
  }

  return 0;
}

Al ejecutar el programa, las cadenas equilibradas imprimirán OK, mientras que las erróneas mostrarán ERROR.

Salida de consola validando paréntesis

10.6 Relación con problemas de entrevistas técnicas

Validar paréntesis es un clásico de entrevistas para medir:

  • Comprensión de pilas y orden LIFO.
  • Habilidad para manejar casos borde (desbordes de pila o cierres extra).
  • Capacidad para justificar la complejidad O(n) y el consumo O(n) en memoria.

Una extensión frecuente es soportar expresiones completas con operadores, distintos tipos de llaves o comentarios. Practicar este ejemplo permite dominar la pila antes de abordar ejercicios más complejos.