# Стек
Стек (stack) -- это линейная структура данных, которая следует логике «первый вошел -- последний вышел».
Стек можно сравнить со стопкой тарелок на столе: чтобы достать тарелку снизу, нужно сначала убрать все тарелки сверху. Заменив тарелки на элементы различных типов (например, целые числа, символы, объекты и т. д.), мы получим структуру данных, называемую стеком.
Как показано на рисунке ниже, верх стопки элементов называется вершиной стека, а низ -- основанием стека. Операция добавления элемента на вершину стека называется вставка, а удаление элемента с вершины -- извлечение.

## Основные операции со стеком
Основные операции со стеком представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования. Здесь в качестве примера используются распространенные имена `push()`, `pop()`, `peek()`.
Таблица Эффективность операций со стеком
| Метод | Описание | Временная сложность |
| -------- | ------------------------------------------------ | ------------------- |
| `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 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 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 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 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 = 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()
/* 元素入栈 */
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 раза), и емкость после расширения также может превышать фактические потребности. Поэтому **стек, реализованный на основе массива, может приводить к некоторым потерям пространства**.
Однако, так как узлы связного списка требуют дополнительного хранения указателей, **занимаемое ими пространство сравнительно больше**.
Таким образом, нельзя однозначно определить, какая реализация более экономична в плане памяти, необходимо анализировать конкретные ситуации.
## Типичные сценарии применения стека
- **Возврат и переход вперед в браузере, отмена и повтор в программном обеспечении**. Каждый раз, когда открывается новая веб-страница, браузер выполняет добавление предыдущей страницы в стек, что позволяет вернуться к ней с помощью операции возврата. Операция возврата фактически является выполнением удаления из стека. Если требуется поддержка как возврата, так и перехода вперед, необходимо использовать два стека.
- **Управление памятью программы**. Каждый раз при вызове функции система добавляет на вершину стека фрейм для записи контекстной информации функции. В рекурсивных функциях на этапе нисходящей рекурсии постоянно выполняется добавление в стек, а на этапе восходящей рекурсии -- удаление из стека.