Структура данных - очередь
Очередь — это линейная структура данных, которая следует принципу «первым пришёл — первым вышел» (FIFO). Её можно сравнить с реальной ситуацией очереди за билетами — кто встал в очередь первым, того и обслуживают первым.
Понятие и принцип очереди
голова
хвост
enqueue: вставка в хвост | dequeue: удаление из головы | Сложность O(1)
Проверка FIFO: enqueue 10→20→30, dequeue 10→20→30 (первым пришёл — первым вышел)
Очередь позволяет вставку только с одного конца (в хвост) и удаление с другого конца (из головы).
Это прямо противоположно принципу LIFO стека.
Реализация очереди с помощью массива
Пример
#include <stdbool.h>
#define MAX 100
struct Queue {
int items[MAX];
int front; /* указатель front: указывает на первый элемент */
int rear; /* указатель rear: указывает на следующую позицию вставки */
};
void initQueue(struct Queue* q) {
q->голова = 0;
q->хвост = 0;
}
bool isEmpty(struct Queue* q) {
return q->front == q->rear;
}
bool isFull(struct Queue* q) {
return q->rear == MAX;
}
/* enqueue: добавить элемент в rear */
void enqueue(struct Queue* q, int value) {
if (isFull(q)) {
printf("Очередь заполнена!"\n");
return;
}
q->items[q->rear++] = value; /* записываем в rear, затем сдвигаем rear назад */
printf("enqueue: %d\n", value);
}
/* dequeue: удалить элемент из начала */
int dequeue(struct Queue* q) {
if (isEmpty(q)) {
printf("Очередь пуста!"\n");
return -1;
}
return q->items[q->front++]; /* вернуть элемент из начала, затем сдвинуть front назад */
}
int main() {
struct Queue q;
initQueue(&q);
enqueue(&q, 10); /* enqueue: 10 */
enqueue(&q, 20); /* enqueue: 20 */
enqueue(&q, 30); /* enqueue: 30 */
printf("dequeue: %d\n", dequeue(&q)); /* вывод: dequeue: 10 (FIFO) */
printf("dequeue: %d\n", dequeue(&q)); /* вывод: dequeue: 20 */
printf("dequeue: %d\n", dequeue(&q)); /* вывод: dequeue: 30 */
return 0;
}
Очередь, реализованная с помощью обычного массива, имеет проблему "ложного переполнения": по мере того как enqueue и dequeue продолжаются, front и rear продолжают смещаться к концу массива, и даже если в начале массива есть много пустого места, его нельзя использовать повторно. Решение — использоватькольцевую очередь。
кольцевую очередь
Ключевые формулы для кольцевой очереди
enqueue: items[rear] = value; rear = (rear + 1) % MAX;
dequeue: value = items[front]; front = (front + 1) % MAX;
Проверка на пустоту: front == rear | Проверка на полноту: (rear + 1) % MAX == front
Пример работы (ёмкость=5, максимум 4 элемента): enqueue 10→20→30→40, dequeue 10→20, enqueue 50→60 → 60 сохраняется по индексу 0 (циклический переход), идеально повторно используя освобождённое пространство.
Кольцевая очередь логически рассматривает массив как кольцевую структуру, в которой голова и хвост соединены, и реализует зацикливание указателей с помощью модульной арифметики.
Пример
#include <stdbool.h>
#define MAX 5 /* намеренно задана маленькая ёмкость, чтобы эффект зацикливания было легче наблюдать */
struct CircularQueue {
int items[MAX];
int front; /* индекс front */
int rear; /* индекс rear (следующая позиция вставки) */
};
void initQueue(struct CircularQueue* q) {
q->голова = 0;
q->хвост = 0;
}
/* проверка на пустоту: front и rear совпадают */
bool isEmpty(struct CircularQueue* q) {
return q->front == q->rear;
}
/* проверка на полноту: (rear + 1) % MAX == front Примечание: в кольцевой очереди намеренно оставляют одну позицию пустой, чтобы отличать пустое состояние от полного */
/* enqueue: записать в rear, затем переместить rear по кругу вперёд */
bool isFull(struct CircularQueue* q) {
return (q->rear + 1) % MAX == q->голова;
}
"Кольцевая очередь заполнена!
void enqueue(struct CircularQueue* q, int value) {
if (isFull(q)) {
printf(/* остаток по модулю для циклического перехода */\n");
return;
}
q->items[q->rear] = value;
q->rear = (q->rear + 1) % MAX; "enqueue: %d (rear=%d)
printf(/* dequeue: вернуть элемент из начала, затем переместить front вперёд циклически */\n", value, q->хвост);
}
/* Dequeue: вернуть элемент в начале, затем front перемещается вперёд циклически */
int dequeue(struct CircularQueue* q) {
if (isEmpty(q)) {
printf("Кольцевая очередь пуста!\n");
return -1;
}
int value = q->items[q->front];
q->front = (q->front + 1) % MAX; /* Операция по модулю обеспечивает циклический переход */
return value;
}
int main() {
struct CircularQueue q;
initQueue(&q);
enqueue(&q, 10); /* Постановка в очередь: 10 (rear=1) */
enqueue(&q, 20); /* Постановка в очередь: 20 (rear=2) */
enqueue(&q, 30); /* Постановка в очередь: 30 (rear=3) */
enqueue(&q, 40); /* Постановка в очередь: 40 (rear=4) — вместимость 5, максимум 4 элемента */
printf("Извлечение: %d\n", dequeue(&q)); /* Извлечение: 10 (front=1) */
printf("Извлечение: %d\n", dequeue(&q)); /* Извлечение: 20 (front=2) */
enqueue(&q, 50); /* Постановка в очередь: 50 (rear=0 — циклически переходит в начало!) */
enqueue(&q, 60); /* Постановка в очередь: 60 (rear=1) */
printf("Оставшиеся элементы: ");
while (!isEmpty(&q)) {
printf("%d ", dequeue(&q));
}
printf("\n"); /* Вывод: Оставшиеся элементы: 30 40 50 60 */
return 0;
}
Сценарии применения очереди
| Сценарии применения | Описание |
|---|---|
| Поиск в ширину (BFS) | Обходить узлы графа уровень за уровнем; нужна очередь для хранения узлов, "подлежащих посещению" |
| Планирование задач операционной системы | ЦП выделяет кванты времени в порядке поступления задач |
| Очередь задач печати | Несколько компьютеров совместно используют один принтер; запросы на печать ставятся в очередь и обрабатываются в порядке отправки |
| Очередь сообщений | В распределённых системах сообщения, создаваемые производителями, передаются потребителям через очередь |