¿Cómo gestionar prioridades con java.util.PriorityQueue?

| Última modificación: 16 de diciembre de 2024 | Tiempo de Lectura: 3 minutos

Algunos de nuestros reconocimientos:

Premios KeepCoding

Te hago un spoiler en caso de que aún no lo hayas vivido. Resulta que al trabajar con estructuras de datos en Java, sí o sí te encontrarás con situaciones donde no basta con procesar elementos en el orden en que fueron añadidos. Afortunadamente, java.util.PriorityQueue llegó para organizar los elementos según una prioridad definida. Te será muy útil en esos problemas donde ciertos datos deben ser atendidos antes que otros.

java.util.PriorityQueue qué es

¿Qué es java.util.PriorityQueue?

La java.util.PriorityQueue es una estructura de datos basada en un heap que ordena sus elementos según un criterio predefinido. Por defecto, usa el orden natural de los elementos (como números de menor a mayor), aunque puedes personalizar esto con un Comparator.

Características

  1. Orden basado en prioridad: Los elementos no se organizan por orden de inserción, sino por su prioridad.
  2. Operaciones rápidas: Las operaciones básicas como agregar (add()) y eliminar (poll()) tienen una complejidad de O(log n).
  3. No permite valores nulos: Intentar añadir un null lanza una NullPointerException.
  4. Es dinámica: Su tamaño se ajusta automáticamente a medida que se agregan o eliminan elementos.
  5. No es segura para múltiples hilos: Para entornos concurrentes, es mejor usar PriorityBlockingQueue.

¿Cómo usar java.util.PriorityQueue? Ejemplos prácticos

Quiero usar varios ejemplos para explicarte bien cómo puedes implementar y trabajar con esta estructura y que aprendas a programar con Java como se debe:

Crea una PriorityQueue básica

import java.util.PriorityQueue;

public class Main {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>();

// Agregar elementos
queue.add(5);
queue.add(1);
queue.add(3);

// Ver el elemento con mayor prioridad (el menor)
System.out.println("Elemento con mayor prioridad: " + queue.peek());

// Eliminar el elemento con mayor prioridad
System.out.println("Eliminado: " + queue.poll());

// Imprimir la cola
System.out.println("Cola actual: " + queue);
}
}

La salida debe aparecerte así:

Elemento con mayor prioridad: 1
Eliminado: 1
Cola actual: [3, 5]

Puedes darte cuenta como los números se organizan automáticamente de menor a mayor. El número 1 tiene la mayor prioridad y se procesa primero.

Usa PriorityQueue con un Comparator

También puedes personalizar la prioridad, solo tienes que usar un Comparator.

Por ejemplo, aquí te muestro cómo configurar la cola para que los números más grandes tengan la mayor prioridad:

import java.util.PriorityQueue;
import java.util.Comparator;

public class Main {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>(Comparator.reverseOrder());

// Agregar elementos
queue.add(5);
queue.add(1);
queue.add(3);

// Ver el elemento con mayor prioridad
System.out.println("Elemento con mayor prioridad: " + queue.peek());

// Imprimir la cola
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}

Observa la salida:

Elemento con mayor prioridad: 5
5
3
1

Aplicaciones prácticas

Lo bueno es que java.util.PriorityQueue se utiliza en muchos escenarios del mundo real, como estos que te explico aquí:

a) Planificación de tareas

Imagina que tienes tareas con diferentes prioridades. Las tareas más importantes deben ejecutarse primero.

import java.util.PriorityQueue;

class Task implements Comparable<Task> {
String name;
int priority;

Task(String name, int priority) {
this.name = name;
this.priority = priority;
}

@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}

@Override
public String toString() {
return name + " (Prioridad: " + priority + ")";
}
}

public class Main {
public static void main(String[] args) {
PriorityQueue<Task> tasks = new PriorityQueue<>();

tasks.add(new Task("Hacer backup", 3));
tasks.add(new Task("Actualizar sistema", 1));
tasks.add(new Task("Revisar logs", 2));

while (!tasks.isEmpty()) {
System.out.println("Ejecutando: " + tasks.poll());
}
}
}

Mira esta salida:

Ejecutando: Actualizar sistema (Prioridad: 1)
Ejecutando: Revisar logs (Prioridad: 2)
Ejecutando: Hacer backup (Prioridad: 3)

b) Algoritmos de búsqueda

Por si fuera poco, en algoritmos como Dijkstra o Prim, se utiliza una PriorityQueue para gestionar los nodos pendientes de visitar según su peso.

Consejos a la hora de usar java.util.PriorityQueue

  1. Define prioridades claras: Te aconsejo usar Comparable en tus clases o un Comparator para establecer cómo se ordenarán los elementos, especialmente si son objetos complejos.
  2. Evita valores nulos: No añadas null a la cola, ya que esto lanzará una NullPointerException. Mejor asegúrate de validar los elementos antes de insertarlos.
  3. Usa estructuras concurrentes si es necesario: Si trabajas en entornos multihilo, PriorityBlockingQueue te servirá más para evitar problemas de sincronización.
  4. Elige un tamaño inicial adecuado: Si conoces la cantidad de elementos, define una capacidad inicial para optimizar el rendimiento.
  5. Verifica el criterio de ordenación: Asegúrate de que el orden definido cumple con tus expectativas para evitar resultados inesperados.

Creo que ya te quedó claro que java.util.PriorityQueue es muy buena opción para gestionar datos según prioridades. Ahorrarás mucho tiempo, ya sea organizando tareas o resolviendo problemas complejos.

¡Grandes noticias para ti! En el Bootcamp de Java Full Stack de KeepCoding, aprenderás a dominar estructuras como PriorityQueue y muchas más. Transforma tu carrera y conviértete en un desarrollador experto en tiempo récord. ¡No esperes más, da el salto hoy mismo!

Ramón Maldonado

Full Stack Developer y Responsable de Formación base en KeepCoding.

Posts más leídos

¡CONVOCATORIA ABIERTA!

Java y Spring Boot

Full Stack Bootcamp

Clases en Directo | Profesores en Activo | Temario 100% actualizado