W tym wykładzie poznamy model obliczeń odpowiadający gramatykom
bezkontekstowym (tak jak automaty skończone odpowiadały
wzorcom).
Ten model, to automaty stosowe.
Automaty stosowe możemy sobie wyobrazić jako automaty skończone
wyposażone w dodatkową nieograniczoną pamięć, ale działającą na
zasadzie stosu.
To znaczy, na wierzch stosu można zawsze włożyć nowy element.
Jeśli stos nie jest pusty, to można zawsze zobaczyć jaki element
jest na wierzchu i ew. zdjąć ten element.
Nie można natomiast oglądać ani wyjmować elementów z wnętrza
stosu.
Zobaczymy na przykładach jak można konstruować automaty stosowe
i pokażemy, że faktycznie ich siła wyrazu jest taka sama jak
gramatyk bezkontekstowych.
Pod względem siły wyrazu automaty stosowe są też równoważne
imperatywnym językom programowania bez plików (pomocniczych)
dynamicznych struktur danych,
wskaźników i parametrów proceduralnych.
Przy tych ograniczeniach dane globalne w programie są stałego
rozmiaru, a danych dynamicznych nie ma.
Można korzystać z rekurencji i nie ma ograniczenia na rozmiar
stosu, jednak wielkość danych związanych z każdym poziomem
rekurencji jest ograniczona przez stałą i nie ma możliwości
wyciągania danych z głębi stosu.
Jak zobaczymy dokładnie odpowiada to automatom stosowym.
Automat stosowy, podobnie jak automat skończony,
ma skończony zbiór stanów i stan początkowy.
Dodatkowo jest on wyposażony w stos, na który może wkładać
elementy, podglądać element znajdujący się na wierzchu i
zdejmować elementy.
Przejścia automatu stosowego są trochę bardziej skomplikowane
niż przejścia w automacie skończonym, gdyż oprócz wczytywania
znaków z wejścia i zmiany stanów obejmują również operacje na
stosie.
Automat taki może być niedeterministyczny i może zawierać
-przejścia, tzn. wykonaniu przejścia nie musi
towarzyszyć wczytanie znaku z wejścia.
Natomiast przedstawione tutaj automaty stosowe będą pozbawione
stanów akceptujących -- automat będzie sygnalizował
akceptację wejścia opróżniając stos2.
Chcąc opisać działanie automatów stosowych musimy wprowadzić
pojęcie konfiguracji.
Konfiguracja będzie obejmować stan automatu (ze skończonego
zbioru stanów), zawartość stosu i pozostałe do wczytania
wejście.
Automat stosowy w jednym kroku obliczeń wykonuje następujące czynności:
Podgląda znak czekający na wczytanie na wejściu.
Podgląda element na wierzchołku stosu.
Na podstawie tych informacji wybiera jedno z przejść do
wykonania.
Jeśli to nie jest -przejście, to wczytywany
jest jeden znak z wejścia.
Zdejmowany jest element z wierzchołku stosu.
Pewna liczba określonych elementów może być włożona na
stos.
Zgodnie z przejściem zmieniany jest stan.
Może wydawać się nienaturalne, że w każdym kroku zdejmujemy
element z wierzchołka stosu.
Jednak dzięki temu, opisany powyżej krok automatu jest na tyle
elastyczny, że może zdejmować elementy ze stosu, wkładać nowe,
podmieniać element na wierzchołku lub nie zmieniać stosu.
Jeżeli chcemy zdjąć element z wierzchołku stosu, to wystarczy,
że nie będziemy nic wkładać.
Jeżeli chcemy podmienić element na wierzchołku, to wystarczy, że
włożymy jeden element, który zastąpi element zdjęty z
wierzchołka.
Jeżeli chcemy włożyć elementy na stos, to wkładamy na stos
właśnie zdjęty element, wraz z elementami, które mają być
umieszczone na stosie.
Jeśli nie chcemy zmieniać zawartości stosu, to po prostu
wkładamy dokładnie ten sam element, który właśnie został
zdjęty.
Możemy włożyć na stos kilka elementów w jednym kroku, ale
niestety nie możemy w jednym kroku zdjąć więcej niż jeden
element.
Zauważmy, że powyższy schemat postępowania wymaga, żeby stos nie
był pusty.
Inaczej nie ma czego podglądać ani zdejmować ze stosu.
Dlatego też, w momencie uruchomienia automatu stos nie jest
pusty.
Zawiera jeden wyróżniony element, który będziemy nazywać
,,pinezką'' i oznaczać przez .
Jak tylko stos zostaje opróżniony, automat zatrzymuje się i
dalsze jego działanie nie jest możliwe.
Jeżeli automat zatrzymuje się z pustym stosem po wczytaniu
całego słowa z wejścia, to przyjmujemy, że słowo to
zostało zaakceptowane.
Zanim formalnie zdefiniujemy automaty stosowe i akceptowane
przez nie języki, zobaczmy prosty nieformalny przykład:
Przykład
Zbudujemy automat akceptujący słowa postaci anbn, dla
.
Automat taki będzie miał dwa stany, s i q, przy czym s
będzie stanem początkowym.
Na stosie będziemy trzymać dwa rodzaje elementów:
i A.
Będąc w stanie s automat ma dwa przejścia do wyboru:
może wczytać z wejścia znak a i włożyć na stos A, lub
nic nie wczytywać, ani nie zmieniać zawartości stosu
i przejść do stanu q.
W stanie q automat ma również dwa przejścia do wyboru:
jeżeli na wejściu czeka na wczytanie znak b i na wierzchołku
stosu jest A, to może wczytać z wejścia b i zdjąć
ze stosu A.
Jeżeli natomiast na wierzchołku stosu jest , to
automat może zdjąć ze stosu i tym samym zakończyć
działanie.
Jedyna możliwość, żeby zaakceptować słowo wygląda tak:
Automat wczytuje sekwencję an i umieszcza na stosie An.
Następnie przechodzi do stanu q i wczytuje znaki b
zdejmując ze stosu A.
Jednych i drugich musi być tyle samo, czyli wczytana zostaje
sekwencja bn.
Po wczytaniu tej sekwencji odsłania się i jest
zdejmowana ze stosu.
Jeżeli automat ma zaakceptować słowo, to musi ono być w tym
momencie całe wczytane, czyli było to słowo anbn.
Poniższy diagram przedstawia obliczenie akceptujące słowo
aaabbb.
Widać na nim kolejne zawartości stosu i stany, w których jest
automat.
Definicja
Automat stosowy to dowolna taka piątka
, w której:
Q to skończony zbiór stanów, to (skończony) alfabet wejściowy, to (skończony) alfabet stosowy, to relacja przejścia,
; określa ona dla danego aktualnego stanu, znaku na wierzchołku stosu i znaku czekającego na wczytanie (lub jeżeli nic nie jest wczytywane), do jakiego stanu należy przejść i co należy włożyć na stos (uprzednio zdjąwszy element z wierzchołka stosu), to stan początkowy,
to symbol początkowy na stosie, tzw. ,,pinezka''.
Niech
będzie
ustalonym automatem stosowym.
Konfiguracja takiego automatu stosowego, to dowolna trójka:
gdzie q reprezentuje aktualny stan,
pozostałe do wczytania wejście, a
zawartość stosu (czytaną od wierzchołka w głąb).
Konfiguracja początkowa dla słowa x, to
.
Jeden krok obliczeń automatu stosowego opisujemy relacją
określoną na konfiguracjach.
Definicja
Niech
będzie
ustalonym automatem stosowym.
Relacja przejścia między konfiguracjami tego automatu
to najmniejsza relacja spełniająca następujące warunki:
jeśli zawiera przejście postaci
(dla , , i
),
to dla dowolnych
i
mamy:
,
jeśli zawiera przejście postaci
,
(dla , i
)
to dla dowolnych
mamy:
.
Przez będziemy oznaczać zwrotnio-przechodnie
domknięcie relacji .
Inaczej mówiąc,
oznacza, że możemy będąc w stanie p i mając na wierzchołku
stosu A przejść do stanu q wczytując z wejścia a i
zastępując na stosie A przez .
Podobnie
oznacza, że możemy będąc w stanie p i mając na wierzchołku
stosu A przejść do stanu q nie wczytując nic z wejścia i
zastępując na stosie A przez .
Relacja opisuje pojedyncze kroki obliczenia, a relacja
dowolnie długie obliczenia.
Definicja
Niech
będzie
ustalonym automatem stosowym.
Język akceptowany przez automat stosowy M jest określony
następująco:
Inaczej mówiąc, automat stosowy akceptuje takie słowa x,
dla których istnieją obliczenia prowadzące od konfiguracji
początkowej (dla słowa x) do konfiguracji, w której całe słowo
zostało wczytane, a stos opróżniony.
Pamiętajmy, że automat stosowy może być niedeterministyczny, a więc
wystarczy żeby istniało jedno obliczenie akceptujące, żeby słowo
było akceptowane przez automat.
Przykład
Skonstruujemy automat akceptujący słowa zawierające tyle samo
liter a, co b.
Automat ten będzie używać stosu jako licznika.
Z każdym wczytanym a licznik zwiększa się o 1, a
z każdym wczytanym b zmniejsza się o 1.
Dodatnie wartości licznika reprezentujemy jako
i stos A, a ujemne, jako i stos B.
Dodatkowo, w momentach gdy odsłonięta jest pinezka,
automat może ją zdjąć ze stosu.
Automat akceptuje tylko takie słowa x, po których wczytaniu
licznik jest wyzerowany, czyli
#a(x) = #b(x).
Automat ten ma jeden stan s i działa w następujący sposób:
Przykładowe obliczenie dla aabbba:
Oczywiście taki automat może usunąć pinezkę gdy jeszcze nie całe słowo jest wczytane. Takie obliczenie nie doprowadzi do zaakceptowania słowa. Pamiętajmy jednak, że automaty stosowe są niedeterministyczne i wystarczy, aby istniało choć jedno obliczenie akceptujące dane słowo, aby należało ono do języka akceptowanego przez automat stosowy.
Przykład
Skonstruujemy automat akceptujący wyrażenia nawiasowe zbudowane z kwadratowych nawiasów [ i ]. Automat ten działa podobnie jak poprzedni, ale licznik może przyjmować tylko dodatnie wartości. Na stosie trzymamy pinezkę i tyle nawiasów zamykających ile wynosi wartość licznika. Z wczytaniem każdego nawiasu otwierającego licznik jest zwiększany o 1, a z wczytaniem każdego nawiasu zamykającego zmniejszany o 1.
Przykładowe obliczenie dla [[][[]]]:
Przykład
Automat akceptujący palindromy nad alfabetem {a,b}. Automat ten musi niedeterministycznie zgadnąć gdzie jest środek palindromu i czy jest on parzystej, czy nieparzystej długości.
Nasz automat będzie miał dwa stany: s i p. Na stosie będziemy przechowywać pinezkę i znaki alfabetu wejściowego. W pierwszej fazie (stan s) automat zapamiętuje pierwszą połowę słowa na stosie. W drugiej fazie (stan p) zdejmuje znaki ze stosu, sprawdzając, czy są one takie same jak wczytywane znaki. Jeżeli słowo jest nieparzystej długości, to przechodząc z pierwszej fazy do drugiej wczytujemy środkowy znak nie zmieniając zawartości stosu. Jeżeli zaś jest parzystej długości, to przechodząc do drugiej fazy nic nie wczytujemy.
Przykładowe obliczenia akceptujące słowa abba i baabaab:
Przykład
W poprzednim wykładzie pokazaliśmy, że język
nie jest bezkontekstowy. Za chwilę pokażemy, że automaty stosowe akceptują dokładnie języki bezkontekstowe. Teraz jednak skonstruujemy automat akceptujący język
, pokazując tym samym, że jest on bezkontekstowy.
Słowo
należy do języka
w dwóch przypadkach. Albo jest nieparzystej długości. Albo jest parzystej długości i istnieje taka pozycja i (
) w pierwszej połowie tego słowa, że na i-tej pozycji w słowie x jest inny znak niż na odpowiadającej jej pozycji w drugiej połowie słowa,
.
Nasz automat będzie miał szereg stanów, przy czym stanem początkowym będzie s. Na stosie będziemy trzymać dwa rodzaje symboli: i . Automat zgaduje najpierw, czy słowo jest parzystej (stan p), czy nieparzystej (stan n) długości. Jeśli nieparzystej, to tylko to sprawdza, że faktycznie tak jest.
Jeśli słowo jest parzystej, to automat stosowy musi zgadnąć niedeterministycznie na jakiej pozycji i istnieje niezgodność między pierwszą i drugą połową słowa, a następnie sprawdzić, że faktycznie tak jest. Inaczej mówiąc, słowo x musi być postaci uavbw lub ubvaw, przy czym |v| = |u|+|w|. To czy w pierwszej połowie na i-tej pozycji jest a czy b można zapamiętać w stanie automatu. Problemem jest natomiast odliczenie odpowiadających sobie pozycji w obu połówkach słowa. Zauważmy jednak, że automat nie musi wiedzieć gdzie jest połowa długości słowa. Wystarczy, że będzie zachodzić |v| = |u|+|w|.
Nasz automat najpierw wczytuje słowo u, wkładając na stos |u| znaków .
Następnie niedeterministycznie zgaduje kiedy jest koniec u i wczytuje jeden znak, zapamiętując go na stanie.
Następnie nasz automat wczytuje tyle znaków z wejścia, ile jest na stosie. W ten sposób wczytujemy prefiks v długości |u|. Po zdjęciu ze stosu wszystkich przechodzimy do kolejnej fazy.
Następnie nasz automat powinien wczytać z wejścia
|w| = |v| - |u| znaków słowa v. Liczbę tę zgaduje niedeterministycznie, wkładając równocześnie |w| znaków na stos. W momencie gdy automat zgaduje niedeterministycznie, że całe słowo v zostało wczytane, wczytuje jeden znak z wejścia sprawdzając równocześnie, że jest on różny od znaku zapamiętanego na stanie.
Pozostało jeszcze do wczytania w, przy czym na stosie powinno być |w| znaków . Po wczytaniu słowa w powinna odsłonić się pinezka, którą zdejmujemy.
Oto przykładowe obliczenie akceptujące słowo abbaabaa.
Skonstruowany automat jest wysoce niedeterministyczny. Jeśli gdziekolwiek cokolwiek zostanie źle zgadnięte, to obliczenie nie doprowadzi do zaakceptowania słowa. Pamiętajmy jednak, iż wystarczy, że można tak zgadnąć, aby obliczenie było akceptujące, aby słowo było akceptowane przez automat.
Pokażemy teraz, że siła wyrazu automatów stosowych jest
dokładanie taka sama, jak gramatyk bezkontekstowych --
tzn. akceptują one dokładnie języki bezkontekstowe.
Przedstawimy tu jedynie szkic dowodu -- oprzemy sie na trzech faktach.
Pokażemy jak dla danej gramatyki bezkontekstowej skonstruować
równoważny jej automat stosowy (z jednym stanem).
Następnie pokażemy, że konstrukcję tę można odwrócić,
tzn. dla danego automatu stosowego z jednym stanem można
skonstruować równoważną mu gramatykę bezkontekstową.
Na koniec pokażemy, jak zredukować liczbę stanów w automacie
stosowym do jednego.
Niech G będzie daną gramatyką bezkontekstową
.
Zbudujemy równoważny jej automat stosowy
.
Automat ten będzie miał tylko jeden stan Q = {s}.
Na stosie będzie przechowywał symbole terminalne i nieterminalne.
.
Przyjmujemy, że .
Dla każdej produkcji mamy przejście:
Dodatkowo, dla każdego mamy przejście:
Nasz automat będzie działa w następujący sposób:
Jeśli na wierzchołku stosu jest terminal, to oczekujemy, że
czeka on na wejściu, wczytujemy go i zdejmujemy ze stosu.
Jeśli na wierzchołku stosu jest nieterminal, to należy wczytać
pewne słowo, które można z niego wyprowadzić.
Najpierw jednak należy zdecydować, jaka produkcja rozpoczyna
wyprowadzenie takiego słowa.
Zdejmujemy więc dany nieterminal ze stosu i
wkładamy na stos prawą stronę wybranej produkcji dla tego nieterminala.
Akceptujemy pustym stosem, gdy całe wyprowadzone słowo zostało
wczytane.
Można pokazać (przez indukcję po długości wyprowadzenia), że
M realizuje tzw. lewostronne wyprowadzenia, to znaczy takie,
w których zawsze jest rozwijany skrajnie lewy nieterminal.
Jeśli wczytywane słowo to x, x = yz, to konfiguracji
odpowiada w wyprowadzeniu słowo
.
Konfiguracja początkowa
odpowiada
aksjomatowi S, a konfiguracja akceptująca
odpowiada w wyprowadzeniu
słowu x.
Stąd L(M) = L(G).
Stosując przedstawioną powyżej metodę otrzymujemy
następujący automat stosowy:
Jest to inny automat niż skonstruowany przez nas wcześniej
automat stosowy akceptujący wyrażenia nawiasowe.
Oto jego przykładowe obliczenie akceptujące [[][[]]].
Przedstawioną w poprzednim punkcie konstrukcję można odwrócić.
Załóżmy, że mamy dany automat stosowy z jednym stanem
.
Bez zmniejszenia ogólności możemy założyć, że
.
Nasza gramatyka ma postać
,
przy czym jeśli zawiera przejście
(dla
), to
w P mamy produkcję .
Odpowiedniość jest taka jak poprzednio.
Jeśli wczytywane słowo to x, x = yz, to konfiguracji
odpowiada w wyprowadzenie
lewostronne
.
Konfiguracja początkowa
odpowiada
aksjomatowi , a konfiguracja akceptująca
odpowiada w wyprowadzeniu
.
Stąd L(M) = L(G).
Przykład
Weźmy, przedstawiony powyżej, automat stosowy akceptujący
słowa zawierające tyle samo liter a, co b.
Stosując przedstawioną powyżej metodę, uzyskujemy
następującą równoważną mu gramatykę (dla czytelności
zastąpiono przez S):
Pozostało nam pokazać, że dla każdego automatu stosowego
istnieje
równoważny mu automat z jednym stanem
.
Bez zmniejszenia ogólności możemy założyć, że
automat M w momencie opróżnienia stosu będzie
zawsze w jednym stanie q.
Na stosie automaty M' będziemy trzymać trójki postaci
.
Jeżeli na wierzchołku stosu M' jest trójka
, to
odpowiada to sytuacji, gdy na wierzchołku stosu w M jest A,
M jest w stanie p i po zdjęciu symbolu A ze stosu
przejdzie do stanu r.
Stany pamiętane na stosie M' muszą się ,,zazębiać'', tzn. pod trójką
musi być trójka postaci
(dla pewnych i ) itd.
Automat M' na początku ma na stosie
trójkę
i akceptuje słowo x wtw., gdy M
zaczynając w stanie s i z symbolem na stosie,
wczytuje x i kończy w stanie q z pustym stosem.
Relacja jest określona następująco:
jeśli zawiera przejście:
dla pewnych
,
, k > 0,
,
to
(dla dowolnych
)
zawiera przejścia:
jeśli zawiera przejście
dla pewnych
,
, , to
zawiera przejście:
Automat ten jest wysoce niedeterministyczny.
Wkładając trójki na stos musimy z góry przewidzieć w jakim
stanie będzie automat M w momencie zdjęcia odpowiedniej
trójki w M'.
Oczywiście zgadnięcie odpowiednich stanów jest możliwe,
a więc M' będzie mógł zasymulować każde obliczenie M
(i tylko obliczenia M).
W tym wykładzie poznaliśmy automaty stosowe.
Zobaczyliśmy na przykładach jak je można konstruować.
Dowiedzieliśmy się też, że są one równoważne gramatykom
bezkontekstowym.
Automat stosowy
to dowolna taka piątka
, w której:
Q to skończony zbiór stanów,
to alfabet wejściowy,
to alfabet stosowy,
to relacja przejścia,
to stan początkowy,
to symbol początkowy na stosie.
Skonstruuj automaty stosowe akceptujące poniższe języki.
Czy potrafisz skonstruować takie automaty, które mają tylko
jeden stan?
Jeśli tak, to przekształć je na równoważne im gramatyki
bezkontekstowe.
wyrażenia nawiasowe zawierające trzy rodzaje nawiasów:
()[]{}; przy tym:
nawiasy okrągłe mogą otaczać tylko nawiasy okrągłe,
nawiasy kwadratowe mogą otaczać nawiasy kwadratowe lub
okrągłe, a nawiasy wąsate mogą otaczać nawiasy wąsate lub
kwadratowe,
palindromy nad alfabetem {a,b,c},
{aib2i},
{aibjajbi},
{w : #a(w) >= 2#b(w)},
,
wyrażenia arytmetyczne w notacji polskiej
(dla uproszczenia liczby mogą być tylko jednocyfrowe),
wyrażenia arytmetyczne w odwrotnej notacji polskiej
(dla uproszczenia liczby mogą być tylko jednocyfrowe).