Ответ
Очередь (queue) в C++ — это контейнер-адаптер из STL, работающий по принципу FIFO (First In, First Out). Реализована как обертка над другими контейнерами, по умолчанию используется deque.
Основные операции:
push()— добавление элемента в конец очереди (enqueue).pop()— удаление элемента из начала очереди (dequeue).front()— доступ к первому элементу (тому, который будет извлечен следующим).back()— доступ к последнему добавленному элементу.empty()— проверка на пустоту.size()— получение количества элементов.
Пример использования std::queue:
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
// Добавление элементов
q.push(10); // Очередь: [10]
q.push(20); // Очередь: [10, 20]
q.push(30); // Очередь: [10, 20, 30]
std::cout << "Первый элемент (front): " << q.front() << std::endl; // 10
std::cout << "Последний элемент (back): " << q.back() << std::endl; // 30
// Удаление элементов
q.pop(); // Удаляем 10
std::cout << "После pop(), front: " << q.front() << std::endl; // 20
std::cout << "Размер очереди: " << q.size() << std::endl; // 2
// Очистка очереди
while (!q.empty()) {
std::cout << q.front() << " ";
q.pop();
}
// Вывод: 20 30
return 0;
}
Особенности реализации:
- Базовый контейнер: Можно указать при создании:
std::queue<int, std::list<int>> q_list; // На основе list std::queue<int, std::deque<int>> q_deque; // На основе deque (по умолчанию) // std::queue<int, std::vector<int>> q_vec; // Ошибка: vector не имеет pop_front() - Ограничения:
- Нет прямого доступа к элементам кроме первого и последнего
- Нет итераторов
- Для обхода очереди нужно последовательно извлекать элементы
- Временная сложность: Все операции O(1) при использовании
dequeилиlistв качестве базового контейнера.
Типичные сценарии использования:
- Обработка задач в порядке поступления
- Реализация BFS (поиска в ширину) для графов
- Буферизация данных в producer-consumer паттернах
- Обработка сообщений в системах очередей
Ручная реализация на связном списке:
template<typename T>
class ListQueue {
private:
struct Node {
T data;
Node* next;
Node(T val) : data(val), next(nullptr) {}
};
Node* frontNode;
Node* rearNode;
int count;
public:
ListQueue() : frontNode(nullptr), rearNode(nullptr), count(0) {}
~ListQueue() {
while (!isEmpty()) {
dequeue();
}
}
void enqueue(T value) {
Node* newNode = new Node(value);
if (isEmpty()) {
frontNode = rearNode = newNode;
} else {
rearNode->next = newNode;
rearNode = newNode;
}
count++;
}
T dequeue() {
if (isEmpty()) throw std::runtime_error("Queue is empty");
Node* temp = frontNode;
T value = temp->data;
frontNode = frontNode->next;
if (frontNode == nullptr) {
rearNode = nullptr;
}
delete temp;
count--;
return value;
}
T front() const {
if (isEmpty()) throw std::runtime_error("Queue is empty");
return frontNode->data;
}
bool isEmpty() const { return frontNode == nullptr; }
int size() const { return count; }
}; Ответ 18+ 🔞
Давай разберем эту штуку, которая называется очередью, или queue, если по-ихнему. Представь себе самую обычную очередь, ну, как в столовой или за пивом. Кто первый пришёл — тот первый и обслужен. Это и есть принцип FIFO, или, как я люблю говорить, «первый зашёл — первый вышел, а последний пусть ждёт, пока не сдохнет». Ёпта, логика проще пареной репы.
Что она умеет, эта queue? Вот основные её фокусы, их всего ничего:
push()— запихнуть элемент в конец очереди. Встал в хвост и терпи.pop()— выдернуть элемент из начала. Подошёл к окошку — получил и свободен.front()— посмотреть, кто там следующий на выход, но не трогать его. Просто подглядеть.back()— увидеть, кто самый последний тормоз в этой очереди.empty()— проверить, а не пустая ли тут у нас очередь, может, все уже разошлись.size()— посчитать, сколько народу ещё томится в ожидании.
Вот смотри, как это в коде выглядит, чтоб совсем понятно стало:
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
// Закидываем народ в очередь
q.push(10); // Стоит один [10]
q.push(20); // К нему пристроился [10, 20]
q.push(30); // И ещё один бедолага [10, 20, 30]
// Смотрим, кто первый
std::cout << "Первый на вылет (front): " << q.front() << std::endl; // 10
// Смотрим, кто последний
std::cout << "Последний пришёл (back): " << q.back() << std::endl; // 30
// Обслужили первого — пока, 10!
q.pop();
std::cout << "После pop(), теперь front: " << q.front() << std::endl; // 20
std::cout << "Сколько всего осталось: " << q.size() << std::endl; // 2
// А теперь разгребаем очередь до конца, пока не опустеет
while (!q.empty()) {
std::cout << q.front() << " "; // Смотрим, кого выгоняем
q.pop(); // И выгоняем
}
// Напечатает: 20 30
// Всё, очередь накрылась медным тазом.
return 0;
}
А теперь подводные камни, блядь:
- Из чего она сделана: По умолчанию она внутри использует
deque(двустороннюю очередь), но можно сказать ей использоватьlist. А вотvectorне подойдёт — у него нетpop_front(), он как тот пидарас шерстяной, только назад умеет, а спереди выковыривать — нет.std::queue<int, std::list<int>> q_list; // На основе списка // std::queue<int, std::vector<int>> q_vec; // Ошибка! Нельзя, сука! - Чего НЕЛЬЗЯ: Нельзя тыкать пальцем в середину очереди и говорить «эй ты, пятый, выходи». Доступ только к первому и последнему. Итераторов тоже нет — нечего там шастать. Чтобы всех посмотреть, надо всех по одному выпнуть.
- Скорость: Все основные операции — O(1), то есть мгновенно. Если, конечно, внутри не какой-нибудь мудя контейнер.
Где это нужно? Ну, задачи в планировщике, поиск в ширину по графу (BFS), всякие буферы, где что-то производится, а что-то потребляется — в общем, везде, где нужен честный порядок «живой очереди».
А вот если вдруг захочется сделать свою очередь, на связном списке, например:
// ... (код ручной реализации остаётся без изменений, как в исходнике)
Смотри, тут всё просто: создаём узел, цепляем в хвост, с головы снимаем. Главное — не забыть всё почистить в деструкторе, а то память потечёт, как из дырявого ведра.