22 KiB
Очередь
Очередь (queue) -- это линейная структура данных, следующая правилу «первый пришел -- первый вышел». Как следует из названия, очередь моделирует реальную очередь, когда новые элементы постоянно добавляются в конец очереди, а элементы в начале очереди покидают ее последовательно.
Как показано на рисунке ниже, начало очереди называется "голова очереди", а конец -- "хвост очереди". Операция добавления элемента в конец очереди называется "добавление в очередь", а удаление элемента из начала очереди -- "удаление из очереди".
Основные операции с очередью
Основные операции с очередью представлены в таблице ниже. Следует отметить, что имена методов могут различаться в зависимости от языка программирования. Здесь используются те же названия методов, что и для стека.
Таблица Эффективность операций с очередью
| Метод | Описание | Временная сложность |
|---|---|---|
push() |
Добавление элемента в очередь, т. е. добавление элемента в конец очереди | O(1) |
pop() |
Удаление элемента из головы очереди | O(1) |
peek() |
Доступ к элементу в голове очереди | O(1) |
Можно использовать готовый класс очереди в языке программирования:
=== "Python"
```python title="queue.py"
from collections import deque
# Инициализация очереди
# В Python обычно используется класс двусторонней очереди deque
# Хотя queue.Queue() является полноценным классом очереди, он не очень удобен, поэтому не рекомендуется к использованию
que: deque[int] = deque()
# Добавление элемента в очередь
que.append(1)
que.append(3)
que.append(2)
que.append(5)
que.append(4)
# Доступ к элементу в голове очереди
front: int = que[0]
# Удаление элемента из очереди
pop: int = que.popleft()
# Получение длины очереди
size: int = len(que)
# Проверка очереди на пустоту
is_empty: bool = len(que) == 0
```
=== "C++"
```cpp title="queue.cpp"
/* Инициализация очереди */
queue<int> queue;
/* Добавление элемента в очередь */
queue.push(1);
queue.push(3);
queue.push(2);
queue.push(5);
queue.push(4);
/* Доступ к элементу в голове очереди */
int front = queue.front();
/* Удаление элемента из очереди */
queue.pop();
/* Получение длины очереди */
int size = queue.size();
/* Проверка очереди на пустоту */
bool empty = queue.empty();
```
=== "Java"
```java title="queue.java"
/* Инициализация очереди */
Queue<Integer> queue = new LinkedList<>();
/* Добавление элемента в очередь */
queue.offer(1);
queue.offer(3);
queue.offer(2);
queue.offer(5);
queue.offer(4);
/* Доступ к элементу в голове очереди */
int peek = queue.peek();
/* Удаление элемента из очереди */
int pop = queue.poll();
/* Получение длины очереди */
int size = queue.size();
/* Проверка очереди на пустоту */
boolean isEmpty = queue.isEmpty();
```
=== "C#"
```csharp title="queue.cs"
/* Инициализация очереди */
Queue<int> queue = new();
/* Добавление элемента в очередь */
queue.Enqueue(1);
queue.Enqueue(3);
queue.Enqueue(2);
queue.Enqueue(5);
queue.Enqueue(4);
/* Доступ к элементу в голове очереди */
int peek = queue.Peek();
/* Удаление элемента из очереди */
int pop = queue.Dequeue();
/* Получение длины очереди */
int size = queue.Count;
/* Проверка очереди на пустоту */
bool isEmpty = queue.Count == 0;
```
=== "Go"
```go title="queue_test.go"
/* Инициализация очереди */
// В Go используется list в качестве очереди
queue := list.New()
/* Добавление элемента в очередь */
queue.PushBack(1)
queue.PushBack(3)
queue.PushBack(2)
queue.PushBack(5)
queue.PushBack(4)
/* Доступ к элементу в голове очереди */
peek := queue.Front()
/* Удаление элемента из очереди */
pop := queue.Front()
queue.Remove(pop)
/* Получение длины очереди */
size := queue.Len()
/* Проверка очереди на пустоту */
isEmpty := queue.Len() == 0
```
=== "Swift"
```swift title="queue.swift"
/* Инициализация очереди */
// В Swift нет встроенного класса очереди, можно использовать Array
var queue: [Int] = []
/* Добавление элемента в очередь */
queue.append(1)
queue.append(3)
queue.append(2)
queue.append(5)
queue.append(4)
/* Доступ к элементу в голове очереди */
let peek = queue.first!
/* Удаление элемента из очереди */
// Так как это массив, removeFirst имеет сложность O(n)
let pool = queue.removeFirst()
/* Получение длины очереди */
let size = queue.count
/* Проверка очереди на пустоту */
let isEmpty = queue.isEmpty
```
=== "JS"
```javascript title="queue.js"
/* Инициализация очереди */
// В JavaScript нет встроенной очереди, можно использовать Array
const queue = [];
/* Добавление элемента в очередь */
queue.push(1);
queue.push(3);
queue.push(2);
queue.push(5);
queue.push(4);
/* Доступ к элементу в голове очереди */
const peek = queue[0];
/* Удаление элемента из очереди */
// Так как это массив, метод shift() имеет временную сложность O(n)
const pop = queue.shift();
/* Получение длины очереди */
const size = queue.length;
/* Проверка очереди на пустоту */
const empty = queue.length === 0;
```
=== "TS"
```typescript title="queue.ts"
/* Инициализация очереди */
// В TypeScript нет встроенной очереди, можно использовать Array
const queue: number[] = [];
/* Добавление элемента в очередь */
queue.push(1);
queue.push(3);
queue.push(2);
queue.push(5);
queue.push(4);
/* Доступ к элементу в голове очереди */
const peek = queue[0];
/* Удаление элемента из очереди */
// Так как это массив, метод shift() имеет временную сложность O(n)
const pop = queue.shift();
/* Получение длины очереди */
const size = queue.length;
/* Проверка очереди на пустоту */
const empty = queue.length === 0;
```
=== "Dart"
```dart title="queue.dart"
/* Инициализация очереди */
// В Dart класс Queue является двусторонней очередью, но может использоваться как обычная очередь
Queue<int> queue = Queue();
/* Добавление элемента в очередь */
queue.add(1);
queue.add(3);
queue.add(2);
queue.add(5);
queue.add(4);
/* Доступ к элементу в голове очереди */
int peek = queue.first;
/* Удаление элемента из очереди */
int pop = queue.removeFirst();
/* Получение длины очереди */
int size = queue.length;
/* Проверка очереди на пустоту */
bool isEmpty = queue.isEmpty;
```
=== "Rust"
```rust title="queue.rs"
/* Инициализация двусторонней очереди */
// В Rust используется двусторонняя очередь как обычная очередь
let mut deque: VecDeque<u32> = VecDeque::new();
/* Добавление элемента в очередь */
deque.push_back(1);
deque.push_back(3);
deque.push_back(2);
deque.push_back(5);
deque.push_back(4);
/* Доступ к элементу в голове очереди */
if let Some(front) = deque.front() {
}
/* Удаление элемента из очереди */
if let Some(pop) = deque.pop_front() {
}
/* Получение длины очереди */
let size = deque.len();
/* Проверка очереди на пустоту */
let is_empty = deque.is_empty();
```
=== "C"
```c title="queue.c"
// В C нет встроенной очереди
```
=== "Kotlin"
```kotlin title="queue.kt"
/* Инициализация очереди */
val queue = LinkedList<Int>()
/* Добавление элемента в очередь */
queue.offer(1)
queue.offer(3)
queue.offer(2)
queue.offer(5)
queue.offer(4)
/* Доступ к элементу в голове очереди */
val peek = queue.peek()
/* Удаление элемента из очереди */
val pop = queue.poll()
/* Получение длины очереди */
val size = queue.size
/* Проверка очереди на пустоту */
val isEmpty = queue.isEmpty()
```
=== "Ruby"
```ruby title="queue.rb"
# Инициализация очереди
# Встроенная очередь Ruby (Thread::Queue) не имеет методов peek и обхода, можно использовать Array
queue = []
# Добавление элемента в очередь
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
# Доступ к элементу в голове очереди
peek = queue.first
# Удаление элемента из очереди
# Обратите внимание: так как это массив, метод Array#shift имеет временную сложность O(n)
pop = queue.shift
# Получение длины очереди
size = queue.length
# Проверка очереди на пустоту
is_empty = queue.empty?
```
??? pythontutor "Визуализация выполнения"
https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%BE%D0%B1%D1%8B%D1%87%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%D0%B9%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20deque%0A%20%20%20%20%23%20%D0%A5%D0%BE%D1%82%D1%8F%20queue.Queue%28%29%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D0%BF%D0%BE%D0%BB%D0%BD%D0%BE%D1%86%D0%B5%D0%BD%D0%BD%D1%8B%D0%BC%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%BC%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20%D0%BE%D0%BD%20%D0%BD%D0%B5%20%D0%BE%D1%87%D0%B5%D0%BD%D1%8C%20%D1%83%D0%B4%D0%BE%D0%B1%D0%B5%D0%BD%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B2%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20%D0%BD%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D1%82%D1%83%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
Реализация очереди
Для реализации очереди необходима структура данных, позволяющая добавлять элементы с одного конца и удалять с другого. Этим требованиям соответствуют как связный список, так и массив.
Реализация на основе связного списка
Как показано на рисунке ниже, можно считать "головной узел" и "хвостовой узел" связного списка соответственно "головой очереди" и "хвостом очереди". При этом в хвост очереди можно только добавлять узлы, а из головы очереди -- только удалять узлы.
Ниже приведен код реализации очереди на основе связного списка:
[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{}
Реализация на основе массива
Удаление первого элемента в массиве имеет временную сложность O(n), что снижает эффективность операции удаления из очереди. Однако можно использовать следующий изящный метод, чтобы избежать этой проблемы.
Можно использовать переменную front, указывающую на индекс элемента в голове очереди, и поддерживать переменную size для записи длины очереди. Определим rear = front + size, эта формула вычисляет rear, указывающий на позицию после элемента в хвосте очереди.
При таком подходе действительный интервал элементов в массиве составляет [front, rear - 1], различные операции реализуются следующим образом:
- Операция добавления в очередь: присваивает входной элемент индексу
rearи увеличиваетsizeна 1. - Операция удаления из очереди: только увеличивает
frontна 1 и уменьшаетsizeна 1.
Как видно, операции добавления и удаления требуют только одного действия, временная сложность обеих составляет O(1).
Можно заметить проблему: в процессе непрерывного добавления и удаления элементов front и rear постоянно смещаются вправо, и когда они достигают конца массива, дальнейшее перемещение становится невозможным. Для решения этой проблемы можно рассматривать массив как "кольцевой массив" с соединенными концами.
Для кольцевого массива необходимо, чтобы front или rear при выходе за пределы конца массива возвращались к началу для продолжения обхода. Эта периодическая закономерность может быть реализована с помощью "операции взятия остатка", как показано в коде ниже:
[file]{array_queue}-[class]{array_queue}-[func]{}
Приведенная выше реализация очереди все еще имеет ограничение: ее длина неизменна. Однако эту проблему несложно решить, заменив массив на динамический массив, что позволит ввести механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно.
Выводы при сравнении двух реализаций аналогичны выводам для стека, поэтому здесь не повторяются.
Типичные сценарии применения очереди
- Заказы на Taobao. После размещения заказа покупателем заказ добавляется в очередь, после чего система последовательно обрабатывает заказы из очереди. Во время распродажи "Двойной одиннадцатый" за короткое время генерируется огромное количество заказов, и высокая параллельность становится ключевой проблемой, которую необходимо решить инженерам.
- Различные задачи в очереди. Любой сценарий, требующий реализации функции "первым пришел -- первым обслужен", например, очередь задач принтера, очередь выдачи блюд в ресторане и т. д. Очередь в этих сценариях эффективно поддерживает порядок обработки.






