mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-09 13:51:48 +08:00
First version.
This commit is contained in:
423
ru/docs/chapter_stack_and_queue/queue.md
Normal file
423
ru/docs/chapter_stack_and_queue/queue.md
Normal file
@@ -0,0 +1,423 @@
|
||||
# Очередь
|
||||
|
||||
<u>Очередь (queue)</u> -- это линейная структура данных, следующая правилу «первый пришел -- первый вышел». Как следует из названия, очередь моделирует реальную очередь, когда новые элементы постоянно добавляются в конец очереди, а элементы в начале очереди покидают ее последовательно.
|
||||
|
||||
Как показано на рисунке ниже, начало очереди называется "голова очереди", а конец -- "хвост очереди". Операция добавления элемента в конец очереди называется "добавление в очередь", а удаление элемента из начала очереди -- "удаление из очереди".
|
||||
|
||||

|
||||
|
||||
## Основные операции с очередью
|
||||
|
||||
Основные операции с очередью представлены в таблице ниже. Следует отметить, что имена методов могут различаться в зависимости от языка программирования. Здесь используются те же названия методов, что и для стека.
|
||||
|
||||
<p align="center"> Таблица <id> Эффективность операций с очередью </p>
|
||||
|
||||
| Метод | Описание | Временная сложность |
|
||||
| -------- | ------------------------------------------------------------- | ------------------- |
|
||||
| `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
|
||||
|
||||
## Реализация очереди
|
||||
|
||||
Для реализации очереди необходима структура данных, позволяющая добавлять элементы с одного конца и удалять с другого. Этим требованиям соответствуют как связный список, так и массив.
|
||||
|
||||
### Реализация на основе связного списка
|
||||
|
||||
Как показано на рисунке ниже, можно считать "головной узел" и "хвостовой узел" связного списка соответственно "головой очереди" и "хвостом очереди". При этом в хвост очереди можно только добавлять узлы, а из головы очереди -- только удалять узлы.
|
||||
|
||||
=== "LinkedListQueue"
|
||||

|
||||
|
||||
=== "push()"
|
||||

|
||||
|
||||
=== "pop()"
|
||||

|
||||
|
||||
Ниже приведен код реализации очереди на основе связного списка:
|
||||
|
||||
```src
|
||||
[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)$.
|
||||
|
||||
=== "ArrayQueue"
|
||||

|
||||
|
||||
=== "push()"
|
||||

|
||||
|
||||
=== "pop()"
|
||||

|
||||
|
||||
Можно заметить проблему: в процессе непрерывного добавления и удаления элементов `front` и `rear` постоянно смещаются вправо, **и когда они достигают конца массива, дальнейшее перемещение становится невозможным**. Для решения этой проблемы можно рассматривать массив как "кольцевой массив" с соединенными концами.
|
||||
|
||||
Для кольцевого массива необходимо, чтобы `front` или `rear` при выходе за пределы конца массива возвращались к началу для продолжения обхода. Эта периодическая закономерность может быть реализована с помощью "операции взятия остатка", как показано в коде ниже:
|
||||
|
||||
```src
|
||||
[file]{array_queue}-[class]{array_queue}-[func]{}
|
||||
```
|
||||
|
||||
Приведенная выше реализация очереди все еще имеет ограничение: ее длина неизменна. Однако эту проблему несложно решить, заменив массив на динамический массив, что позволит ввести механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно.
|
||||
|
||||
Выводы при сравнении двух реализаций аналогичны выводам для стека, поэтому здесь не повторяются.
|
||||
|
||||
## Типичные сценарии применения очереди
|
||||
|
||||
- **Заказы на Taobao**. После размещения заказа покупателем заказ добавляется в очередь, после чего система последовательно обрабатывает заказы из очереди. Во время распродажи "Двойной одиннадцатый" за короткое время генерируется огромное количество заказов, и высокая параллельность становится ключевой проблемой, которую необходимо решить инженерам.
|
||||
- **Различные задачи в очереди**. Любой сценарий, требующий реализации функции "первым пришел -- первым обслужен", например, очередь задач принтера, очередь выдачи блюд в ресторане и т. д. Очередь в этих сценариях эффективно поддерживает порядок обработки.
|
||||
Reference in New Issue
Block a user