W tym wykładzie zajmiemy się problemem jak efektywnie
rozpoznawać, czy dane słowo należy do języka generowanego przez
daną gramatykę.
W tym celu poznamy najpierw szczególną postać gramatyk
bezkontekstowych, tzw. postać normalną Chomsky'ego i dowiemy
się jak przekształcać gramatyki do tej postaci.
Następnie poznamy dynamiczny algorytm rozpoznający czy dane
słowo można wyprowadzić w danej gramatyce w postaci normalnej
Chomsky'ego.
Definicja
Powiemy, że gramatyka jest w
postaci normalnej Chomsky'ego,
jeśli wszystkie
produkcje w gramatyce są postaci:

lub

(dla pewnych nieterminali
A,
B i
C
oraz terminala
a).
Zauważmy, że jeśli gramatyka jest w postaci normalnej
Chomsky'ego, to każda produkcja przekształca jeden nieterminal
na dwa nieterminale, lub jeden nieterminal na jeden
nieterminal.
Ułatwia to rozstrzygnięcie, czy dane słowo x da się wyprowadzić w
danej gramatyce -- wiadomo, że w takim wyprowadzeniu
|x|-1 razy należy zastosować produkcje przekształcające
nieterminal na dwa nieterminale i |x| razy zastosować
produkcje przekształcające nieterminal na terminal.
Podstawowe pytanie, jakie można sobie zadać brzmi:
czy dla każdego języka bezkontekstowego istnieje generująca go
gramatyka w postaci normalnej Chomsky'ego?
Odpowiedź brzmi: nie.
Zauważmy, że
zastosowanie dowolnej produkcji nie zmniejsza liczby symboli.
Ponieważ zaczynamy od słowa złożonego z jednego symbolu
(aksjomatu), więc nigdy nie uzyskamy słowa pustego.
Jeśli więc język A jest bezkontekstowy i
,
to A nie jest generowany przez żadną gramatykę w postaci normalnej
Chomsky'ego.
Na szczęście słowo puste jest jedynym elementem, którego nie
możemy uzyskać rozważając gramatyki w postaci normalnej
Chomsky'ego.
Jeżeli dopuścimy, aby słowo puste zniknęło z języka generowanego
przez gramatykę, to każdą gramatykę można przekształcić do
postaci normalnej Chomsky'ego.
Niech
.
Konstrukcja gramatyki G' przebiega w kilku krokach:
- Niech gramatyka
będzie gramatyką powstałą z G przez
domknięcie zbioru produkcji zgodnie z następującymi
dwiema regułami:
- Jeśli mamy produkcje postaci
i
(dla X,Y in N i
), to
dodajemy produkcję
.
Tak dodana produkcja realizuje w jednym kroku to, co
można było zrobić w dwóch krokach:
.
- Jeśli mamy produkcje postaci
i
(dla X,Y in N i
),
to dodajemy produkcję
.
Tak dodana produkcja realizuje w jednym kroku to, co
można było zrobić w dwóch krokach:
.
Stosowanie powyższych reguł powtarzamy tak długo, jak
długo wprowadzają one jakiekolwiek nowe produkcje.
Ponieważ dodawane produkcje nie są dłuższe od już
istniejących, więc cały proces doda tylko ograniczoną
liczbę produkcji i w pewnym momencie się zakończy.
Zauważmy, że L(G1) = L(G), gdyż dodawane produkcje nie
pozwalają wyprowadzić niczego, czego i tak nie dałoby się
wcześniej wyprowadzić.
- Dla dowolnego słowa z
,
rozważymy jego najkrótsze wyprowadzenie.
W takim wyprowadzeniu nie będą się pojawiały produkcje postaci
ani
.
Niech G2 będzie gramatyką powstałą z G1 przez
usunięcie tych produkcji,
, gdzie
Wówczas:
- Gramatyka G3 powstaje z G2 przez dodanie do niej,
dla każdego terminala
, odpowiadającego
mu nowego nieterminala (
), oraz produkcji:
,
,
, ....
We wszystkich pozostałych produkcjach
, gdzie
, zamieniamy wszystkie terminale na
odpowiadające im nieterminale.
Nie zmienia to języka generowanego przez gramatykę:
Natomiast w ten sposób w G3 mamy tylko dwa rodzaje
produkcji:
gdzie a jest terminalem, oraz
gdzie Yi to nieterminale i k>1.
- Gramatyka G4 powstaje z G3 przez zastąpienie
każdej produkcji postaci
(dla k>2)
przez zestaw produkcji:
gdzie
to nowe nieterminale
dodane do G4.
Tak uzyskana gramatyka generuje dokładnie ten sam język --
jedyne co się zmieniło to to, że zamiast
zastosowania jednej produkcji, trzeba ich zastosować
kilka,
Natomiast G4 jest już w postaci normalnej Chomsky'ego.
Przykład
Zobaczmy, jak powyższy algorytm działa na przykładzie
gramatyki generującej język

.
Pierwszy krok powoduje dodanie produkcji

:
W drugim kroku usuwamy produkcję

.
W trzecim kroku dodajemy nieterminale
A i
B,
zastępujemy nimi w istniejących produkcjach terminale
a i
b, oraz dodajemy produkcje

i

.
W ostatnim kroku algorytmu rozbijamy produkcję

na dwie produkcje.
Tak uzyskana gramatyka jest w postaci normalnej Chomsky'ego.
Na przykład, wyprowadzenie słowa
aaabbb ma postać:
Przykład
Zobaczmy, jak powyższy algorytm działa na przykładzie
gramatyki generującej wyrażenia nawiasowe.
Pierwszy krok powoduje dodanie produkcji:
![$S \to []$](jfa-main-img605.png)
i

:
W drugim kroku usuwamy produkcje

i

.
W trzecim kroku dodajemy nieterminale
L i
P,
zastępujemy nimi w istniejących produkcjach odpowiednio
terminale
[ i
], oraz dodajemy produkcje

i
![$P \to ]$](jfa-main-img610.png)
.
W ostatnim kroku algorytmu rozbijamy produkcję

na dwie produkcje.
Tak uzyskana gramatyka jest w postaci normalnej Chomsky'ego.
Na przykład, wyprowadzenie słowa
[[][[]]] w tej gramatyce
ma postać:
Algorytm CYK pozwala stwierdzić, czy dane słowo jest
wyprowadzalne w danej gramatyce (w postaci normalnej Chomsky'ego).
Jeśli gramatyka nie jest w postaci normalnej Chomsky'ego, to
można ją sprowadzić do tej postaci zgodnie z opisaną wyżej
konstrukcją.
Tak jak to zwykle bywa w programowaniu dynamicznym,
żeby rozwiązać dany problem musimy go najpierw uogólnić i
zamiast szukać odpowiedzi na jedno pytanie,
znajdziemy odpowiedzi na całe mnóstwo powiązanych ze sobą
pytań.
Niech
będzie daną gramatyką w
postaci normalnej Chomsky'ego, a
dane słowo
będzie postaci
.
Dla każdej pary indeksów
wyznaczymy
wszystkie takie nieterminale X, z których można wyprowadzić
podsłowo
.
Dokładniej, wypełnimy tablicę:
Gdy już będziemy mieli obliczoną tablicę T, to słowo
x można wyprowadzić w gramatyce G wtw., gdy
.
W jaki sposób możemy wypełniać tę tablicę?
- Zaczynamy od przekątnej.
T[i,i] to zbiór nieterminali, z których można wyprowadzić
ai.
Jednak takie wyprowadzenie może składać się tylko z
zastosowania jednej produkcji zastępującej nieterminal
terminalem:
- Następnie wypełniamy kolejne przy-przekątne.
Zauważmy, że jeżeli
, i < j, to
wyprowadzenie to musi mieć postać:
,
,
,
dla pewnego k,
.
Pomocna nam będzie następująca operacja na zbiorach
nieterminali, która na podstawie zbioru wszystkich możliwych
Y-ów i Z-ów wyznacza zbiór wszystkich możliwych X-ów:
Operacja
może być skomplikowana, ale zauważmy, że
jej koszt jest stały (tzn. zależy od gramatyki, ale nie od
długości słowa x).
Obliczając T[i,j] uwzględniamy wszystkie możliwe
punkty podziału k podsłowa
:
Tablica ma rozmiar
przy czym aby wypełnić jeden
element tablicy trzeba wykonać liniową liczbę operacji
,
tak więc złożoność czasowa tego algorytmu to
.
Przykład
Rozważmy gramatykę w postaci normalnej Chomsky'ego:
oraz słowo
aabbab.
Algorytm CYK da nam w wyniku następującą tablicę
T:
Jeżeli spróbujemy odtworzyć skąd w
T[1,6] wzięło się
S, to
uzyskamy informacje o drzewach wyprowadzenia badanego słowa.
Poniżej podano przykładowe drzewo wyprowadzenia i zaznaczono
odpowiadające mu nieterminale w tablicy
T.
W tym wykładzie poznaliśmy postać normalną Chomsky'ego gramatyk
bezkontekstowych i pokazaliśmy, że każdą gramatykę
bezkontekstową można sprowadzić do tej postaci
(z dokładnością do akceptowania słowa pustego
).
Poznaliśmy też algorytm CKY rozpoznający, czy dane słowo należy
do języka generowanego przez daną gramatykę bezkontekstową w
postaci normalnej Chomsky'ego.
- Postać normalna Chomsky'ego
-- taka postać gramatyk bezkontekstowych, w której gramatyka
może zawierać tylko produkcje postaci:
lub
(dla pewnych nieterminali A, B i C
oraz terminala a).
Zobacz też artykuł w
Wikipedii.
- Algorytm CYK
-- algorytm rozpoznawania czy dane słowo jest
wyprowadzalne w danej gramatyce w postaci normalnej Chomsky'ego.
Zobacz też artykuł w
Wikipedii.
- Sprowadź do postaci normalnej Chomsky'ego następującą
gramatykę bezkontekstową:
- Dla podanej gramatyki bezkontekstowych w postaci
normalnej Chomsky'ego i słowa abaabb
zasymuluj działanie algorytmu CYK.
Podaj zawartość tablicy T oraz przykładowe drzewo
wyprowadzenia.
- Sprowadź do postaci normalnej Chomsky'ego następujące
gramatyki bezkontekstowe:
- Dla każdej z poniższych gramatyk bezkontekstowych w postaci
normalnej Chomsky'ego zasymuluj działanie algorytmu CYK dla
podanego słowa.
Czy podane słowo faktycznie można wyprowadzić w podanej
gramatyce?
Jeśli tak, to podaj przykładowe drzewo wyprowadzenia.
- aabbbab
- aabaaab
- aaaaaba.
- aabbaba
- aacacab