First version.

This commit is contained in:
krahets
2026-01-20 15:08:42 +08:00
parent 2213a59ff6
commit 8071daddaa
106 changed files with 11790 additions and 0 deletions

View File

@@ -0,0 +1,388 @@
# Двусторонняя очередь
В очереди мы можем удалять элементы только из головы или добавлять элементы в хвост. Как показано на рисунке ниже, <u>двусторонняя очередь (double-ended queue)</u> предоставляет большую гибкость, позволяя выполнять операции добавления или удаления элементов как в голове, так и в хвосте.
![Операции двусторонней очереди](../assets/deque_operations.png)
## Основные операции с двусторонней очередью
Основные операции с двусторонней очередью представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования.
<p align="center"> Таблица <id> &nbsp; Эффективность операций двусторонней очереди </p>
| Метод | Описание | Временная сложность |
| -------------- | -------------------------------- | ------------------- |
| `push_first()` | Добавление элемента в голову очереди | $O(1)$ |
| `push_last()` | Добавление элемента в хвост очереди | $O(1)$ |
| `pop_first()` | Удаление элемента из головы очереди | $O(1)$ |
| `pop_last()` | Удаление элемента из хвоста очереди | $O(1)$ |
| `peek_first()` | Доступ к элементу в голове очереди | $O(1)$ |
| `peek_last()` | Доступ к элементу в хвосте очереди | $O(1)$ |
Аналогично, можно использовать готовый класс двусторонней очереди в языке программирования:
=== "Python"
```python title="deque.py"
from collections import deque
# Инициализация двусторонней очереди
deq: deque[int] = deque()
# Добавление элемента в очередь
deq.append(2) # Добавление в хвост
deq.append(5)
deq.append(4)
deq.appendleft(3) # Добавление в голову
deq.appendleft(1)
# Доступ к элементам
front: int = deq[0] # Элемент в голове очереди
rear: int = deq[-1] # Элемент в хвосте очереди
# Удаление элементов из очереди
pop_front: int = deq.popleft() # Удаление элемента из головы
pop_rear: int = deq.pop() # Удаление элемента из хвоста
# Получение длины двусторонней очереди
size: int = len(deq)
# Проверка двусторонней очереди на пустоту
is_empty: bool = len(deq) == 0
```
=== "C++"
```cpp title="deque.cpp"
/* Инициализация двусторонней очереди */
deque<int> deque;
/* Добавление элемента в очередь */
deque.push_back(2); // Добавление в хвост
deque.push_back(5);
deque.push_back(4);
deque.push_front(3); // Добавление в голову
deque.push_front(1);
/* Доступ к элементам */
int front = deque.front(); // Элемент в голове очереди
int back = deque.back(); // Элемент в хвосте очереди
/* Удаление элементов из очереди */
deque.pop_front(); // Удаление элемента из головы
deque.pop_back(); // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
int size = deque.size();
/* Проверка двусторонней очереди на пустоту */
bool empty = deque.empty();
```
=== "Java"
```java title="deque.java"
/* Инициализация двусторонней очереди */
Deque<Integer> deque = new LinkedList<>();
/* Добавление элемента в очередь */
deque.offerLast(2); // Добавление в хвост
deque.offerLast(5);
deque.offerLast(4);
deque.offerFirst(3); // Добавление в голову
deque.offerFirst(1);
/* Доступ к элементам */
int peekFirst = deque.peekFirst(); // Элемент в голове очереди
int peekLast = deque.peekLast(); // Элемент в хвосте очереди
/* Удаление элементов из очереди */
int popFirst = deque.pollFirst(); // Удаление элемента из головы
int popLast = deque.pollLast(); // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
int size = deque.size();
/* Проверка двусторонней очереди на пустоту */
boolean isEmpty = deque.isEmpty();
```
=== "C#"
```csharp title="deque.cs"
/* Инициализация двусторонней очереди */
// В C# связный список LinkedList используется как двусторонняя очередь
LinkedList<int> deque = new();
/* Добавление элемента в очередь */
deque.AddLast(2); // Добавление в хвост
deque.AddLast(5);
deque.AddLast(4);
deque.AddFirst(3); // Добавление в голову
deque.AddFirst(1);
/* Доступ к элементам */
int peekFirst = deque.First.Value; // Элемент в голове очереди
int peekLast = deque.Last.Value; // Элемент в хвосте очереди
/* Удаление элементов из очереди */
deque.RemoveFirst(); // Удаление элемента из головы
deque.RemoveLast(); // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
int size = deque.Count;
/* Проверка двусторонней очереди на пустоту */
bool isEmpty = deque.Count == 0;
```
=== "Go"
```go title="deque_test.go"
/* Инициализация двусторонней очереди */
// В Go list используется как двусторонняя очередь
deque := list.New()
/* Добавление элемента в очередь */
deque.PushBack(2) // Добавление в хвост
deque.PushBack(5)
deque.PushBack(4)
deque.PushFront(3) // Добавление в голову
deque.PushFront(1)
/* Доступ к элементам */
front := deque.Front() // Элемент в голове очереди
rear := deque.Back() // Элемент в хвосте очереди
/* Удаление элементов из очереди */
deque.Remove(front) // Удаление элемента из головы
deque.Remove(rear) // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
size := deque.Len()
/* Проверка двусторонней очереди на пустоту */
isEmpty := deque.Len() == 0
```
=== "Swift"
```swift title="deque.swift"
/* Инициализация двусторонней очереди */
// В Swift нет встроенного класса двусторонней очереди, можно использовать Array
var deque: [Int] = []
/* Добавление элемента в очередь */
deque.append(2) // Добавление в хвост
deque.append(5)
deque.append(4)
deque.insert(3, at: 0) // Добавление в голову
deque.insert(1, at: 0)
/* Доступ к элементам */
let peekFirst = deque.first! // Элемент в голове очереди
let peekLast = deque.last! // Элемент в хвосте очереди
/* Удаление элементов из очереди */
// При использовании Array сложность popFirst составляет O(n)
let popFirst = deque.removeFirst() // Удаление элемента из головы
let popLast = deque.removeLast() // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
let size = deque.count
/* Проверка двусторонней очереди на пустоту */
let isEmpty = deque.isEmpty
```
=== "JS"
```javascript title="deque.js"
/* Инициализация двусторонней очереди */
// В JavaScript нет встроенной двусторонней очереди, используется Array
const deque = [];
/* Добавление элемента в очередь */
deque.push(2);
deque.push(5);
deque.push(4);
// Обратите внимание: при использовании массива временная сложность unshift() составляет O(n)
deque.unshift(3);
deque.unshift(1);
/* Доступ к элементам */
const peekFirst = deque[0];
const peekLast = deque[deque.length - 1];
/* Удаление элементов из очереди */
// Обратите внимание: при использовании массива временная сложность shift() составляет O(n)
const popFront = deque.shift();
const popBack = deque.pop();
/* Получение длины двусторонней очереди */
const size = deque.length;
/* Проверка двусторонней очереди на пустоту */
const isEmpty = size === 0;
```
=== "TS"
```typescript title="deque.ts"
/* Инициализация двусторонней очереди */
// В TypeScript нет встроенной двусторонней очереди, используется Array
const deque: number[] = [];
/* Добавление элемента в очередь */
deque.push(2);
deque.push(5);
deque.push(4);
// Обратите внимание: при использовании массива временная сложность unshift() составляет O(n)
deque.unshift(3);
deque.unshift(1);
/* Доступ к элементам */
const peekFirst: number = deque[0];
const peekLast: number = deque[deque.length - 1];
/* Удаление элементов из очереди */
// Обратите внимание: при использовании массива временная сложность shift() составляет O(n)
const popFront: number = deque.shift() as number;
const popBack: number = deque.pop() as number;
/* Получение длины двусторонней очереди */
const size: number = deque.length;
/* Проверка двусторонней очереди на пустоту */
const isEmpty: boolean = size === 0;
```
=== "Dart"
```dart title="deque.dart"
/* Инициализация двусторонней очереди */
// В Dart Queue определяется как двусторонняя очередь
Queue<int> deque = Queue<int>();
/* Добавление элемента в очередь */
deque.addLast(2); // Добавление в хвост
deque.addLast(5);
deque.addLast(4);
deque.addFirst(3); // Добавление в голову
deque.addFirst(1);
/* Доступ к элементам */
int peekFirst = deque.first; // Элемент в голове очереди
int peekLast = deque.last; // Элемент в хвосте очереди
/* Удаление элементов из очереди */
int popFirst = deque.removeFirst(); // Удаление элемента из головы
int popLast = deque.removeLast(); // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
int size = deque.length;
/* Проверка двусторонней очереди на пустоту */
bool isEmpty = deque.isEmpty;
```
=== "Rust"
```rust title="deque.rs"
/* Инициализация двусторонней очереди */
let mut deque: VecDeque<u32> = VecDeque::new();
/* Добавление элемента в очередь */
deque.push_back(2); // Добавление в хвост
deque.push_back(5);
deque.push_back(4);
deque.push_front(3); // Добавление в голову
deque.push_front(1);
/* Доступ к элементам */
if let Some(front) = deque.front() { // Элемент в голове очереди
}
if let Some(rear) = deque.back() { // Элемент в хвосте очереди
}
/* Удаление элементов из очереди */
if let Some(pop_front) = deque.pop_front() { // Удаление элемента из головы
}
if let Some(pop_rear) = deque.pop_back() { // Удаление элемента из хвоста
}
/* Получение длины двусторонней очереди */
let size = deque.len();
/* Проверка двусторонней очереди на пустоту */
let is_empty = deque.is_empty();
```
=== "C"
```c title="deque.c"
// В C нет встроенной двусторонней очереди
```
=== "Kotlin"
```kotlin title="deque.kt"
/* Инициализация двусторонней очереди */
val deque = LinkedList<Int>()
/* Добавление элемента в очередь */
deque.offerLast(2) // Добавление в хвост
deque.offerLast(5)
deque.offerLast(4)
deque.offerFirst(3) // Добавление в голову
deque.offerFirst(1)
/* Доступ к элементам */
val peekFirst = deque.peekFirst() // Элемент в голове очереди
val peekLast = deque.peekLast() // Элемент в хвосте очереди
/* Удаление элементов из очереди */
val popFirst = deque.pollFirst() // Удаление элемента из головы
val popLast = deque.pollLast() // Удаление элемента из хвоста
/* Получение длины двусторонней очереди */
val size = deque.size
/* Проверка двусторонней очереди на пустоту */
val isEmpty = deque.isEmpty()
```
=== "Ruby"
```ruby title="deque.rb"
# Инициализация двусторонней очереди
# В Ruby нет встроенной двусторонней очереди, используется Array
deque = []
# Добавление элемента в очередь
deque << 2
deque << 5
deque << 4
# Обратите внимание: при использовании массива временная сложность Array#unshift составляет O(n)
deque.unshift(3)
deque.unshift(1)
# Доступ к элементам
peek_first = deque.first
peek_last = deque.last
# Удаление элементов из очереди
# Обратите внимание: при использовании массива временная сложность Array#shift составляет O(n)
pop_front = deque.shift
pop_back = deque.pop
# Получение длины двусторонней очереди
size = deque.length
# Проверка двусторонней очереди на пустоту
is_empty = size.zero?
```
??? 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%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%0A%20%20%20%20deq%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%20deq.append%282%29%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%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%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%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%83%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%D0%94%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20deque%20%3D%22,%20deq%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%D0%B0%D0%BC%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%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%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%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%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%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B5%20rear%20%3D%22,%20rear%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%BE%D0%B2%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_front%20%3D%20deq.popleft%28%29%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%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%0A%20%20%20%20print%28%22%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%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20%20pop_front%20%3D%22,%20pop_front%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%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%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%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%0A%20%20%20%20print%28%22%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%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20%20pop_rear%20%3D%22,%20pop_rear%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%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20deque%20%3D%22,%20deq%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%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%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%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%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%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%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%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%94%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%3D%22,%20is_empty%29&cumulative=false&curIn

View File

@@ -0,0 +1,11 @@
```markdown
# Стек и очередь
![Стек и очередь](../assets/covers/chapter_stack_and_queue.jpg)
!!! abstract
Стек можно сравнить со стопкой котиков, а очередь — с котиками, стоящими в очереди.
Они представляют логику «первый вошел — последний вышел» и «первый пришел — первый вышел» соответственно.
```

View File

@@ -0,0 +1,423 @@
# Очередь
<u>Очередь (queue)</u> -- это линейная структура данных, следующая правилу «первый пришел -- первый вышел». Как следует из названия, очередь моделирует реальную очередь, когда новые элементы постоянно добавляются в конец очереди, а элементы в начале очереди покидают ее последовательно.
Как показано на рисунке ниже, начало очереди называется "голова очереди", а конец -- "хвост очереди". Операция добавления элемента в конец очереди называется "добавление в очередь", а удаление элемента из начала очереди -- "удаление из очереди".
![Правило очереди «первый пришел -- первый вышел»](../assets/queue_operations.png)
## Основные операции с очередью
Основные операции с очередью представлены в таблице ниже. Следует отметить, что имена методов могут различаться в зависимости от языка программирования. Здесь используются те же названия методов, что и для стека.
<p align="center"> Таблица <id> &nbsp; Эффективность операций с очередью </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"
![Операции добавления и удаления в очереди на основе связного списка](../assets/linkedlist_queue_step1.png)
=== "push()"
![linkedlist_queue_push](../assets/linkedlist_queue_step2_push.png)
=== "pop()"
![linkedlist_queue_pop](../assets/linkedlist_queue_step3_pop.png)
Ниже приведен код реализации очереди на основе связного списка:
```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"
![Операции добавления и удаления в очереди на основе массива](../assets/array_queue_step1.png)
=== "push()"
![array_queue_push](../assets/array_queue_step2_push.png)
=== "pop()"
![array_queue_pop](../assets/array_queue_step3_pop.png)
Можно заметить проблему: в процессе непрерывного добавления и удаления элементов `front` и `rear` постоянно смещаются вправо, **и когда они достигают конца массива, дальнейшее перемещение становится невозможным**. Для решения этой проблемы можно рассматривать массив как "кольцевой массив" с соединенными концами.
Для кольцевого массива необходимо, чтобы `front` или `rear` при выходе за пределы конца массива возвращались к началу для продолжения обхода. Эта периодическая закономерность может быть реализована с помощью "операции взятия остатка", как показано в коде ниже:
```src
[file]{array_queue}-[class]{array_queue}-[func]{}
```
Приведенная выше реализация очереди все еще имеет ограничение: ее длина неизменна. Однако эту проблему несложно решить, заменив массив на динамический массив, что позволит ввести механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно.
Выводы при сравнении двух реализаций аналогичны выводам для стека, поэтому здесь не повторяются.
## Типичные сценарии применения очереди
- **Заказы на Taobao**. После размещения заказа покупателем заказ добавляется в очередь, после чего система последовательно обрабатывает заказы из очереди. Во время распродажи "Двойной одиннадцатый" за короткое время генерируется огромное количество заказов, и высокая параллельность становится ключевой проблемой, которую необходимо решить инженерам.
- **Различные задачи в очереди**. Любой сценарий, требующий реализации функции "первым пришел -- первым обслужен", например, очередь задач принтера, очередь выдачи блюд в ресторане и т. д. Очередь в этих сценариях эффективно поддерживает порядок обработки.

View File

@@ -0,0 +1,430 @@
# Стек
<u>Стек (stack)</u> -- это линейная структура данных, которая следует логике «первый вошел -- последний вышел».
Стек можно сравнить со стопкой тарелок на столе: чтобы достать тарелку снизу, нужно сначала убрать все тарелки сверху. Заменив тарелки на элементы различных типов (например, целые числа, символы, объекты и т. д.), мы получим структуру данных, называемую стеком.
Как показано на рисунке ниже, верх стопки элементов называется вершиной стека, а низ -- основанием стека. Операция добавления элемента на вершину стека называется вставка, а удаление элемента с вершины -- извлечение.
![Правило «первый вошел -- последний вышел» для стека](../assets/stack_operations.png)
## Основные операции со стеком
Основные операции со стеком представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования. Здесь в качестве примера используются распространенные имена `push()`, `pop()`, `peek()`.
<p align="center"> Таблица <id> &nbsp; Эффективность операций со стеком </p>
| Метод | Описание | Временная сложность |
| -------- | ------------------------------------------------ | ------------------- |
| `push()` | Вставка элемента (добавление на вершину стека) | $O(1)$ |
| `pop()` | Извлечение элемента с вершины стека | $O(1)$ |
| `peek()` | Доступ к элементу на вершине стека | $O(1)$ |
Обычно достаточно использовать классы стека, встроенные в язык программирования. Однако в некоторых языках может не быть специального класса для стека. Тогда можно использовать массив или связный список в качестве стека, игнорируя операции, не связанные со стеком.
=== "Python"
```python title="stack.py"
# Инициализация стека.
# В Python нет встроенного класса стека, можно использовать list.
stack: list[int] = []
# Вставка элемента.
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
# Доступ к элементу на вершине стека.
peek: int = stack[-1]
# Извлечение элемента.
pop: int = stack.pop()
# Получение длины стека.
size: int = len(stack)
# Проверка на пустоту.
is_empty: bool = len(stack) == 0
```
=== "C++"
```cpp title="stack.cpp"
/* 初始化栈 */
stack<int> stack;
/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 访问栈顶元素 */
int top = stack.top();
/* 元素出栈 */
stack.pop(); // 无返回值
/* 获取栈的长度 */
int size = stack.size();
/* 判断是否为空 */
bool empty = stack.empty();
```
=== "Java"
```java title="stack.java"
/* 初始化栈 */
Stack<Integer> stack = new Stack<>();
/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 访问栈顶元素 */
int peek = stack.peek();
/* 元素出栈 */
int pop = stack.pop();
/* 获取栈的长度 */
int size = stack.size();
/* 判断是否为空 */
boolean isEmpty = stack.isEmpty();
```
=== "C#"
```csharp title="stack.cs"
/* 初始化栈 */
Stack<int> stack = new();
/* 元素入栈 */
stack.Push(1);
stack.Push(3);
stack.Push(2);
stack.Push(5);
stack.Push(4);
/* 访问栈顶元素 */
int peek = stack.Peek();
/* 元素出栈 */
int pop = stack.Pop();
/* 获取栈的长度 */
int size = stack.Count;
/* 判断是否为空 */
bool isEmpty = stack.Count == 0;
```
=== "Go"
```go title="stack_test.go"
/* 初始化栈 */
// 在 Go 中,推荐将 Slice 当作栈来使用
var stack []int
/* 元素入栈 */
stack = append(stack, 1)
stack = append(stack, 3)
stack = append(stack, 2)
stack = append(stack, 5)
stack = append(stack, 4)
/* 访问栈顶元素 */
peek := stack[len(stack)-1]
/* 元素出栈 */
pop := stack[len(stack)-1]
stack = stack[:len(stack)-1]
/* 获取栈的长度 */
size := len(stack)
/* 判断是否为空 */
isEmpty := len(stack) == 0
```
=== "Swift"
```swift title="stack.swift"
/* 初始化栈 */
// Swift 没有内置的栈类,可以把 Array 当作栈来使用
var stack: [Int] = []
/* 元素入栈 */
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
/* 访问栈顶元素 */
let peek = stack.last!
/* 元素出栈 */
let pop = stack.removeLast()
/* 获取栈的长度 */
let size = stack.count
/* 判断是否为空 */
let isEmpty = stack.isEmpty
```
=== "JS"
```javascript title="stack.js"
/* 初始化栈 */
// JavaScript 没有内置的栈类,可以把 Array 当作栈来使用
const stack = [];
/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 访问栈顶元素 */
const peek = stack[stack.length-1];
/* 元素出栈 */
const pop = stack.pop();
/* 获取栈的长度 */
const size = stack.length;
/* 判断是否为空 */
const is_empty = stack.length === 0;
```
=== "TS"
```typescript title="stack.ts"
/* 初始化栈 */
// TypeScript 没有内置的栈类,可以把 Array 当作栈来使用
const stack: number[] = [];
/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 访问栈顶元素 */
const peek = stack[stack.length - 1];
/* 元素出栈 */
const pop = stack.pop();
/* 获取栈的长度 */
const size = stack.length;
/* 判断是否为空 */
const is_empty = stack.length === 0;
```
=== "Dart"
```dart title="stack.dart"
/* 初始化栈 */
// Dart 没有内置的栈类,可以把 List 当作栈来使用
List<int> stack = [];
/* 元素入栈 */
stack.add(1);
stack.add(3);
stack.add(2);
stack.add(5);
stack.add(4);
/* 访问栈顶元素 */
int peek = stack.last;
/* 元素出栈 */
int pop = stack.removeLast();
/* 获取栈的长度 */
int size = stack.length;
/* 判断是否为空 */
bool isEmpty = stack.isEmpty;
```
=== "Rust"
```rust title="stack.rs"
/* 初始化栈 */
// 把 Vec 当作栈来使用
let mut stack: Vec<i32> = Vec::new();
/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 访问栈顶元素 */
let top = stack.last().unwrap();
/* 元素出栈 */
let pop = stack.pop().unwrap();
/* 获取栈的长度 */
let size = stack.len();
/* 判断是否为空 */
let is_empty = stack.is_empty();
```
=== "C"
```c title="stack.c"
// C 未提供内置栈
```
=== "Kotlin"
```kotlin title="stack.kt"
/* 初始化栈 */
val stack = Stack<Int>()
/* 元素入栈 */
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
/* 访问栈顶元素 */
val peek = stack.peek()
/* 元素出栈 */
val pop = stack.pop()
/* 获取栈的长度 */
val size = stack.size
/* 判断是否为空 */
val isEmpty = stack.isEmpty()
```
=== "Ruby"
```ruby title="stack.rb"
# 初始化栈
# Ruby 没有内置的栈类,可以把 Array 当作栈来使用
stack = []
# 元素入栈
stack << 1
stack << 3
stack << 2
stack << 5
stack << 4
# 访问栈顶元素
peek = stack.last
# 元素出栈
pop = stack.pop
# 获取栈的长度
size = stack.length
# 判断是否为空
is_empty = stack.empty?
```
??? pythontutor "可视化运行"
https://pythontutor.com/render.html#code=%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%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Реализация стека
Чтобы глубже понять механизм работы стека, попробуем реализовать собственный класс стека.
Стек следует принципу «первый вошел -- последний вышел», поэтому добавление и удаление элементов возможно только на вершине стека. Однако в массивах и связных списках элементы можно добавлять и удалять в любом месте, **поэтому стек можно рассматривать как ограниченный массив или связный список**. Иными словами, можно скрыть часть операций массива или связного списка, чтобы их внешняя логика соответствовала характеристикам стека.
### Реализация на основе связного списка
При использовании для реализации стека связного списка можно считать головной узел связного списка вершиной стека, а хвостовой узел -- основанием стека.
Как показано на рисунке ниже, для операции вставки элемента достаточно вставить его в начало связного списка. Этот метод вставки узла называется вставка в голову. Для операции извлечения элемента достаточно удалить головной узел из связного списка.
=== "LinkedListStack"
![Операции вставки и извлечения в стеке на основе связного списка](../assets/linkedlist_stack_step1.png)
=== "push()"
![linkedlist_stack_push](../assets/linkedlist_stack_step2_push.png)
=== "pop()"
![linkedlist_stack_pop](../assets/linkedlist_stack_step3_pop.png)
Ниже приведен пример кода для реализации стека на основе связного списка:
```src
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
```
### Реализация на основе массива
При использовании для реализации стека массива можно считать конец массива вершиной стека. Как показано на рисунке ниже, операции вставки и извлечения соответствуют добавлению и удалению элементов в конце массива. Временная сложность этих операций составляет $O(1)$.
=== "ArrayStack"
![Операции вставки и извлечения в стеке на основе массива](../assets/array_stack_step1.png)
=== "push()"
![array_stack_push](../assets/array_stack_step2_push.png)
=== "pop()"
![array_stack_pop](../assets/array_stack_step3_pop.png)
Поскольку количество вставляемых элементов может постоянно увеличиваться, можно использовать динамический массив, чтобы не заниматься расширением массива самостоятельно. Ниже приведен пример кода:
```src
[file]{array_stack}-[class]{array_stack}-[func]{}
```
## Сравнение двух реализаций
**Поддерживаемые операции**
Обе реализации поддерживают все операции, определенные для стека. Реализация на основе массива дополнительно поддерживает произвольный доступ, но это выходит за рамки определения стека, поэтому обычно не используется.
**Временная сложность**
В реализации на основе массива операции добавления и удаления элемента выполняются в заранее выделенной непрерывной памяти, что обеспечивает хорошую локальность кеша и, следовательно, высокую эффективность. Однако, если при добавлении элемента превышается емкость массива, срабатывает механизм расширения, что приводит к увеличению временной сложности данной операции до $O(n)$.
В реализации на основе связного списка расширение происходит очень гибко, и не возникает проблемы снижения эффективности, как в случае расширения массива. Однако операция добавления элемента требует инициализации объекта узла и изменения указателя, что делает ее относительно менее эффективной. Тем не менее, если добавляемый элемент уже является объектом узла, можно избежать шага инициализации, что повысит эффективность.
Таким образом, если элементы операций добавления и удаления являются примитивными типами данных, такими как `int` или `double`, можно сделать следующие выводы:
- Стек, реализованный на основе массива, при срабатывании механизма расширения теряет в эффективности, но, так как расширение является редкой операцией, средняя эффективность выше.
- Стек, реализованный на основе связного списка, обеспечивает более стабильную эффективность.
**Пространственная сложность**
При инициализации массива система выделяет для него начальную емкость, которая может превышать фактические потребности. Кроме того, механизм расширения обычно осуществляется с определенным коэффициентом (например, в 2 раза), и емкость после расширения также может превышать фактические потребности. Поэтому **стек, реализованный на основе массива, может приводить к некоторым потерям пространства**.
Однако, так как узлы связного списка требуют дополнительного хранения указателей, **занимаемое ими пространство сравнительно больше**.
Таким образом, нельзя однозначно определить, какая реализация более экономична в плане памяти, необходимо анализировать конкретные ситуации.
## Типичные сценарии применения стека
- **Возврат и переход вперед в браузере, отмена и повтор в программном обеспечении**. Каждый раз, когда открывается новая веб-страница, браузер выполняет добавление предыдущей страницы в стек, что позволяет вернуться к ней с помощью операции возврата. Операция возврата фактически является выполнением удаления из стека. Если требуется поддержка как возврата, так и перехода вперед, необходимо использовать два стека.
- **Управление памятью программы**. Каждый раз при вызове функции система добавляет на вершину стека фрейм для записи контекстной информации функции. В рекурсивных функциях на этапе нисходящей рекурсии постоянно выполняется добавление в стек, а на этапе восходящей рекурсии -- удаление из стека.

View File

@@ -0,0 +1,31 @@
# Резюме
### Ключевые моменты
- Стек -- это структура данных, следующая принципу «первый вошел -- последний вышел», которая может быть реализована с помощью массива или связного списка.
- С точки зрения временной эффективности, реализация стека на основе массива имеет более высокую среднюю эффективность, но во время расширения временная сложность одной операции вставки ухудшается до $O(n)$. В сравнении с этим, реализация стека на основе связного списка обеспечивает более стабильную эффективность.
- С точки зрения пространственной эффективности, реализация стека на основе массива может привести к определенной степени потери пространства. Но следует отметить, что память, занимаемая узлами связного списка, больше, чем элементами массива.
- Очередь -- это структура данных, следующая принципу «первый пришел -- первый вышел», которая также может быть реализована с помощью массива или связного списка. В сравнении временной и пространственной эффективности выводы для очереди аналогичны выводам для стека.
- Двусторонняя очередь -- это очередь с большей степенью свободы, которая позволяет добавлять и удалять элементы с обоих концов.
### Вопросы и ответы
**В**: Реализованы ли функции перехода вперед и назад в браузере с помощью двусвязного списка?
Функции перехода вперед и назад в браузере по сути являются проявлением «стека». Когда пользователь посещает новую страницу, эта страница добавляется на вершину стека; когда пользователь нажимает кнопку «назад», эта страница извлекается с вершины стека. Использование двусторонней очереди позволяет удобно реализовать некоторые дополнительные операции, что упоминается в разделе «Двусторонняя очередь».
**В**: Нужно ли освобождать память узла после извлечения из стека?
Если извлеченный узел потребуется в дальнейшем, то освобождать память не нужно. Если он больше не понадобится, то в таких языках, как `Java` и `Python`, есть автоматическая сборка мусора, поэтому освобождать память вручную не требуется; в `C` и `C++` необходимо освобождать память вручную.
**В**: Двусторонняя очередь похожа на два стека, соединенных вместе. Каково ее назначение?
Двусторонняя очередь похожа на комбинацию стека и очереди или на два стека, соединенных вместе. Она представляет логику стека + очереди, поэтому может реализовать все применения стека и очереди, и при этом более гибка.
**В**: Как именно реализованы отмена (undo) и повтор (redo)?
Используются два стека: стек `A` для отмены и стек `B` для повтора.
1. Каждый раз, когда пользователь выполняет операцию, эта операция помещается в стек `A`, а стек `B` очищается.
2. Когда пользователь выполняет «отмену», последняя операция извлекается из стека `A` и помещается в стек `B`.
3. Когда пользователь выполняет «повтор», последняя операция извлекается из стека `B` и помещается в стек `A`.