Una cola de prioridad atiende primero al elemento con mayor prioridad (o menor coste, según la convención elegida) sin importar el orden de llegada. Mantiene la eficiencia del modelo FIFO en la interfaz pero introduce una relación de orden parcial que requiere estructuras internas específicas.
Se usa en planificadores, routers, simuladores de eventos discretos y algoritmos de grafos como Dijkstra o Prim, donde es vital extraer constantemente el próximo elemento más relevante.
insert o push: incorpora un elemento con su prioridad asociada.extract o pop: remueve el elemento con mejor prioridad, devolviendo el par valor-prioridad.peek: consulta el elemento ganador sin extraerlo.update: opcional; ajusta la prioridad de un elemento existente (decrease-key/increase-key).Para mantener complejidad logarítmica, la estructura interna suele ser un heap binario almacenado en arreglo continuo.
En C, el heap binario resulta sencillo de mantener usando índices y operaciones de intercambio.
En un heap almacenado en un array, el padre de un nodo en la posición i se encuentra en (i - 1) / 2 y los hijos en 2 * i + 1 y 2 * i + 2. El siguiente esqueleto funciona como max-heap (mayor prioridad implica mayor valor numérico):
#define CAP_HEAP 256
typedef struct {
int valor;
int prioridad;
} NodoPQ;
typedef struct {
NodoPQ datos[CAP_HEAP];
int cantidad;
} PriorityQueue;
void pq_init(PriorityQueue *pq) {
pq->cantidad = 0;
}
static void intercambiar(NodoPQ *a, NodoPQ *b) {
NodoPQ tmp = *a;
*a = *b;
*b = tmp;
}
static void sift_up(PriorityQueue *pq, int idx) {
while (idx > 0) {
int padre = (idx - 1) / 2;
if (pq->datos[idx].prioridad <= pq->datos[padre].prioridad) {
break;
}
intercambiar(&pq->datos[idx], &pq->datos[padre]);
idx = padre;
}
}
static void sift_down(PriorityQueue *pq, int idx) {
while (1) {
int hijoIzq = 2 * idx + 1;
int hijoDer = hijoIzq + 1;
int mayor = idx;
if (hijoIzq < pq->cantidad && pq->datos[hijoIzq].prioridad > pq->datos[mayor].prioridad) {
mayor = hijoIzq;
}
if (hijoDer < pq->cantidad && pq->datos[hijoDer].prioridad > pq->datos[mayor].prioridad) {
mayor = hijoDer;
}
if (mayor == idx) {
break;
}
intercambiar(&pq->datos[idx], &pq->datos[mayor]);
idx = mayor;
}
}
Los helpers sift_up y sift_down mantienen el invariante del heap tras cada inserción o extracción.
Insertar y extraer requieren validar la capacidad y reubicar nodos para preservar el orden de prioridad:
int pq_push(PriorityQueue *pq, int valor, int prioridad) {
if (pq->cantidad == CAP_HEAP) {
return 0;
}
int idx = pq->cantidad++;
pq->datos[idx].valor = valor;
pq->datos[idx].prioridad = prioridad;
sift_up(pq, idx);
return 1;
}
int pq_pop(PriorityQueue *pq, int *valor, int *prioridad) {
if (pq->cantidad == 0) {
return 0;
}
*valor = pq->datos[0].valor;
*prioridad = pq->datos[0].prioridad;
pq->datos[0] = pq->datos[pq->cantidad - 1];
pq->cantidad--;
sift_down(pq, 0);
return 1;
}
int pq_peek(const PriorityQueue *pq, int *valor, int *prioridad) {
if (pq->cantidad == 0) {
return 0;
}
*valor = pq->datos[0].valor;
*prioridad = pq->datos[0].prioridad;
return 1;
}
El costo amortizado se mantiene en O(log n) gracias a las funciones de sift. Si se necesitan prioridades mínimas se intercambia la comparación (> por <).
Algunos escenarios requieren usar criterios compuestos (por ejemplo, mayor prioridad numérica pero, en caso de empate, menor tiempo de llegada). En esos casos conviene encapsular la comparación en una función:
typedef struct {
int prioridad;
unsigned long llegada;
int valor;
} ElementoPQ;
static int cmp(const ElementoPQ *a, const ElementoPQ *b) {
if (a->prioridad != b->prioridad) {
return (a->prioridad > b->prioridad) ? 1 : -1;
}
return (a->llegada < b->llegada) ? 1 : -1;
}
El comparador define si la cola es estable. Guardar un contador de llegada facilita romper empates sin perder orden cronológico.
En todos los casos el diseño define si la prioridad es mutable y cómo se sincroniza el acceso concurrente.
El ejemplo siguiente crea una cola de prioridad de maxima prioridad numérica, inserta varias tareas y luego las procesa en orden:
#include <stdio.h>
#include <stdlib.h>
#define CAP_HEAP 64
typedef struct {
int valor;
int prioridad;
} NodoPQ;
typedef struct {
NodoPQ datos[CAP_HEAP];
int cantidad;
} PriorityQueue;
void pq_init(PriorityQueue *pq) {
pq->cantidad = 0;
}
static void swap(NodoPQ *a, NodoPQ *b) {
NodoPQ tmp = *a;
*a = *b;
*b = tmp;
}
static void sift_up(PriorityQueue *pq, int idx) {
while (idx > 0) {
int padre = (idx - 1) / 2;
if (pq->datos[idx].prioridad <= pq->datos[padre].prioridad) {
break;
}
swap(&pq->datos[idx], &pq->datos[padre]);
idx = padre;
}
}
static void sift_down(PriorityQueue *pq, int idx) {
while (1) {
int mayor = idx;
int hijoIzq = 2 * idx + 1;
int hijoDer = hijoIzq + 1;
if (hijoIzq < pq->cantidad && pq->datos[hijoIzq].prioridad > pq->datos[mayor].prioridad) {
mayor = hijoIzq;
}
if (hijoDer < pq->cantidad && pq->datos[hijoDer].prioridad > pq->datos[mayor].prioridad) {
mayor = hijoDer;
}
if (mayor == idx) {
break;
}
swap(&pq->datos[idx], &pq->datos[mayor]);
idx = mayor;
}
}
int pq_push(PriorityQueue *pq, int valor, int prioridad) {
if (pq->cantidad == CAP_HEAP) {
return 0;
}
int idx = pq->cantidad++;
pq->datos[idx].valor = valor;
pq->datos[idx].prioridad = prioridad;
sift_up(pq, idx);
return 1;
}
int pq_pop(PriorityQueue *pq, int *valor, int *prioridad) {
if (pq->cantidad == 0) {
return 0;
}
*valor = pq->datos[0].valor;
*prioridad = pq->datos[0].prioridad;
pq->datos[0] = pq->datos[pq->cantidad - 1];
pq->cantidad--;
sift_down(pq, 0);
return 1;
}
void imprimir(const PriorityQueue *pq) {
printf("[pq] cantidad=%d -> ", pq->cantidad);
for (int i = 0; i < pq->cantidad; ++i) {
printf("(%d,%d) ", pq->datos[i].valor, pq->datos[i].prioridad);
}
puts("");
}
int main(void) {
PriorityQueue pq;
pq_init(&pq);
pq_push(&pq, 100, 5);
pq_push(&pq, 20, 1);
pq_push(&pq, 300, 10);
pq_push(&pq, 40, 3);
imprimir(&pq);
int valor, prioridad;
while (pq_pop(&pq, &valor, &prioridad)) {
printf("Atendiendo valor=%d prioridad=%d\n", valor, prioridad);
}
return 0;
}
Modifica los valores de prioridad y observa cómo el montículo reorganiza los elementos para mantener siempre el máximo en la raíz.