유한 오토마타에 스택 한 개를 끼워 넣으면, 어디까지 갈 수 있을까?
지난 강의에서 우리는 문맥 자유 문법(CFG)을 익혔다. 이번에는 그 문법이 만들어 내는 언어를 기계로 받아들이는 장치를 짚어 본다. 결정적 유한 오토마타(DFA)는 기억 용량이 정확히 상태 개수만큼이라, aⁿbⁿ처럼 a의 개수를 b가 다 끝날 때까지 들고 있어야 하는 문제 앞에서 무력했다. 해법은 의외로 소박하다. 카페에서 접시를 쌓아 올리듯, 오토마타 옆에 무한 스택 하나를 두는 것이다.
이 장치를 푸시다운 오토마타(pushdown automaton, PDA)라 부른다. 입력을 한 글자씩 읽으면서 스택의 맨 위(top)를 보고, 거기에 무엇을 새로 쌓을지(push) 혹은 무엇을 걷어 낼지(pop)를 결정한다. 흥미로운 점은 PDA는 본디 비결정적(nondeterministic)이라는 사실이다. 결정적 모델은 표현력을 잃기에, 처음부터 분기를 허용한다.
전이 함수가 ε을 세 자리 모두에 허용한다는 점에 주목하자. 입력을 안 읽고도 움직일 수 있고, 스택을 안 건드려도 되고, 스택에 안 쌓아도 된다. 이 자유도가 비결정적 분기를 빚어낸다.
고전 중의 고전. 아이디어는 단순하다. a를 읽을 때마다 스택에 표식 A를 쌓고, b가 시작되면 a 하나마다 A를 하나씩 걷어 낸다. 입력 끝에서 스택이 처음 상태로 돌아와 있으면 a와 b의 개수가 같다.
구현 디테일 하나. 스택이 비었는지 검사하는 표준 트릭은, 시작과 동시에 바닥 표시 $를 깔아 두는 것이다. 그러면 "스택이 비었음"을 직접 묻는 대신 "$가 top에 있음"을 물으면 된다.
상태: qstart --(ε, ε → $)--> q1
q1 --(a, ε → A)--> q1 (a를 읽고 A 푸시)
q1 --(ε, ε → ε)--> q2 (b 단계로 비결정적 전환)
q2 --(b, A → ε)--> q2 (b를 읽고 A 팝)
q2 --(ε, $ → ε)--> qaccept (바닥이 보이면 수락)
입력 a a a b b b 의 진행:
스택 남은 입력
$ a a a b b b
A $ a a b b b
A A $ a b b b
A A A $ b b b
A A A $ b b b ← q1 → q2 ε-전이 (분기)
A A $ b b
A $ b
$ ε
ε ε ← 수락
"q1에서 언제 q2로 넘어가야 하는가"라는 결정은 비결정성에 맡긴다. 어떤 분기든 입력 끝에서 수락할 수 있는 길이 하나라도 있으면 PDA는 그 문자열을 받아들인다.
이제 큰 정리로 향하는 한 방향, "CFG가 만든 언어는 어떤 PDA가 인식한다"를 보자. 발상은 깔끔하다. 스택을 유도의 작업대로 쓴다. 좌측 유도(leftmost derivation)는 항상 "맨 왼쪽 비단말을 한 번 펼친다". 그러므로 스택 top에 비단말이 있으면 그것을 규칙의 우변으로 바꿔 치우면 된다.
한 번에 여러 기호를 푸시한다는 표현은 앞서 언급한 대로 ε-전이 연쇄로 풀어 쓰면 된다. 스택을 "역순으로" 쌓아야 한다는 점이 학생들이 자주 놓치는 자리다. α = X1X2X3이라면 X3을 먼저, 그 위에 X2, 그 위에 X1을 쌓아야 X1이 top에 온다.
역방향은 한 차례 정신을 단단히 차려야 한다. 임의의 PDA가 인식하는 언어를 어떻게 문법으로 환원할 수 있을까? 그 핵심 발상은 다음과 같다.
PDA의 동작에서 한 토막을 떼어 보자. 시점 A에서 상태 p에 있고 스택의 어떤 기호 X가 top이며, 시점 B에서 상태 q에 있고 그 X가 마침내 팝되어 사라지는 순간이라고 하자. 그 사이 스택은 X를 일단 떠받친 채 위로 더 쌓였다 다시 무너져 내렸을 것이다. 즉, 시작과 끝 사이의 스택 높이는 X 위로만 변동했다. 이 구간 동안 PDA가 읽은 입력을 한 비단말 Apq로 부르자.
일반성을 위해 PDA를 약간 표준화한다. (i) 유일한 수락 상태가 하나이고, (ii) 수락은 스택이 비었을 때만 일어나며, (iii) 모든 전이는 푸시 또는 팝 가운데 정확히 하나만 한다. 이렇게 정리하면, 임의의 수락 실행은 "처음에 무언가를 푸시 — 그 위에서 사다리꼴이 오르내림 — 결국 그것을 팝"의 형태로 분해된다.
규칙은 두 종류로 나온다.
증명은 길어 보여도 분해 원리는 한 문장이다. 스택은 결국 자기가 쌓은 것을 자기가 다 치우고 가야 한다. 그 "쌓고 치움"의 기록을 비단말 Apq가 저장하는 것이다.
두 방향의 구성을 합치면 위 정리가 된다. 이제 우리는 같은 사물을 두 가지 언어로 말할 수 있다. 문법으로 묘사하거나, 기계로 묘사하거나. 어느 쪽이 편한지는 문제마다 다르다. 폐쇄성(합집합·연결·* 연산에 대한 닫힘)은 문법 쪽이 다루기 쉽고, 인식 알고리즘과 파싱은 기계 쪽이 다루기 쉽다.
다음 강의에서는 CFL의 한계를 가르는 도구, 즉 문맥 자유 언어 펌핑 보조정리를 들고 와서 "이 언어는 CFL이 아니다"를 증명하는 법을 배운다. 그리고 마침내 스택 한 개의 천장을 뚫고, 무한 테이프라는 새 풍경 — 튜링 기계 — 으로 발을 들인다.