Add ru version (#1865)

* Add Russian docs site baseline

* Add Russian localized codebase

* Polish Russian code wording

* Update ru code translation.

* Update code translation and chapter covers.

* Fix pythontutor extraction.

* Add README and landing page.

* placeholder of profiles

* Use figures of English version

* Remove chapter paperbook
This commit is contained in:
Yudong Jin
2026-03-28 04:24:07 +08:00
committed by GitHub
parent 2ca570cc33
commit 772183705e
1958 changed files with 108186 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
# Стек и очередь
![Стек и очередь](../assets/covers/chapter_stack_and_queue.jpg)
!!! abstract
Стек похож на стопку кошек, а очередь - на очередь из кошек.
Эти две структуры соответственно представляют отношения "последним пришел - первым вышел" и "первым пришел - первым вышел".
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+423
View File
@@ -0,0 +1,423 @@
# Очередь
<u>Очередь (queue)</u> - это линейная структура данных, подчиняющаяся правилу "первым пришел - первым вышел". Как видно из названия, очередь моделирует обычную ситуацию ожидания: новые люди непрерывно присоединяются к хвосту очереди, а стоящие в начале по одному уходят.
Как показано на рисунке ниже, начало очереди называется "головой очереди", а конец - "хвостом очереди"; операцию добавления элемента в хвост называют "enqueue", а операцию удаления элемента из головы - "dequeue".
![Правило FIFO для очереди](queue.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 и traverse, поэтому можно использовать 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%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8E%D1%8E%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20deque%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%D1%8E%D1%82%20%D0%BA%D0%B0%D0%BA%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%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%BD%D0%B0%D1%81%D1%82%D0%BE%D1%8F%D1%89%D0%B8%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%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%D1%81%D1%8F%20%D0%B8%D0%BC%20%D0%BD%D0%B5%20%D1%81%D0%BB%D0%B8%D1%88%D0%BA%D0%BE%D0%BC%20%D1%83%D0%B4%D0%BE%D0%B1%D0%BD%D0%BE%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%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%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%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%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20front%20%3D%22%2C%20front%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%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%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22que%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%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%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%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%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%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Реализация очереди
Чтобы реализовать очередь, нам нужна такая структура данных, которая позволяет добавлять элементы с одного конца и удалять их с другого; и связный список, и массив этим требованиям удовлетворяют.
### Реализация на основе связного списка
Как показано на рисунке ниже, мы можем рассматривать "головной узел" и "хвостовой узел" связного списка как "голову очереди" и "хвост очереди" соответственно, договорившись, что добавлять узлы можно только в хвост, а удалять - только из головы.
=== "LinkedListQueue"
![Операции enqueue и dequeue в реализации очереди на связном списке](queue.assets/linkedlist_queue_step1.png)
=== "push()"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
=== "pop()"
![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png)
Ниже приведен код реализации очереди на связном списке:
```src
[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{}
```
### Реализация на основе массива
Удаление первого элемента из массива имеет временную сложность $O(n)$ , из-за чего операция dequeue оказывается неэффективной. Однако этого можно избежать с помощью следующего приема.
Мы можем использовать переменную `front` , указывающую на индекс элемента в голове очереди, и поддерживать переменную `size` , которая хранит длину очереди. Определим `rear = front + size` ; эта формула дает позицию `rear`, указывающую на ячейку сразу после хвоста очереди.
Исходя из этого, **эффективный диапазон элементов массива равен `[front, rear - 1]`**, а различные операции реализуются, как показано на рисунке ниже.
- Операция enqueue: записать входной элемент по индексу `rear` и увеличить `size` на 1.
- Операция dequeue: просто увеличить `front` на 1 и уменьшить `size` на 1.
Можно увидеть, что и enqueue, и dequeue требуют всего одной операции, а значит обе имеют временную сложность $O(1)$ .
=== "ArrayQueue"
![Операции enqueue и dequeue в реализации очереди на массиве](queue.assets/array_queue_step1.png)
=== "push()"
![array_queue_push](queue.assets/array_queue_step2_push.png)
=== "pop()"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
Ты можешь заметить еще одну проблему: при непрерывных операциях enqueue и dequeue значения `front` и `rear` оба движутся вправо, и **когда они доходят до конца массива, дальше сдвигаться уже нельзя**. Чтобы решить эту проблему, можно рассматривать массив как "кольцевой массив", у которого начало и конец соединены.
Для кольцевого массива нужно сделать так, чтобы `front` или `rear`, перешагнув конец массива, сразу возвращались к его началу и продолжали движение. Такую периодичность удобно реализовать с помощью операции взятия остатка, как показано в коде ниже:
```src
[file]{array_queue}-[class]{array_queue}-[func]{}
```
Даже такая реализация очереди остается ограниченной: ее длина неизменяема. Однако это несложно исправить, заменив массив на динамический массив и тем самым введя механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно.
Выводы сравнения двух реализаций в целом такие же, как и для стека, поэтому здесь мы не будем повторяться.
## Типичные применения очереди
- **Заказы на Taobao**. После оформления заказа покупателем заказ попадает в очередь, а затем система обрабатывает заказы по порядку. Во время крупных распродаж, таких как Double 11, за короткое время возникает огромный поток заказов, и высокая конкурентная нагрузка становится ключевой инженерной проблемой.
- **Различные отложенные задачи**. Любой сценарий, где нужно реализовать принцип "кто раньше пришел, тот раньше обслуживается", например очередь заданий принтера или очередь блюд на кухне ресторана, хорошо моделируется очередью, которая эффективно поддерживает нужный порядок обработки.
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+430
View File
@@ -0,0 +1,430 @@
# Стек
<u>Стек (stack)</u> - это линейная структура данных, подчиняющаяся логике "последним пришел - первым вышел".
Стек можно сравнить со стопкой тарелок на столе. Если разрешено перемещать только одну тарелку за раз, то, чтобы достать тарелку снизу, сначала придется по одной убрать все тарелки сверху. Если заменить тарелки различными элементами (например целыми числами, символами, объектами и т.д.), получится структура данных "стек".
Как показано на рисунке ниже, верхнюю часть стопки элементов мы называем "вершиной стека", а нижнюю - "основанием стека". Операция добавления элемента на вершину называется "push", а операция удаления верхнего элемента - "pop".
![Правило LIFO для стека](stack.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%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%BD%D0%B5%D1%82%20%D0%B2%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20list%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%B0%D0%BA%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%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%D1%81%D1%82%D0%B5%D0%BA%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Реализация стека
Чтобы глубже понять механизм работы стека, попробуем самостоятельно реализовать класс стека.
Стек подчиняется принципу LIFO, поэтому мы можем добавлять и удалять элементы только на вершине. Однако и массив, и связный список позволяют добавлять и удалять элементы в произвольном месте. **Следовательно, стек можно рассматривать как ограниченный массив или связный список**. Иными словами, мы можем "скрыть" часть нерелевантных операций массива или списка, так чтобы внешняя логика соответствовала свойствам стека.
### Реализация на основе связного списка
Если реализовывать стек на основе связного списка, то головной узел списка можно рассматривать как вершину стека, а хвостовой - как основание.
Как показано на рисунке ниже, для операции push достаточно вставить элемент в голову связного списка. Такой способ вставки называется "вставкой в голову". Для операции pop достаточно удалить головной узел из списка.
=== "LinkedListStack"
![Операции push и pop в реализации стека на связном списке](stack.assets/linkedlist_stack_step1.png)
=== "push()"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
Ниже приведен пример кода реализации стека на основе связного списка:
```src
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
```
### Реализация на основе массива
Если реализовывать стек на основе массива, то хвост массива можно рассматривать как вершину стека. Как показано на рисунке ниже, операции push и pop соответствуют добавлению элемента в конец массива и удалению элемента из конца, обе имеют временную сложность $O(1)$ .
=== "ArrayStack"
![Операции push и pop в реализации стека на массиве](stack.assets/array_stack_step1.png)
=== "push()"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
Поскольку количество элементов, помещаемых в стек, может непрерывно расти, мы можем использовать динамический массив и тем самым не заниматься расширением массива вручную. Ниже приведен пример кода:
```src
[file]{array_stack}-[class]{array_stack}-[func]{}
```
## Сравнение двух реализаций
**Поддерживаемые операции**
Обе реализации поддерживают все операции, определенные для стека. Реализация на массиве дополнительно позволяет выполнять произвольный доступ, но это уже выходит за рамки определения стека и обычно не используется.
**Временная эффективность**
В реализации на массиве и push, и pop выполняются в заранее выделенной непрерывной памяти, которая хорошо использует локальность кэша, поэтому такие операции обычно эффективнее. Однако если при push емкость массива оказывается превышена, включается механизм расширения, и временная сложность конкретно этой операции push становится $O(n)$ .
В реализации на связном списке расширение выполняется очень гибко, и проблемы падения эффективности из-за расширения массива здесь нет. Но сама операция push требует инициализации объекта-узла и изменения указателей, поэтому в среднем она немного менее эффективна. Впрочем, если помещаемые в стек элементы уже сами являются объектами-узлами, шаг инициализации можно пропустить и тем самым повысить эффективность.
Итак, когда элементами, помещаемыми и извлекаемыми из стека, являются базовые типы данных, например `int` или `double` , можно сделать следующие выводы.
- Стек на основе массива теряет в эффективности в моменты расширения, но поскольку расширение происходит редко, его средняя эффективность выше.
- Стек на основе связного списка может обеспечивать более стабильную производительность.
**Пространственная эффективность**
При инициализации списка система выделяет "начальную емкость", которая может превышать реальную потребность. Кроме того, механизм расширения обычно увеличивает емкость по некоторому коэффициенту (например в 2 раза), и расширенная емкость тоже может оказаться больше фактически необходимой. Поэтому **реализация стека на основе массива может приводить к некоторым потерям памяти**.
Однако, поскольку узлы связного списка должны дополнительно хранить указатели, **узлы списка сами по себе занимают больше пространства**.
В итоге нельзя просто сказать, какая из реализаций более экономна по памяти; это нужно анализировать в контексте конкретной задачи.
## Типичные применения стека
- **Кнопки "назад" и "вперед" в браузере, undo и redo в программах**. Каждый раз, когда мы открываем новую страницу, браузер помещает предыдущую страницу в стек, чтобы по операции "назад" можно было вернуться к ней. Операция "назад" по сути является pop. Если нужно одновременно поддерживать и "назад", и "вперед", то обычно используются два стека.
- **Управление памятью программы**. Каждый раз при вызове функции система помещает на вершину стека стековый кадр, в котором хранится контекст функции. В рекурсивной функции на этапе углубления рекурсии непрерывно выполняются push-операции, а на этапе возврата - pop-операции.
@@ -0,0 +1,31 @@
# Краткие итоги
### Основные моменты
- Стек - это структура данных, следующая правилу "последним пришел - первым вышел", и его можно реализовать с помощью массива или связного списка.
- С точки зрения временной эффективности реализация стека на массиве обычно работает быстрее в среднем, но во время расширения емкости временная сложность отдельной операции push может ухудшаться до $O(n)$ . Напротив, реализация стека на связном списке дает более стабильные характеристики.
- С точки зрения использования памяти реализация стека на массиве может приводить к некоторой потере пространства. Однако следует учитывать, что узлы связного списка занимают больше памяти, чем элементы массива.
- Очередь - это структура данных, следующая правилу "первым пришел - первым вышел", и ее также можно реализовать с помощью массива или связного списка. Сравнение временной и пространственной эффективности для очереди в целом приводит к тем же выводам, что и для стека.
- Двусторонняя очередь - это очередь с более высокой степенью свободы, которая позволяет добавлять и удалять элементы с обеих сторон.
### Q & A
**Q**: Реализованы ли кнопки "вперед" и "назад" в браузере с помощью двусвязного списка?
По сути, функция переходов "вперед/назад" в браузере отражает логику "стека". Когда пользователь открывает новую страницу, она помещается на вершину стека; когда пользователь нажимает кнопку "назад", эта страница снимается с вершины стека. Двусторонняя очередь позволяет удобно реализовать некоторые дополнительные операции, об этом уже упоминалось в разделе "Двусторонняя очередь".
**Q**: Нужно ли освобождать память узла после извлечения его из стека?
Если извлеченный узел еще понадобится, память освобождать не нужно. Если он больше не нужен, то в языках `Java` и `Python` есть автоматический сборщик мусора, поэтому ручное освобождение памяти не требуется; в `C` и `C++` память нужно освобождать вручную.
**Q**: Двусторонняя очередь выглядит как два соединенных стека. Для чего она нужна?
Двусторонняя очередь похожа на комбинацию стека и очереди или на два соединенных стека. Она выражает логику "стек + очередь", поэтому может покрыть все применения стека и очереди и при этом остается более гибкой.
**Q**: Как именно реализуются отмена (undo) и повтор (redo)?
Используются два стека: стек `A` для отмены и стек `B` для повтора.
1. Каждый раз, когда пользователь выполняет действие, это действие помещается в стек `A` , а стек `B` очищается.
2. Когда пользователь выполняет "undo", последнее действие извлекается из стека `A` и помещается в стек `B` .
3. Когда пользователь выполняет "redo", последнее действие извлекается из стека `B` и помещается обратно в стек `A` .