mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-13 16:49:44 +08:00
First version.
This commit is contained in:
388
ru/docs/chapter_stack_and_queue/deque.md
Normal file
388
ru/docs/chapter_stack_and_queue/deque.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# Двусторонняя очередь
|
||||
|
||||
В очереди мы можем удалять элементы только из головы или добавлять элементы в хвост. Как показано на рисунке ниже, <u>двусторонняя очередь (double-ended queue)</u> предоставляет большую гибкость, позволяя выполнять операции добавления или удаления элементов как в голове, так и в хвосте.
|
||||
|
||||

|
||||
|
||||
## Основные операции с двусторонней очередью
|
||||
|
||||
Основные операции с двусторонней очередью представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования.
|
||||
|
||||
<p align="center"> Таблица <id> Эффективность операций двусторонней очереди </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
|
||||
11
ru/docs/chapter_stack_and_queue/index.md
Normal file
11
ru/docs/chapter_stack_and_queue/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
```markdown
|
||||
# Стек и очередь
|
||||
|
||||

|
||||
|
||||
!!! abstract
|
||||
|
||||
Стек можно сравнить со стопкой котиков, а очередь — с котиками, стоящими в очереди.
|
||||
|
||||
Они представляют логику «первый вошел — последний вышел» и «первый пришел — первый вышел» соответственно.
|
||||
```
|
||||
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**. После размещения заказа покупателем заказ добавляется в очередь, после чего система последовательно обрабатывает заказы из очереди. Во время распродажи "Двойной одиннадцатый" за короткое время генерируется огромное количество заказов, и высокая параллельность становится ключевой проблемой, которую необходимо решить инженерам.
|
||||
- **Различные задачи в очереди**. Любой сценарий, требующий реализации функции "первым пришел -- первым обслужен", например, очередь задач принтера, очередь выдачи блюд в ресторане и т. д. Очередь в этих сценариях эффективно поддерживает порядок обработки.
|
||||
430
ru/docs/chapter_stack_and_queue/stack.md
Normal file
430
ru/docs/chapter_stack_and_queue/stack.md
Normal file
@@ -0,0 +1,430 @@
|
||||
# Стек
|
||||
|
||||
<u>Стек (stack)</u> -- это линейная структура данных, которая следует логике «первый вошел -- последний вышел».
|
||||
|
||||
Стек можно сравнить со стопкой тарелок на столе: чтобы достать тарелку снизу, нужно сначала убрать все тарелки сверху. Заменив тарелки на элементы различных типов (например, целые числа, символы, объекты и т. д.), мы получим структуру данных, называемую стеком.
|
||||
|
||||
Как показано на рисунке ниже, верх стопки элементов называется вершиной стека, а низ -- основанием стека. Операция добавления элемента на вершину стека называется вставка, а удаление элемента с вершины -- извлечение.
|
||||
|
||||

|
||||
|
||||
## Основные операции со стеком
|
||||
|
||||
Основные операции со стеком представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования. Здесь в качестве примера используются распространенные имена `push()`, `pop()`, `peek()`.
|
||||
|
||||
<p align="center"> Таблица <id> Эффективность операций со стеком </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"
|
||||

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

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

|
||||
|
||||
Ниже приведен пример кода для реализации стека на основе связного списка:
|
||||
|
||||
```src
|
||||
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
|
||||
```
|
||||
|
||||
### Реализация на основе массива
|
||||
|
||||
При использовании для реализации стека массива можно считать конец массива вершиной стека. Как показано на рисунке ниже, операции вставки и извлечения соответствуют добавлению и удалению элементов в конце массива. Временная сложность этих операций составляет $O(1)$.
|
||||
|
||||
=== "ArrayStack"
|
||||

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

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

|
||||
|
||||
Поскольку количество вставляемых элементов может постоянно увеличиваться, можно использовать динамический массив, чтобы не заниматься расширением массива самостоятельно. Ниже приведен пример кода:
|
||||
|
||||
```src
|
||||
[file]{array_stack}-[class]{array_stack}-[func]{}
|
||||
```
|
||||
|
||||
## Сравнение двух реализаций
|
||||
|
||||
**Поддерживаемые операции**
|
||||
|
||||
Обе реализации поддерживают все операции, определенные для стека. Реализация на основе массива дополнительно поддерживает произвольный доступ, но это выходит за рамки определения стека, поэтому обычно не используется.
|
||||
|
||||
**Временная сложность**
|
||||
|
||||
В реализации на основе массива операции добавления и удаления элемента выполняются в заранее выделенной непрерывной памяти, что обеспечивает хорошую локальность кеша и, следовательно, высокую эффективность. Однако, если при добавлении элемента превышается емкость массива, срабатывает механизм расширения, что приводит к увеличению временной сложности данной операции до $O(n)$.
|
||||
|
||||
В реализации на основе связного списка расширение происходит очень гибко, и не возникает проблемы снижения эффективности, как в случае расширения массива. Однако операция добавления элемента требует инициализации объекта узла и изменения указателя, что делает ее относительно менее эффективной. Тем не менее, если добавляемый элемент уже является объектом узла, можно избежать шага инициализации, что повысит эффективность.
|
||||
|
||||
Таким образом, если элементы операций добавления и удаления являются примитивными типами данных, такими как `int` или `double`, можно сделать следующие выводы:
|
||||
|
||||
- Стек, реализованный на основе массива, при срабатывании механизма расширения теряет в эффективности, но, так как расширение является редкой операцией, средняя эффективность выше.
|
||||
- Стек, реализованный на основе связного списка, обеспечивает более стабильную эффективность.
|
||||
|
||||
**Пространственная сложность**
|
||||
|
||||
При инициализации массива система выделяет для него начальную емкость, которая может превышать фактические потребности. Кроме того, механизм расширения обычно осуществляется с определенным коэффициентом (например, в 2 раза), и емкость после расширения также может превышать фактические потребности. Поэтому **стек, реализованный на основе массива, может приводить к некоторым потерям пространства**.
|
||||
|
||||
Однако, так как узлы связного списка требуют дополнительного хранения указателей, **занимаемое ими пространство сравнительно больше**.
|
||||
|
||||
Таким образом, нельзя однозначно определить, какая реализация более экономична в плане памяти, необходимо анализировать конкретные ситуации.
|
||||
|
||||
## Типичные сценарии применения стека
|
||||
|
||||
- **Возврат и переход вперед в браузере, отмена и повтор в программном обеспечении**. Каждый раз, когда открывается новая веб-страница, браузер выполняет добавление предыдущей страницы в стек, что позволяет вернуться к ней с помощью операции возврата. Операция возврата фактически является выполнением удаления из стека. Если требуется поддержка как возврата, так и перехода вперед, необходимо использовать два стека.
|
||||
- **Управление памятью программы**. Каждый раз при вызове функции система добавляет на вершину стека фрейм для записи контекстной информации функции. В рекурсивных функциях на этапе нисходящей рекурсии постоянно выполняется добавление в стек, а на этапе восходящей рекурсии -- удаление из стека.
|
||||
31
ru/docs/chapter_stack_and_queue/summary.md
Normal file
31
ru/docs/chapter_stack_and_queue/summary.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Резюме
|
||||
|
||||
### Ключевые моменты
|
||||
|
||||
- Стек -- это структура данных, следующая принципу «первый вошел -- последний вышел», которая может быть реализована с помощью массива или связного списка.
|
||||
- С точки зрения временной эффективности, реализация стека на основе массива имеет более высокую среднюю эффективность, но во время расширения временная сложность одной операции вставки ухудшается до $O(n)$. В сравнении с этим, реализация стека на основе связного списка обеспечивает более стабильную эффективность.
|
||||
- С точки зрения пространственной эффективности, реализация стека на основе массива может привести к определенной степени потери пространства. Но следует отметить, что память, занимаемая узлами связного списка, больше, чем элементами массива.
|
||||
- Очередь -- это структура данных, следующая принципу «первый пришел -- первый вышел», которая также может быть реализована с помощью массива или связного списка. В сравнении временной и пространственной эффективности выводы для очереди аналогичны выводам для стека.
|
||||
- Двусторонняя очередь -- это очередь с большей степенью свободы, которая позволяет добавлять и удалять элементы с обоих концов.
|
||||
|
||||
### Вопросы и ответы
|
||||
|
||||
**В**: Реализованы ли функции перехода вперед и назад в браузере с помощью двусвязного списка?
|
||||
|
||||
Функции перехода вперед и назад в браузере по сути являются проявлением «стека». Когда пользователь посещает новую страницу, эта страница добавляется на вершину стека; когда пользователь нажимает кнопку «назад», эта страница извлекается с вершины стека. Использование двусторонней очереди позволяет удобно реализовать некоторые дополнительные операции, что упоминается в разделе «Двусторонняя очередь».
|
||||
|
||||
**В**: Нужно ли освобождать память узла после извлечения из стека?
|
||||
|
||||
Если извлеченный узел потребуется в дальнейшем, то освобождать память не нужно. Если он больше не понадобится, то в таких языках, как `Java` и `Python`, есть автоматическая сборка мусора, поэтому освобождать память вручную не требуется; в `C` и `C++` необходимо освобождать память вручную.
|
||||
|
||||
**В**: Двусторонняя очередь похожа на два стека, соединенных вместе. Каково ее назначение?
|
||||
|
||||
Двусторонняя очередь похожа на комбинацию стека и очереди или на два стека, соединенных вместе. Она представляет логику стека + очереди, поэтому может реализовать все применения стека и очереди, и при этом более гибка.
|
||||
|
||||
**В**: Как именно реализованы отмена (undo) и повтор (redo)?
|
||||
|
||||
Используются два стека: стек `A` для отмены и стек `B` для повтора.
|
||||
|
||||
1. Каждый раз, когда пользователь выполняет операцию, эта операция помещается в стек `A`, а стек `B` очищается.
|
||||
2. Когда пользователь выполняет «отмену», последняя операция извлекается из стека `A` и помещается в стек `B`.
|
||||
3. Когда пользователь выполняет «повтор», последняя операция извлекается из стека `B` и помещается в стек `A`.
|
||||
Reference in New Issue
Block a user