Что такое стек
Стек — это вид структуры данных, в котором элементы упорядочены и добавление или удаление элементов происходит с верхней части стека — по принципу «Последним пришел — первым ушел» (LIFO). Реализовать стек можно, например, С, JavaScript, С++, Java, Python, Lisp или C#.
Как работает стек
Визуально это похоже на стопку тарелок. Первую помытую взять сложно, как и находящуюся где-то внутри, проще начать с последней.
Стек придерживается линейной связи, в которой данные располагаются в строгом порядке и нарушить данную последовательность невозможно. Данные, оказавшиеся в стеке последними, должны использоваться первыми.
Четыре основные операции над стеком:
- push — добавление элемента
- pop — удаление верхнего элемента
- isEmpty — проверка стека на наличие в нем элементов
- peek — возвращение последнего элемента, не удаляя его.
Разновидности стеков
Существует несколько видов стека:
- Стек вызовов: область памяти, в которой хранится информация о точках перехода между элементами программного кода. Например: скрипт вызывает функцию, интерпретатор записывает ее в стек вызовов. Любые функции, вызванные этой функцией, добавляются в стек и выполняются по очереди. Когда основная функция отработает, интерпретатор извлечет ее из стека вызовов и продолжит выполнять код с того места, где он остановился ранее.
- Стек на основе массива: в этом типе стека элементы хранятся в массиве фиксированного размера, и доступ к ним осуществляется по индексу. Главным недостатком является ограниченность размера массива, что может привести к проблемам с добавлением новых элементов, если стек заполнен. Также этот тип стека может быть неэффективным, если требуется частое добавление и удаление элементов.
- Стек на основе связного списка: в нем элементы хранятся в связном списке, где каждый элемент содержит указатель на следующий элемент. Этот тип стека позволяет эффективно добавлять и удалять элементы, но может быть менее эффективным при доступе к элементам по индексу. Одним из преимуществ стека на основе связного списка является его динамичность — размер стека может быть изменен в любой момент времени.
- Стек на основе двухсторонней очереди (Deque): в этом случае элементы хранятся в двухсторонней очереди, где элементы могут быть добавлены или удалены с обоих концов.
- Стек на основе дерева: здесь элементы хранятся в дереве, где каждый узел содержит указатель на родительский узел. Этот тип стека может быть полезен для решения задач, связанных с деревьями, таких как обход дерева в глубину.
- Стек на основе кучи (Heap): в этом виде стека элементы хранятся в куче, где каждый элемент имеет приоритет. Этот тип стека может быть полезен для решения задач, связанных с приоритетами, таких как поиск минимального или максимального элемента.
Переполнение стека
Переполнение стека — это ситуация, когда стек заполняется элементами, превышающими его максимальный размер, установленный при создании стека. Когда стек переполняется, он не может больше хранить элементы, и любая попытка добавления нового элемента приведет к ошибке.
Причины переполнения стека
Рекурсия: если функция вызывает саму себя слишком много раз, то каждый новый вызов функции добавляет новый элемент в стек. Если глубина рекурсии слишком большая, то стек может переполниться.
Бесконечный цикл: если в цикле добавляются новые элементы в стек, то если цикл не останавливается, то стек может быстро заполниться.
Неправильно написанный код: некоторые ошибки в коде могут привести к тому, что стек будет заполняться слишком быстро. Например, если функция добавляет элементы в стек, но не удаляет их, то стек будет быстро заполнен.
Недостаточный размер стека: если размер стека недостаточно большой для хранения всех элементов, которые добавляются в него, то стек может переполниться.
Опасности переполнения стека
- Падение программы: переполнение стека может привести к аварийному завершению программы, что может привести к потере данных или другим негативным последствиям.
- Потеря данных: если стек переполнен, то данные, которые не помещаются в стек, могут быть потеряны.
- Уязвимость безопасности: переполнение стека может привести к уязвимостям безопасности, так как злоумышленник может использовать переполнение стека для выполнения вредоносного кода или других опасных действий.
- Непредсказуемое поведение программы: переполнение стека иногда запускает непредсказуемое поведение программы, включая зависание программы или ошибки в работе.
Важно следить за использованием стека и предотвращать его переполнение, например, путем использования итерационных алгоритмов вместо рекурсии и проверки глубины стека.
Реализация стека
Стек может быть выполнен с помощью связанного списка, массива фиксированной длины или динамического массива.
Пример стека на массиве в JavaScript
const arr = [];
//Добавляем элементы в массив
arr.push("Один");
console.log(arr); // выведет ["Один"]
arr.push('Два');
console.log(); // выведет ["Один", "Два"]
//Удаляем элемент
arr.pop();
console.log(arr); // выведет ["Один"]
//Добавим еще один элемент
arr.push("Сосиска");
//Отобразим последний элемент, не удаляя его
console.log(arr.peek()); // выведет Сосиска
//Проверим, пуст ли наш стек
console.log(arr.isEmpty()); // выведет false
Пример стека на связанном списке в Python
class Node:
def __init__(self, data=None):
self.data = data
self.next = None
class LinkedStack:
def __init__(self):
self.top = None
# Проверим, пуст ли наш стек
def is_empty(self):
return self.top is None
# Добавляем элемент в стек
def push(self, item):
new_node = Node(item)
new_node.next = self.top
self.top = new_node
# Удаляем элемент из вершины стека и возвращаем его
def pop(self):
if self.is_empty():
raise Exception("Stack is empty")
item = self.top.data
self.top = self.top.next
return item
# Отобразим последний элемент, не удаляя его
def peek(self):
if self.is_empty():
raise Exception("Stack is empty")
return self.top.data
Класс Node представляет узел связанного списка, а класс LinkedStack использует узлы, чтобы создать стек. Каждый узел содержит данные (data) и ссылку на следующий узел (next). При вызове метода push, создается новый узел с переданным элементом, и ссылка на этот узел устанавливается в вершину стека (top). При вызове метода pop, элемент извлекается из вершины стека, и вершина обновляется, чтобы указывать на следующий узел в списке.