Что такое очередь с приоритетом (Priority Queue)?

Ответ

Очередь с приоритетом — это абстрактный тип данных, в котором каждый элемент имеет "приоритет". Элементы извлекаются не в порядке поступления (FIFO), а в порядке убывания (или возрастания) приоритета.

Ключевые операции и их сложность (на основе двоичной кучи):

  • insert(element, priority) / enqueue(): O(log n)
  • extractMax() / extractMin() / dequeue(): O(log n)
  • peek() (посмотреть элемент с наивысшим приоритетом): O(1)

Реализация и пример на Java (используя PriorityQueue):

import java.util.PriorityQueue;

// Очередь с приоритетом по умолчанию (наименьший элемент - высший приоритет)
PriorityQueue<Task> taskQueue = new PriorityQueue<>();

// Предположим, класс Task реализует Comparable или мы передали Comparator
class Task implements Comparable<Task> {
    String name;
    int priority; // чем меньше число, тем выше приоритет

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

// Добавление задач
taskQueue.add(new Task("Критичный баг", 1));
taskQueue.add(new Task("Новая фича", 3));
taskQueue.add(new Task("Рефакторинг", 2));

// Извлечение в порядке приоритета
while (!taskQueue.isEmpty()) {
    Task nextTask = taskQueue.poll(); // Извлекает и удаляет голову очереди
    System.out.println(nextTask.name); // Выведет: Критичный баг, Рефакторинг, Новая фича
}

Типичные сценарии использования:

  • Планировщик задач в ОС.
  • Алгоритм Дейкстры для поиска кратчайшего пути в графе.
  • Обработка событий в системах реального времени.
  • Слияние K отсортированных списков (используя кучу минимальных элементов).

Реализация "под капотом" чаще всего основана на двоичной куче (binary heap), которая обеспечивает эффективность основных операций.

Ответ 18+ 🔞

Вот, слушай, объясняю на пальцах, что это за зверь такой — очередь с приоритетом. Представь себе обычную очередь в кассу, но тут подходит какой-то хитрая жопа с грудой купонов и его сразу пропускают вперёд, потому что у него «приоритет». Вот и вся суть. Не по принципу «кто первый пришёл — тот первый ушёл», а по принципу «у кого цифра в бирочке круче — тот и рулит». Ёпта, как в поликлинике, но только без бабулек.

Основные плюшки и сколько они отжирают времени (если делать на двоичной куче, а не на коленке):

  • insert(element, priority) / enqueue() — запихнуть элемент: O(log n). Не мгновенно, но и не овердохуища.
  • extractMax() / dequeue() — выдернуть самый важный элемент: O(log n). Примерно та же история.
  • peek() — просто посмотреть, какой там главный по тарелочкам висит: O(1). Мгновенно, потому что он всегда на виду, на самом верху кучи.

Как это выглядит в коде на Java (чтоб не изобретать велосипед, возьмём готовый PriorityQueue):

import java.util.PriorityQueue;

// Создаём очередь. По умолчанию она выдёргивает наименьший элемент первым (то есть, чем МЕНЬШЕ число, тем ВЫШЕ приоритет).
PriorityQueue<Task> taskQueue = new PriorityQueue<>();

// Допустим, у нас есть задачи. Чтобы очередь понимала, как их сравнивать, класс должен реализовать Comparable.
class Task implements Comparable<Task> {
    String name;
    int priority; // Чем меньше цифра, тем срочнее задача. 1 — это прям «пиздец горит».

    @Override
    public int compareTo(Task other) {
        // Сравниваем по полю priority.
        return Integer.compare(this.priority, other.priority);
    }
}

// Накидываем задач в кучу.
taskQueue.add(new Task("Критичный баг", 1));   // Этот вылезет первым
taskQueue.add(new Task("Новая фича", 3));      // Этот вылезет последним
taskQueue.add(new Task("Рефакторинг", 2));     // Этот — посерединке

// А теперь выгребаем их в правильном порядке.
while (!taskQueue.isEmpty()) {
    Task nextTask = taskQueue.poll(); // Метод poll() — выдерни и удали верхушку.
    System.out.println(nextTask.name); // Напечатает: "Критичный баг", потом "Рефакторинг", потом "Новая фича".
}

Где эта штука в жизни пригождается? Да везде, блядь:

  • Планировщик в операционке — решает, какому процессу дать процессорное время.
  • Алгоритм Дейкстры для поиска кратчайшего пути — там без неё вообще никуда.
  • Разные системы реального времени, где события прилетают пачками и их надо обрабатывать по срочности.
  • Слияние кучки отсортированных списков — тоже элегантно решается через такую очередь, ядрёна вошь.

А под капотом-то что? В 99% случаев там двоичная куча (binary heap). Это такое хитрое древовидное представление в обычном массиве, которое и даёт эти логарифмические сложности. Не идеально, но чертовски эффективно для большинства задач. Если, конечно, не пытаться запихнуть туда овердохуища элементов — но это уже совсем другая история.