Границы КС-языков и замкнутость

КС-языки мощнее регулярных, но и у них есть потолок. И ведут они себя капризнее: некоторые операции выводят за пределы класса.

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

Что замкнуто, а что нет

Хорошие новости: объединение L∪M, конкатенация L·M и звезда L* двух КС-языков снова КС (строится прямо на грамматиках: добавить правило S → S1 | S2 для объединения и т.п.). Плохие новости: пересечение и дополнение КС-языков могут не быть КС. Это резкий контраст с регулярными, где замкнуто всё.

Классический пример незамкнутости пересечения. Пусть L1 = {anbncm} и L2 = {ambncn} — оба КС (каждый сравнивает только две буквы, со стеком справляется). Но их пересечение:

L1 ∩ L2 = { a^n b^n c^m } ∩ { a^m b^n c^n }
        = { a^n b^n c^n }   ← равны все три!
        = НЕ КС-язык (доказали леммой о накачке)

Пересечение двух КС-языков дало anbncn, который не КС. Значит, КС-языки не замкнуты относительно пересечения. Через де Моргана отсюда следует и незамкнутость относительно дополнения (иначе пересечение получалось бы из объединения и дополнений).

Полезное исключение: пересечение с регулярным

Есть важная лазейка: пересечение КС-языка с регулярным языком — снова КС. Это очень практично: можно «отфильтровать» КС-язык регулярным условием, оставаясь в классе. Конструкция — произведение PDA и конечного автомата (стек берётся от PDA, состояния — пара). Покажем идею: проверяем строки на КС-свойство (баланс ab) И регулярное (оканчивается на c).

def balanced_ab(s):              # КС: число a == число b в порядке a..b
    depth = 0
    for ch in s:
        if ch == "a": depth += 1
        elif ch == "b":
            depth -= 1
            if depth < 0: return False
        # c игнорируем для баланса
    return depth == 0

def ends_with_c(s):              # регулярное условие
    return s.endswith("c")

# пересечение: КС ∩ регулярный = снова КС
for w in ["abc", "aabbc", "abcc", "ab", "aabb"]:
    res = balanced_ab(w) and ends_with_c(w)
    print(f"{w:>6}: КС∩рег = {res}")

Вывод:

   abc: КС∩рег = True
 aabbc: КС∩рег = True
  abcc: КС∩рег = True
    ab: КС∩рег = False
  aabb: КС∩рег = False

«ab» сбалансирована, но не оканчивается на c — отсеяна регулярным условием. Класс КС при этом не покинут.

Верхняя граница: что дальше

За КС-языками идут контекстно-зависимые (тип 1): им уже доступен anbncn и многое другое. Их распознаёт линейно-ограниченный автомат — машина Тьюринга с лентой, ограниченной длиной входа. Ещё выше — тип 0, рекурсивно-перечислимые языки и полная машина Тьюринга. Так мы подходим к вершине иерархии Хомского и к следующему разделу про вычислимость.

Как работает под капотом

Почему регулярные замкнуты относительно всего, а КС — нет? Дело в детерминизации. Для регулярных мы умеем строить ДКА и инвертировать его (дополнение). Для КС детерминированные PDA слабее недетерминированных, и инверсия не работает — нет аналога чистой детерминизации. Стек, в отличие от конечной памяти, нельзя «обратить» так же легко. Это глубокая причина капризности КС-класса.

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

Переносить замкнутость регулярных на КС. Пересечение и дополнение КС-языков НЕ обязаны быть КС — частая ловушка.

Забыть про лазейку с регулярными. КС ∩ регулярный = КС — это работает и очень полезно на практике (фильтрация).

Считать anbncn вообще нераспознаваемым. Он не КС, но контекстно-зависим и легко распознаётся машиной Тьюринга — просто требует памяти мощнее стека.

Итог

  • КС-языки замкнуты относительно объединения, конкатенации и звезды.
  • НЕ замкнуты относительно пересечения и дополнения: L1∩L2 может дать не-КС anbncn.
  • Лазейка: пересечение КС-языка с регулярным снова КС (произведение PDA и автомата).
  • Выше КС — контекстно-зависимые языки (тип 1, ЛО-автомат) и далее машина Тьюринга.
Проверьте себя
1. Относительно каких операций КС-языки НЕ замкнуты?
Aобъединения и конкатенации
Bпересечения и дополнения
Cзвезды Клини
Dконкатенации и звезды
2. Чему равно пересечение L1={a^n b^n c^m} и L2={a^m b^n c^n}?
Aпустому языку
B{a^n b^n c^n} — не-КС языку
Cрегулярному языку
D{a^m b^m c^m}
3. Что верно про пересечение КС-языка с регулярным языком?
Aоно никогда не КС
Bоно всегда снова КС
Cоно всегда регулярно
Dоно неразрешимо