4 - Operaciones en colas lineales

Este tema se concentra en las operaciones indispensables de una cola lineal basada en arrays. Cada rutina mantiene la complejidad O(1) y valida los estados de la estructura antes de modificarla.

4.1 Enqueue

enqueue inserta un nuevo elemento al final. Debe verificar que no se haya alcanzado la capacidad. Devuelve 1 si el valor ingresó y 0 en caso contrario.

int enqueue(Cola *cola, int valor) {
  if (cola->final == CAPACIDAD) {
    return 0;
  }
  cola->datos[cola->final++] = valor;
  cola->cantidad++;
  return 1;
}

Se recomienda encapsular la constante CAPACIDAD en un #define o enum y centralizar la inicialización en una función que ponga a cero todos los campos.

4.2 Dequeue

dequeue extrae el elemento ubicado en frente. Si la cola está vacía retorna 0 y deja el parámetro de salida sin modificar.

int dequeue(Cola *cola, int *valor) {
  if (cola->cantidad == 0) {
    return 0;
  }
  *valor = cola->datos[cola->frente++];
  cola->cantidad--;
  if (cola->frente == cola->final) {
    cola->frente = 0;
    cola->final = cola->cantidad;
  }
  return 1;
}

El reinicio opcional de índices evita que frente continúe creciendo indefinidamente cuando la cola vuelve a quedar vacía.

4.3 Peek

peek consulta el próximo elemento a atender sin retirarlo. Devuelve un valor centinela (por ejemplo -1) cuando la cola está vacía. Alternativamente puede retornar un código y escribir el resultado en un puntero.

int peek(const Cola *cola) {
  if (cola->cantidad == 0) {
    return -1;
  }
  return cola->datos[cola->frente];
}

En contextos donde el dato -1 es válido, conviene usar una función similar a dequeue con retorno entero y parámetro de salida.

4.4 isEmpty

Permite consultar si hay elementos pendientes. Resulta útil para las capas que consumen la cola sin conocer su implementación interna.

int esVacia(const Cola *cola) {
  return cola->cantidad == 0;
}

4.5 isFull

Complementa a isEmpty para evitar encolados cuando ya no queda espacio disponible.

int esLlena(const Cola *cola) {
  return cola->final == CAPACIDAD;
}

4.6 Imprimir la cola

Instrumentar una función de depuración que recorra desde frente hasta final ayuda a inspeccionar el estado de la estructura mientras se prueban las operaciones.

void imprimir(const Cola *cola) {
  printf("[cola] frente=%d final=%d cantidad=%d -> ", cola->frente, cola->final, cola->cantidad);
  for (int i = cola->frente; i < cola->final; ++i) {
    printf("%d ", cola->datos[i]);
  }
  puts("");
}

Este tipo de asistencia visual es clave cuando se integran colas en hilos o interrupciones.

4.7 Errores comunes

  • No validar cola llena: provoca escrituras fuera del arreglo o sobre datos no consumidos.
  • No actualizar el contador: desconecta las funciones isEmpty y isFull del estado real.
  • Olvidar reiniciar los índices: impide reusar la cola tras consumir todos los elementos.
  • Retornar referencias internas: copiar el dato antes de devolverlo evita exponer el array.

Detectar a tiempo estos errores evita comportamientos no deterministas en sistemas embebidos o servicios concurrentes.

4.8 Código completo para CLion

El siguiente ejemplo combina todas las operaciones presentadas y ejecuta una serie de pruebas básicas. Es un archivo main.c autocontenido listo para copiar en CLion u otro IDE.

#include <stdio.h>

#define CAPACIDAD 6

typedef struct {
  int datos[CAPACIDAD];
  int frente;
  int final;
  int cantidad;
} Cola;

void inicializar(Cola *cola) {
  cola->frente = 0;
  cola->final = 0;
  cola->cantidad = 0;
}

int esVacia(const Cola *cola) {
  return cola->cantidad == 0;
}

int esLlena(const Cola *cola) {
  return cola->final == CAPACIDAD;
}

int enqueue(Cola *cola, int valor) {
  if (esLlena(cola)) {
    return 0;
  }
  cola->datos[cola->final++] = valor;
  cola->cantidad++;
  return 1;
}

int dequeue(Cola *cola, int *valor) {
  if (esVacia(cola)) {
    return 0;
  }
  *valor = cola->datos[cola->frente++];
  cola->cantidad--;
  if (cola->frente == cola->final) {
    cola->frente = 0;
    cola->final = cola->cantidad;
  }
  return 1;
}

int peek(const Cola *cola) {
  if (esVacia(cola)) {
    return -1;
  }
  return cola->datos[cola->frente];
}

void imprimir(const Cola *cola) {
  printf("[cola] frente=%d final=%d cantidad=%d -> ", cola->frente, cola->final, cola->cantidad);
  for (int i = cola->frente; i < cola->final; ++i) {
    printf("%d ", cola->datos[i]);
  }
  puts("");
}

int main(void) {
  Cola cola;
  inicializar(&cola);

  enqueue(&cola, 10);
  enqueue(&cola, 20);
  enqueue(&cola, 30);
  imprimir(&cola);

  printf("Peek inicial: %d\n", peek(&cola));

  int valor;
  while (dequeue(&cola, &valor)) {
    printf("Dequeue: %d\n", valor);
    if (valor == 20) {
      enqueue(&cola, 99);
    }
  }

  imprimir(&cola);
  printf("Estado final vacio: %d\n", esVacia(&cola));

  return 0;
}

Este flujo permite observar cómo se comporta la cola cuando se encolan elementos después de haber desencolado y cómo reaccionan las funciones de estado.