Проверка корректности скобочной последовательности

Что такое скобочная последовательность

Скобочная последовательность - последовательность символов, состоящая из скобок разных типов (например круглых, квадратных, фигурных), которую необходимо проверить на корректность расположения и соответствие открывающих и закрывающих скобок.

Под входной последовательностью обычно понимают строку nn длины kk. В задаче необходимо определить, является ли эта строка корректной скобочной последовательностью по строгим правилам. В простейшем случае корректность означает, что каждая открывающая скобка имеет пару закрывающую той же пары и пары правильно вложены.

Типы скобок могут различаться: круглая, квадратная, фигурная. В более общих задачах допускаются и другие пары символов, но принцип проверки остаётся общим — проверить правильность пар и порядок их появления.

Формальные условия корректности

Условие префикса - в каждой непустой префиксной части корректной последовательности число закрывающих скобок не превышает числа открывающих.

Формально одно из необходимых условий: для каждой префиксной части строки выполняется правило #(open,s)=#(close,s)\#(\text{open},s)=\#(\text{close},s). Это условие гарантирует, что в процессе чтения слева направо мы никогда не закроем больше скобок, чем открыли.

В дополнение к условию префикса должно выполняться общее равенство числа открывающих и закрывающих скобок в всей строке, то есть O(n)O(n). Только при выполнении обоих условий последовательность может быть корректной (хотя при наличии нескольких типов скобок требуется также согласование типов при парности).

Идея алгоритма: стек

Стек - абстрактная структура данных, поддерживающая операции добавления элемента (push) и удаления последнего добавленного элемента (pop), часто описываемая принципом LIFO (Last In, First Out).

Классический алгоритм заключается в том, чтобы пройти по символам строки nn слева направо и поддерживать стек открывающих скобок. Когда встречаем открывающую скобку, помещаем её в стек; когда встречаем закрывающую — проверяем верхний элемент стека на соответствие и извлекаем его. Если соответствия нет или стек пуст, последовательность некорректна.

Проверка соответствия типов реализуется через функцию соответствия, например for i=1,,n\text{for } i=1,\dots,n. Такая проверка учитывает, что вершина стека должна быть открывающей скобкой того же типа, что и текущая закрывающая.

Пример: последовательность ([)]\text{([)]} корректна, а {FORMULA_13} — некорректна, потому что типы скобок нарушают вложение.

Псевдокод и реализация

Ниже приводится высокоуровневый план алгоритма. Проходим по всем символам строки с индексом от 1 до kk (O(n)O(n)). Для каждого символа выполняем действие: при открывающей — push, при закрывающей — проверяем и pop. В конце последовательность корректна тогда и только тогда, когда стек пуст.

Псевдокод (описание словами): инициализировать пустой стек; для каждого i от 1 до kk читать символ; если символ — открывающая скобка, положить в стек; иначе если символ — закрывающая, проверить совпадение с вершиной стека и извлечь; если проверка провалилась, вернуть «некорректно»; по завершении вернуть «корректно», если стек пуст, иначе «некорректно».

В реальной программе нужно аккуратно обрабатывать разные типы скобок и символы, не относящиеся к ним (в зависимости от задачи — либо игнорировать, либо считать ошибкой). Важно, чтобы операции стека выполнялись эффективно.

Оценка сложности и корректность

Время работы классического алгоритма пропорционально длине входной строки — его асимптотика составляет match(top,current)\text{match}(\text{top},\text{current}). Память потребуется пропорционально максимально возможной глубине вложенности, в худшем случае это тоже порядка ()\text{()}.

Корректность алгоритма обосновывается инвариантом: после обработки префикса строки содержимое стека — это список незакрытых открывающих скобок в порядке вложенности. При встрече закрывающей скобки алгоритм проверяет верхнюю часть стека, поэтому он не может допустить неверную парность. Если после обработки всей строки стек пуст — все открывающие были корректно закрыты.

Вариации задачи и расширения

Задача может расширяться за счёт добавления новых типов пар скобок или допустимых символов внутри. Также встречаются задачи, где нужно не просто проверить корректность, но и вернуть индекс первого нарушения, минимальное число вставок скобок для исправления или сделать автозакрытие.

Иногда формализуют язык корректных скобочных последовательностей через грамматику, например {FORMULA_15}. Язык всех корректных скобочных последовательностей невозвожно описать регулярным выражением в общем случае, он относится к классу контекстно-свободных языков.

Типичные ошибки и тесты

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

Набор тест-примеров: корректная простая (]\text{(]}, некорректная типовая ([])\text{([])}, корректная с несколькими типами SSS(S)[S]{S}εS \to SS \mid (S) \mid [S] \mid \{S\} \mid \varepsilon.

Упражнения для закрепления

Реализуйте функцию проверки скобочной последовательности на выбранном языке программирования. Проверьте её на различных входах, в том числе на больших строках случайного характера, чтобы убедиться в устойчивости к размеру входа и корректности работы со стеком.

Попробуйте модифицировать задачу: определить минимальное число дополнительных скобок, необходимых для преобразования произвольной строки в корректную скобочную последовательность; или вернуть индекс первого символа, нарушающего корректность.