ε-переходы и НКА-ε

ε-переход позволяет автомату сменить состояние, ничего не прочитав, — крошечное расширение, делающее сборку автоматов почти тривиальной.

ε-переход — переход НКА из одного состояния в другое без чтения входного символа; такой автомат называют НКА-ε.

Переход «бесплатно»

Обычный переход «съедает» символ входа. ε-переход — это переход по пустому слову ε: автомат может в любой момент проскользнуть по нему, не двигаясь по строке. Зачем? Чтобы склеивать автоматы. Если у вас есть автомат для языка A и автомат для языка B, то ε-переходы позволяют соединить их в автомат для A·B (конкатенация) или для A∪B (объединение) почти механически — просто протянув ε-стрелки между ними. Это сердце доказательства теоремы Клини, к которой мы придём.

ε-замыкание

Раз по ε можно ходить «бесплатно», то находясь в состоянии q, автомат фактически находится сразу во всех состояниях, достижимых из q по цепочкам ε-переходов. Это множество называют ε-замыканием (ECLOSE) состояния q. Симуляция НКА-ε работает так: (1) берём ε-замыкание стартового состояния; (2) на каждом символе делаем обычный переход, затем снова берём ε-замыкание результата; (3) в конце проверяем пересечение с принимающими.

Пример: НКА-ε для языка a*b*  («сколько-то a, потом сколько-то b»)

        a              b
       ┌─┐            ┌─┐
       │ v            │ v
  ──>((q0)) --ε-->   ((q1))

(q0): петля по a, ((q0)) принимающее (можно сразу b? нет — сначала ε)
ε-стрелка q0->q1 позволяет перейти к чтению b, не тратя символ.
ε-замыкание(q0) = {q0, q1}.

Симуляция НКА-ε на Python

Реализуем ε-замыкание (обход по ε-рёбрам) и общий прогон. Возьмём автомат для языка a*b*.

eps = {"q0": {"q1"}}                      # ε-переходы
delta = {("q0", "a"): {"q0"}, ("q1", "b"): {"q1"}}
accept = {"q1"}

def eclose(states):
    stack, seen = list(states), set(states)
    while stack:
        s = stack.pop()
        for t in eps.get(s, set()):
            if t not in seen:
                seen.add(t); stack.append(t)
    return seen

def run(word):
    cur = eclose({"q0"})
    for ch in word:
        nxt = set()
        for st in cur:
            nxt |= delta.get((st, ch), set())
        cur = eclose(nxt)
    return bool(cur & accept)

for w in ["", "aaa", "aabb", "bb", "ba"]:
    print(f"{w or 'ε':>4} -> {'принята' if run(w) else 'отвергнута'}")

Вывод:

   ε -> принята
 aaa -> принята
aabb -> принята
  bb -> принята
  ba -> отвергнута

«ba» отвергнута: после b нельзя вернуться к чтению a. ε-замыкание стартового состояния делает q1 принимающим сразу, поэтому пустая строка и «aaa» проходят.

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

ε-переходы не добавляют мощности: любой НКА-ε преобразуется в обычный НКА (а затем в ДКА). Идея — «втянуть» ε-переходы в обычные: если из q можно по ε дойти до p, а из p по a — в r, то добавляем прямой переход q --a--> r и переносим принимающность по ε-замыканию. Поэтому НКА-ε, НКА и ДКА — три эквивалентных формализма для одного класса регулярных языков. ε-переходы — это «строительный клей», удобный при сборке, но устранимый.

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

Забыть ε-замыкание стартового состояния. Прогон начинается не с {q0}, а с ECLOSE({q0}) — иначе пропустите состояния, достижимые по ε сразу.

Не брать ε-замыкание после каждого символа. После обычного перехода всегда снова применяйте ECLOSE, иначе потеряете ветви.

Считать, что ε-переход что-то читает. Он не двигает указатель по строке — строка стоит на месте, меняется только состояние.

Итог

  • ε-переход меняет состояние без чтения символа; автомат с ними — НКА-ε.
  • ε-замыкание состояния — все состояния, достижимые по цепочкам ε-переходов.
  • Симуляция: ε-замыкание старта, затем на каждом символе переход + повторное ε-замыкание.
  • ε-переходы удобны для склейки автоматов (конкатенация, объединение), но мощности не добавляют.
Проверьте себя
1. Что делает ε-переход?
Aчитает специальный символ ε из входа
Bменяет состояние, не читая никакого входного символа
Cзавершает работу автомата
Dсбрасывает автомат в стартовое состояние
2. Что такое ε-замыкание состояния q?
Aвсе состояния, достижимые из q по одному символу
Bмножество всех состояний, достижимых из q по цепочкам ε-переходов
Cтолько само состояние q
Dпринимающие состояния автомата
3. Добавляют ли ε-переходы выразительной мощности по сравнению с обычным НКА?
Aда, позволяют распознавать КС-языки
Bнет, любой НКА-ε сводится к НКА и ДКА
Cда, делают автомат детерминированным
Dтолько при наличии стека