PriorityQueue (front): Dátová štruktúra priority queue v Jave

Prioritné fronty prinášajú poriadok tam, kde nestačí obyčajné FIFO – od real‑time simulácií až po plánovanie úloh. V tejto časti seriálu o dátových štruktúrach ti, ako môžeš efektívne použiť triedu PriorityQueue z balíka java.until. Prevedieme ťa konštruktormi, metódami aj úskaliami synchronizácie a chýbať nebude ani názorný príklad z nemocničnej praxe.

Mladý programátor píše v prostredí Java kód s využitím PriorityQueue.
Práca s prioritnou frontou v kóde

V článku sa dozvieš:

    Zatiaľ čo priority queue označuje abstraktný dátový typ, ktorý spracúva prvky podľa priority, PriorityQueue je konkrétna trieda v Jave, ktorú použiješ na jeho implementáciu. Dátová štruktúra PriorityQueue, ktorá umožňuje efektívne spracovanie prvkov vo fronte podľa ich priority, resp. nami definovaného poradia.

    PriorityQueue kolekcia – hierarchia dedičnosti

    PriorityQueue kolekcia – hierarchia dedičnosti (zdroj)

    Odporúčame ti…

    Java PriorityQueue je jednou z množstva dátových štruktúr na jednoduchšiu prácu s dátami. V našom seriáli si postupne predstavíme aj rôzne ďalšie dátové štruktúry. Ukážeme si, ako s nimi pracovať, aké operácie sa s nimi dajú robiť, aké sú najčastejšie používané metódy. Spomenieme výhody a nevýhody a kedy je vhodné konkrétnu dátovú štruktúru použiť.

    PriorityQueue v Java – predstavenie dátovej štruktúry

    Dátová kolekcia PriorityQueue (k dispozícii od Java 1.5) je implementáciou rozhrania Queue, ktorá predstavuje abstraktný dátový typ podobný fronte. Rozdiel spočíva v algoritme odstraňovania prvkov.

    Zatiaľ čo klasická fronta pracuje na princípe FIFO (First In, First out) a prvky sa pridávajú na koniec fronty a odstraňujú na začiatku, v PriorityQueue sú prvky uložené podľa ako už názov dátovej štruktúry napovedá podľa priority. Najskôr sa vždy odstráni prvok s najvyššou prioritou, pred ostatnými s nižšou prioritou. Ak existuje viacero prvkov s najvyššou prioritou, jeden z týchto prvkov sa ľubovoľne vyberie ako najmenší prvok.

    Interná štruktúra je binárna halda (binary heap), čo zabezpečuje, že prístup k prvku s najvyšším prioritou (metóda peek() alebo poll()) trvá O(1), zatiaľ čo vkladanie a odstraňovanie v čase O(log n). Podľa potreby môžeme použiť prirodzené poradie prvkov (ak implementujú Comparable), alebo vlastný Comparator, ktorý definuje prioritu.

    Nulové prvky nie sú povolené, rovnako sa neumožňuje vkladanie prvkov neporovnateľných prvkov, takých, kde sa nedá určiť priorita.

    Kolekcia PriorityQueue nie je synchronizovaná. To znamená, že nie bezpečná pre viacvláknové spracovanie. Na prácu s viacerými vláknami poskytuje Java triedu PriorityBlockingQueue, ktorá implementuje rozhranie BlockingQueue.

    PriorityQueue – konštruktory

    Pre vytvorenie inštancie PriorityQueue máme k dispozícii niekoľko konštruktorov:

    1. Prázdna PriorityQueue
      Vytvorí PriorityQueue s predvolenou počiatočnou kapacitou (11), ktorá zoradí svoje prvky podľa ich prirodzeného usporiadania.

      PriorityQueue()
    2. PriorityQueue s danou počiatočnou kapacitou
      Vytvorí PriorityQueue so zadanou počiatočnou kapacitou, ktorá usporiada svoje prvky podľa ich prirodzeného usporiadania.

      PriorityQueue(int initialCapacity)
    3. PriorityQueue s kapacitou a Comparatorom
      Vytvorí PriorityQueue so zadanou počiatočnou kapacitou, ktorá usporiada svoje prvky podľa zadaného porovnávača.

      PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
    4. PriorityQueue skonštruovaná z existujúcej kolekcie
      Vytvorí PriorityQueue obsahujúca prvky zo zadanej kolekcie.

      PriorityQueue(Collection<? extends E> c)
    5. PriorityQueue s Comparatorom
      Vytvorí PriorityQueue s predvolenou počiatočnou kapacitou, ktorej prvky sú zoradené podľa zadaného komparátora.

      PriorityQueue(Comparator<? super E> comparator)
    6. PriorityQueue skonštruovaná z inej PriorityQueue
      Vytvorí PriorityQueue, ktorá bude obsahovať prvky v zadanej prioritnej fronte.

      PriorityQueue(PriorityQueue<? extends E> c)
    7. PriorityQueue skonštruovaná zo SortedSet
      Vytvorí PriorityQueue, ktorá bude obsahovať prvky prvky v zadanej zoradenej množine.

      PriorityQueue(SortedSet<? extends E> c)

    PriorityQueue – základné operácie

    Medzi základné operácie, ktoré môžeme vykonávať s PriorityQueue, patria:

    • Vkladanie prvkov
      Pomocou metód add(E e) alebo offer(E e) sa vloží prvok s danou prioritou. Prvá metóda vyvolá výnimku, ak je front plný, druha vráti v rovnakom prípade false.
    • Získanie referencie prvku s najvyššou prioritou
      Metóda peek() vráti prvok s najvyššou prioritou bez odstránenia, null ak prázdna.
    • Získanie a odstránenie prvku s najvyššou prioritou
      Metóda poll() odstráni a vráti prvok s najvyššou prioritou, null ak prázdna.
    • Odstraňovanie prvkov
      Metóda remove(Object o) sa používa na odstránenie konkrétneho prvku z PriorityQueue.
    • Vyprázdnenie kolekcie
      Metóda clear() odstráni všetky prvky z PriorityQueue kolekcie.
    • Vyhľadávanie prvkov podľa kľúča alebo hodnoty
      Metóda containsKey(Object o) zisťuje, či sa zadaný prvok nachádza v kolekcii.
    • Zistenie veľkosti kolekcie
      Metóda size() vráti počet záznamov (objektov) danej PriorityQueue.
    • Iterácia cez prvky
      Pri iterácii cez PriorityQueue poradie prvkov nie je garantované.

    PriorityQueue – dokumentácia

    Kompletný prehľad metód triedy PriorityQueue, nájdeš v oficiálnej dokumentácii

    PriorityQueue – výhody a nevýhody

    Výhody PriorityQueue

    • Efektívny prístup k prvkom s najvyššou prioritou.
      PriorityQueue umožňuje rýchly prístup k elementom s najvyššou prioritou – peek() v O(1).
    • Flexibilita pri stanovení priority
      PriorityQueue nám dáva možnosť definovať vlastný Comparator, alebo použitie prirodzeného poradia.
    • Logaritmický čas operácií
      Základné operácie s prvkami (pridávanie, odstraňovanie, vyhľadávanie) prebiehajú v čase O(log n).

    Nevýhody PriorityQueue

    • Poradie prvkov pri iterácii nie je garantované
      Pri iterácii kvôli internej reprezentácii prvky nie sú zoradené.
    • Nie je synchronizovaná
      A teda nie je vhodná pre multivláknové spracovanie.
    • Vyžaduje definovanie porovnateľnosti
      Prvky v PriorityQueue musia implementovať rozhranie Comparable alebo musí byť zadaný vhodný Comparator.

    PriorityQueue – kedy ho použiť a kedy nie?

    PriorityQueue je ideálny v prípadoch, keď:

    • Potrebujeme plánovať úlohy podľa priority (task scheduling).
    • Pri simulovaní udalostí (event simulation).
    • Hodia sa pre algoritmy grafov a real-time systémy

    Príklad použitia PriorityQueue v Jave

    Teraz si ukážeme, ako prakticky pracovať s kolekciou PriorityQueue v Java. Dátová štruktúra PriorityQueue sa vynikajúco hodí pre simulovanie udalostí, resp. programy, ktoré vyžadujú spracovanie prvkov podľa priority.

    Dnes si naprogramujeme realistický simulátor nemocnice. Do nemocnice prichádzajú pacienti (alebo sú privezení sanitkou). Každý pacient sa najskôr v nemocnici rýchlo vyšetrí a určí sa závažnosť jeho stavu. Stav pacientov sa ohodnotí od 1 až 10, kde 10 je život ohrozujúci stav a 1, kde pacient je takmer zdravý.

    Pacientov po vyšetrení umiestnime do PriorityQueue (prioritná fronta na liečbu), kde budú čakať, kým sa dostanú na rad. Lekári vždy začnú liečiť pacienta s najvyššou závažnosťou, takéhoto pacienta označíme na konci liečby ako vyliečeného. Aby sme simulátor nemocnice spravili trochu realistický, tak pacientom so stavom 5 až 10 sa v každom ťahu (liečba iného pacienta) zhorší stav o +1. Ak mal pacient kriticky stav 10 a nedostal sa prioritne na vyšetrenie, zomiera. Priebeh simulácie vypíšeme prehľadne na konzolu.

    Patient.java

    import java.util.Random;
    
    public class Patient {
        private final String name;
        private int severity; // Severity from 1 (lowest) to 10 (highest)
    
        public Patient(String name) {
            this.name = name;
            this.severity = examine();
        }
    
        // Simulate examination by assigning random severity between 1 and 10
        private int examine() {
            Random random = new Random();
            return random.nextInt(10) + 1;
        }
    
        public String getName() {
            return name;
        }
    
        public int getSeverity() {
            return severity;
        }
    
        public void increaseSeverity() {
            severity++;
        }
    
        @Override
        public String toString() {
            return name + " (severity: " + severity + ")";
        }
    }

    HospitalSimulation.java

    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    import java.util.PriorityQueue;
    
    public class HospitalSimulation {
        private final PriorityQueue<Patient> triageQueue;
    
        public HospitalSimulation() {
            // Create a PriorityQueue sorting patients from highest severity to lowest
            this.triageQueue = new PriorityQueue<>(Comparator.comparingInt(Patient::getSeverity).reversed());
        }
    
        // Add patients to the queue
        public void admitPatients(Patient[] patients) {
            System.out.println("🚑 Admitting patients...");
            for (Patient patient : patients) {
                triageQueue.add(patient);
                System.out.println("✅ Admitted: " + patient);
            }
        }
    
        // Treat patients based on their severity
        public void treatPatients() {
            System.out.println("\n🏥 Starting treatment...");
            while (!triageQueue.isEmpty()) {
                Patient currentPatient = triageQueue.poll(); // Remove patient with the highest severity
                System.out.println("👨‍⚕️ Treating: " + currentPatient);
                simulateTreatment();
                System.out.println("✅ " + currentPatient.getName() + " has been treated.\n");
    
                // After treating a patient, increase the severity of waiting patients
                worsenWaitingPatients();
            }
            System.out.println("🎉 All patients have been treated or deceased!");
        }
    
        // Increase severity for waiting patients, handle deaths
        private void worsenWaitingPatients() {
            if (triageQueue.isEmpty()) {
                return;
            }
    
            List<Patient> updatedPatients = new ArrayList<>();
            List<Patient> deceasedPatients = new ArrayList<>();
    
            while (!triageQueue.isEmpty()) {
                Patient patient = triageQueue.poll();
                if (patient.getSeverity() >= 5) {
                    patient.increaseSeverity();
                    if (patient.getSeverity() > 10) {
                        deceasedPatients.add(patient); // Patient dies
                        continue;
                    }
                }
                updatedPatients.add(patient);
            }
    
            // Rebuild the queue with updated patients
            triageQueue.addAll(updatedPatients);
    
            // Report deaths
            for (Patient deceased : deceasedPatients) {
                System.out.println("⚰️ Patient " + deceased.getName() + " has died while waiting (severity exceeded 10).");
            }
        }
    
        // Simulate the time taken to treat a patient
        private void simulateTreatment() {
            try {
                Thread.sleep(500); // Simulate short delay (500ms)
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    
        // Entry point
        public static void main(String[] args) {
            // Create hospital simulation instance
            HospitalSimulation hospital = new HospitalSimulation();
    
            // Create 10 patients arriving at the hospital
            Patient[] patients = {
                    new Patient("Alena"),
                    new Patient("Beáta"),
                    new Patient("Ctibor"),
                    new Patient("Daniel"),
                    new Patient("Elena"),
                    new Patient("František"),
                    new Patient("Gregor"),
                    new Patient("Helena"),
                    new Patient("Igor"),
                    new Patient("Jana")
            };
    
            // Simulate admitting and treating patients
            hospital.admitPatients(patients);
            hospital.treatPatients();
        }
    }

    Výstup z tohto príkladu môže vyzerať nasledovne:

    Konzolový výstup programu s použitím PriorityQueue v Jave.
    Výsledok práce s prioritnou frontou

    Pripravili sme pre teba súbory so spomínaným príkladom vo forme kódu, ktorý si môžeš spustiť priamo v Jave. Stiahni si Java kód pre PriorityQueue tu.

    O autorovi

    Jozef Wagner

    Java Developer Senior

    Viac ako 10 rokov programujem v Jave, momentálne pracujem v msg life Slovakia ako Java programátor senior a pomáham zákazníkom implementovať ich požiadavky do poistného softvéru Life Factory. Vo voľnom čase si rád oddýchnem v lese, prípadne si zahrám nejakú dobrú počítačovú hru.

    Daj nám o sebe vedieť