Cuando trabajamos con colas en C, una de las aplicaciones prácticas más claras es la gestión de pedidos que llegan de manera secuencial y deben procesarse en orden de llegada para garantizar justicia y eficiencia. Imaginemos que solo podemos atender un pedido a la vez, por ejemplo, porque la pasarela de pagos es un recurso que no puede compartirse simultáneamente y tarda un tiempo en procesar cada operación. En este contexto, implementar una cola nos permite mantener un registro ordenado de los pedidos y procesarlos uno a uno.
Para empezar, definimos una estructura pedido que contiene la información mínima necesaria, como un identificador del cliente y una lista enlazada con los productos solicitados. A partir de ahí, construimos la estructura de la cola usando nodos que apuntan a estos pedidos. Cada nodo tiene un puntero al pedido que contiene y otro al siguiente nodo en la cola, formando así una lista enlazada.
La estructura de la cola en sí misma mantiene dos punteros fundamentales: uno al primer nodo y otro al último. Esto es crucial porque, al insertar nuevos pedidos, siempre los añadimos al final, y tener un puntero directo al último nodo evita tener que recorrer toda la lista para encontrar dónde insertar.
Para manejar la cola, implementamos varias operaciones básicas. Primero, la creación y destrucción de nodos, que se realiza con malloc y free para gestionar la memoria dinámica. Al crear un nodo, le asignamos el pedido correspondiente y establecemos su puntero siguiente a NULL. Al destruirlo, nos aseguramos de limpiar correctamente la memoria y evitar punteros colgantes.
Luego, creamos la cola en sí, reservando memoria para ella y estableciendo sus punteros primer y ultimo a NULL, indicando que está vacía. Para comprobar si la cola está vacía, simplemente verificamos si primer es NULL.
Cuando queremos encolar un pedido, primero creamos un nodo nuevo con ese pedido. Si la cola está vacía, asignamos tanto primer como ultimo a ese nodo, ya que es el único elemento. Si no está vacía, enlazamos el nuevo nodo al final de la cola actualizando el puntero siguiente del último nodo y luego actualizamos el puntero ultimo para que apunte al nuevo nodo. Esto mantiene la integridad de la estructura y asegura que siempre podamos acceder rápidamente al final de la cola.
Para consultar el pedido que está en la cabeza de la cola, simplemente devolvemos el pedido apuntado por primer, siempre que la cola no esté vacía. Si está vacía, devolvemos NULL para indicar que no hay elementos.
Eliminar el primer elemento de la cola es un poco más delicado. Primero comprobamos que la cola no esté vacía. Si hay un nodo, lo almacenamos temporalmente, actualizamos primer para que apunte al siguiente nodo, y luego liberamos la memoria del nodo eliminado. Un detalle importante es que si, tras eliminar el nodo, la cola queda vacía (es decir, primer es NULL), también debemos actualizar ultimo a NULL para evitar que apunte a memoria liberada, manteniendo así la cola en un estado consistente.
Para destruir completamente la cola, no basta con liberar la estructura de la cola, sino que debemos eliminar todos los nodos que contiene. Esto se logra eliminando repetidamente el primer elemento hasta que la cola quede vacía, y luego liberamos la memoria de la cola en sí. Así evitamos pérdidas de memoria y aseguramos una gestión correcta.
Una operación que algunos prefieren es combinar la consulta y eliminación en una sola función, a la que podríamos llamar despachar. Esta función recupera el pedido en la cabeza de la cola y lo elimina al mismo tiempo, devolviendo el pedido para que pueda ser procesado inmediatamente. Si la cola está vacía, devuelve NULL.
Además de estas operaciones básicas, podemos implementar funcionalidades adicionales, como contar cuántos pedidos hay en la cola para monitorizar la carga de trabajo, o eliminar pedidos que no estén en la cabeza, por ejemplo, si un cliente decide cancelar su pedido antes de que se procese.
A continuación, mostramos un ejemplo básico de cómo podría implementarse la función para encolar un pedido en la cola:
typedef struct Nodo {
Pedido *pedido;
struct Nodo *siguiente;
} Nodo;
typedef struct Cola {
Nodo *primer;
Nodo *ultimo;
} Cola;
Nodo* crearNodo(Pedido *pedido) {
Nodo *nuevo = (Nodo*) malloc(sizeof(Nodo));
if (nuevo) {
nuevo->pedido = pedido;
nuevo->siguiente = NULL;
}
return nuevo;
}
void encolar(Cola *cola, Pedido *pedido) {
Nodo *nuevo = crearNodo(pedido);
if (!nuevo) return; // Manejar error de malloc
if (cola->primer == NULL) {
cola->primer = nuevo;
cola->ultimo = nuevo;
} else {
cola->ultimo->siguiente = nuevo;
cola->ultimo = nuevo;
}
}
Y para eliminar el primer elemento de la cola:
void eliminarCabeza(Cola *cola) {
if (cola->primer == NULL) return;
Nodo *eliminado = cola->primer;
cola->primer = eliminado->siguiente;
if (cola->primer == NULL) {
cola->ultimo = NULL;
}
free(eliminado);
}
Con estas bases, podemos construir una cola dinámica en C que gestione pedidos de forma eficiente y justa, asegurándonos de manejar correctamente la memoria y mantener la integridad de la estructura en todo momento.