Menu

Stack в C#: Push, Pop, Peek, отмена действий и проверка скобок

Stack<T> это коллекция «последним пришёл, первым вышел»: последним добавленный элемент выходит первым. Push, Pop и Peek, исключение пустого стека и TryPop, почему стек перебирается в обратном порядке и два классических применения: история отмены и проверка сбалансированных скобок.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Stack<T> это стопка: вы кладёте элементы наверх через Push и снимаете их сверху через Pop, поэтому последний положенный элемент выходит первым (LIFO). Доступна только вершина, и каждая операция с ней выполняется за постоянное время.

Push, Pop и Peek

Вывод:

On top: green
Count: 3
Took green
On top: red
Took red
Took blue
Count: 0

green положили последним, поэтому он снимается первым. Peek возвращает вершину, не меняя стек; так можно посмотреть, что даст Pop, прежде чем решить, забирать ли элемент.

Исключение пустого стека и TryPop

Извлечение или просмотр вершины пустого стека выбрасывает InvalidOperationException. Чаще всего это случается в парсерах и алгоритмах, получивших на вход больше закрывающих элементов, чем открывающих.

Вывод:

Caught InvalidOperationException
True 10
False 0

TryPop и TryPeek (.NET Core 2.0 и новее) возвращают false для пустого стека и задают переменной out значение по умолчанию, здесь 0. В .NET Framework сначала проверяйте Count > 0.

Порядок перебора: сначала вершина

Перебор стека ничего не удаляет и идёт сверху вниз, в том порядке, в каком элементы вернул бы Pop:

Вывод:

checkout products home 
checkout > products > home
True
home
checkout

Перевёрнутая копия многих застаёт врасплох: конструктор принимает любой IEnumerable<T> и кладёт его элементы по порядку, а стек перебирается с вершины, поэтому старая вершина оказывается на дне копии. Если сначала перевернуть последовательность (Reverse() из LINQ возвращает элементы начиная со дна), получится копия с той же вершиной.

Если положить список элементов в новый стек, они тоже перевернутся, и это быстрый способ перевернуть последовательность: new Stack<char>("hello") отдаёт обратно o, l, l, e, h.

Пример: история отмены

Редакторы кладут каждое изменение в стек. Отмена извлекает последнее изменение и откатывает его; повтор хранит второй стек отменённых изменений.

Вывод:

Hello, world!
Hello, world
Hello
Hello, world

Хранение целых снимков это самый простой вариант. Настоящие редакторы вместо этого кладут небольшие объекты-команды (что вставлено и куда), у каждого из которых есть метод для отката, но два стека работают так же.

Пример: сбалансированные скобки

Проверка, что (, [ и { закрыты в правильном порядке, это стандартное упражнение на стек, и та же логика работает внутри каждого компилятора и JSON-парсера.

Вывод:

"f(a[i], {x: 1})" -> True
"(]" -> False
"((a)" -> False
"a)b(" -> False
"" -> True

Три проверки на ошибку соответствуют трём способам, которыми скобки могут быть нарушены: закрывающая скобка без открытой (a)b(, её ловит Count == 0, а не исключение из Pop), закрывающая скобка не того вида ((]) и открывающие скобки, которые так и не закрыли (((a), их ловит финальная проверка).

Другие применения

  • Поиск в глубину. Замените очередь в поиске в ширину на стек, и обход пойдёт вглубь, а не вширь. Явный стек также заменяет рекурсию, когда входные данные достаточно глубоки, чтобы рисковать StackOverflowException, который нельзя перехватить.
  • Вычисление выражений. Постфиксная запись (3 4 + 2 *) вычисляется так: числа кладутся в стек, а для каждого оператора извлекаются два числа.
  • Возврат с отменой (backtracking). История навигации, прохождение лабиринта и состояния парсера кладут позицию в стек и возвращаются к ней в тупике.

Аналог «первым пришёл, первым вышел» описан на странице Queue.

Stack, Queue или List

Stack<T>Queue<T>List<T>
Порядок выдачиСначала новыеСначала старыеЛюбой, по индексу
ДобавитьPushEnqueueAdd, Insert
УдалитьPop (вершина)Dequeue (начало)Remove, RemoveAt
ПосмотретьPeekPeeklist[i]
Безопасные вариантыTryPop, TryPeekTryDequeue, TryPeekне нужны

Для нескольких потоков ConcurrentStack<T> из System.Collections.Concurrent предлагает Push, TryPop и TryPeek без блокировок.

Частые ошибки

  • Извлечение без проверки. Пустой стек выбрасывает InvalidOperationException; проверяйте Count или используйте TryPop.
  • Ожидание, что foreach начнёт с первого положенного элемента. Он начинает с вершины.
  • Копирование через new Stack<T>(stack). Копия получается перевёрнутой.
  • Добавление внутри foreach по тому же стеку. Выбрасывает исключение; используйте цикл while (stack.Count > 0).

Часто задаваемые вопросы

Что такое Stack в C#?

Stack<T> из System.Collections.Generic это коллекция «последним пришёл, первым вышел» (LIFO). Push кладёт элемент наверх, Pop удаляет и возвращает верхний элемент, а Peek возвращает верхний элемент, не удаляя его. Все три операции выполняются за постоянное время.

Что происходит при Pop из пустого стека в C#?

Pop и Peek выбрасывают InvalidOperationException, когда стек пуст. Сначала проверьте stack.Count > 0 или используйте TryPop(out var item) и TryPeek(out var item), которые вместо исключения возвращают false (.NET Core 2.0 и новее).

В каком порядке foreach проходит по Stack?

Сверху вниз: первым идёт элемент, добавленный последним, в том же порядке, в каком их вернул бы Pop. ToArray() использует тот же порядок. Как следствие, new Stack<T>(otherStack) создаёт перевёрнутую копию, потому что конструктор кладёт элементы в порядке их перебора.

Чем Stack отличается от Queue в C#?

Stack<T> возвращает сначала самый новый элемент (последним пришёл, первым вышел), а Queue<T> сначала самый старый (первым пришёл, первым вышел). Используйте стек для истории отмены, вложенных структур и поиска в глубину; используйте очередь для обработки работы в порядке поступления и поиска в ширину.

Как проверить сбалансированность скобок в C#?

Пройдите строку один раз. Кладите каждую открывающую скобку в Stack<char>. Для каждой закрывающей скобки стек должен быть непустым, а его верхний элемент должен быть соответствующей открывающей скобкой, которую вы затем извлекаете. Строка сбалансирована, если по окончании прохода стек пуст.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ