Stack

Стек — структура данных, в которой элементы добавляются и удаляются только с
одного конца.

Этот конец называется верхушкой стека.

Главное правило стека:

LIFO = Last In, First Out

То есть:

последний добавленный элемент удаляется первым

Пример:

push(7)
push(3)
push(9)
push(1)

Состояние стека:

bottom              top
  7    3    9    1

Если выполнить:

pop()

то будет удалён 1, потому что он находится на верхушке.

Термины

stack       -> стек
top         -> верхний элемент
bottom      -> нижний элемент
size        -> количество элементов в стеке
push(x)     -> положить x на верхушку
pop()       -> снять верхний элемент
peek()      -> посмотреть верхний элемент без удаления
isEmpty()   -> проверить, пуст ли стек

Стек задаётся тремя вещами:

1. набор элементов
2. порядок добавления элементов
3. верхушка, через которую идут основные операции

Структура стека

Структура отвечает на вопрос:

Что внутри и как расположено?

У стека есть:

элементы
верхушка
нижняя часть
порядок добавления
размер

Стек можно представить как вертикальную стопку:

top
 ↓
[1]
[9]
[3]
[7]
 ↑
bottom

Новые элементы кладутся сверху. Удаляются тоже сверху.

Свойства

Короткая карта свойств стека как структуры:

верхушка            -> основная точка доступа к стеку
LIFO                -> последним вошёл, первым вышел
ограниченный доступ -> доступен только верхний элемент
размер              -> количество элементов в стеке
порядок добавления  -> порядок влияет на порядок удаления
пустота             -> стек может не содержать элементов

Конечность здесь является общей предпосылкой:

конкретный стек содержит конечное число элементов

Но для стека важнее операциональная форма этой конечности:

size = n
пустой стек = size == 0
непустой стек = size > 0
верхушка существует только у непустого стека

Важно отделять свойства стека от характеристик конкретного стека:

свойство:       у стека есть верхушка
характеристика: верхний элемент этого стека = 1

свойство:       у стека есть размер
характеристика: размер этого стека = 4

Верхушка

Верхушка — это единственная позиция, через которую стек обычно предоставляет
доступ к элементам.

top(stack)

Для стека:

[7, 3, 9, 1]

верхний элемент:

top = 1

LIFO

Стек работает по правилу:

Last In, First Out

Если элементы добавлены в таком порядке:

7, 3, 9, 1

то удаляться они будут в обратном порядке:

1, 9, 3, 7

Ограниченный доступ

В отличие от массива, стек не даёт произвольный доступ по индексу как базовую
операцию.

arr[i]      -> обычная операция массива
stack[i]    -> не базовая операция стека

Стек скрывает внутренние позиции и даёт работать с верхушкой:

push
pop
peek

Размер

Размер стека — это количество элементов в нём.

size(stack) = n

Размер меняется при операциях:

push(x) -> size увеличивается на 1
pop()   -> size уменьшается на 1

Пустота

Стек может быть пустым.

stack = []
size = 0

Для пустого стека нельзя корректно выполнить:

pop()
peek()

если реализация явно не задаёт специальное поведение для этих случаев.

Базовые операции

Создание

stack = []

Обычно:

O(1)

Push

Добавить элемент на верхушку стека:

push(stack, x)

Пример:

stack = [7, 3, 9]
push(1)
stack = [7, 3, 9, 1]

Обычно:

O(1)

Если стек реализован через динамический массив, отдельная операция может стоить
O(n) при расширении внутреннего хранилища, но амортизированно это:

amortized O(1)

Pop

Удалить и вернуть верхний элемент:

pop(stack)

Пример:

stack = [7, 3, 9, 1]
pop() -> 1
stack = [7, 3, 9]

Условие:

size(stack) > 0

Обычно:

O(1)

Peek / Top

Посмотреть верхний элемент без удаления:

peek(stack)
top(stack)

Пример:

stack = [7, 3, 9, 1]
peek() -> 1
stack = [7, 3, 9, 1]

Условие:

size(stack) > 0

Сложность:

O(1)

Is Empty

Проверить, пуст ли стек:

isEmpty(stack)

Пример:

[]          -> true
[7, 3, 9]   -> false

Сложность:

O(1)

Size

Получить количество элементов:

size(stack)

Обычно:

O(1)

если размер хранится отдельно.

Проход по стеку

Если нужно обработать все элементы через стековые операции, стек можно
постепенно разбирать:

while not isEmpty(stack):
    x = pop(stack)
    use x

Сложность:

O(n)

Важно: такой проход изменяет стек, потому что pop() удаляет элементы.

Таблица сложностей

Операция Сложность
создание O(1)
push(x) O(1) / amortized O(1)
pop() O(1)
peek() / top() O(1)
isEmpty() O(1)
size() O(1)
проход с удалением всех элементов O(n)
поиск произвольного значения O(n)

Реализация

Стек — это абстрактная структура данных. Его можно реализовать разными способами.

Через динамический массив

Верхушка стека обычно совпадает с концом массива:

stack = [7, 3, 9, 1]
                  ^
                 top

Тогда:

push(x) -> append(x)
pop()   -> remove last element
peek()  -> last element

Такой вариант даёт быстрые операции на верхушке.

Через связный список

Верхушка стека может быть головой списка:

top -> [1] -> [9] -> [3] -> [7]

Тогда:

push(x) -> добавить новый узел в голову
pop()   -> удалить голову
peek()  -> прочитать голову

Стек и массив

Массив даёт доступ по индексу:

arr[i]

Стек даёт доступ к верхушке:

top(stack)

Массив удобен, когда важны позиции и произвольный доступ.

Стек удобен, когда важен порядок вложенности, отката или ожидания:

последнее открытое закрывается первым
последнее действие отменяется первым
последняя нерешённая задача обрабатывается первой

Типичные применения

проверка скобок
отмена действий
обход графа в глубину
обработка рекурсии
монотонный стек
Min Stack
вычисление выражений

Формальная схема

Stack = {
  elements: e1, e2, ..., en,
  top: en,
  size: n,
  rule: LIFO
}

Операции стека изменяют или используют одну из трёх сущностей:

верхушка
размер
порядок удаления

Примеры:

push(x)    -> меняет верхушку и увеличивает размер
pop()      -> возвращает верхушку и уменьшает размер
peek()     -> читает верхушку без изменения стека
isEmpty()  -> проверяет размер

Минимальная формулировка:

стек = структура с доступом к последнему добавленному элементу
Прокрутить вверх