W poprzednim wykładzie poznaliśmy definicje języków obliczalnych
i częściowo obliczalnych.
Jak dotąd, wszystkie przykłady języków, jakie rozważaliśmy były
obliczalne.
Poznamy teraz przykłady języków, dla których problem
przynależności słów do języka nie jest obliczalny,
a nawet nie jest częściowo obliczalny.
Poznamy też hierarchię Chomsky'ego, hierarchię klas języków,
która podsumowuje większość poznanych przez nas klas języków i
formalizmów.
Mówiliśmy wcześniej o uniwersalnej maszynie Turinga.
Sprecyzujmy jej postać.
Ustalamy alfabet wejściowy
.
Uniwersalna maszyna Turinga symuluje działanie danej innej
maszyny Turinga na podstawie jej opisu oraz jej wejścia.
Zarówno opis symulowanej maszyny, jak i jej wejście maszyna
uniwersalna otrzymuje na swoim wejściu w postaci ciągów zer i
jedynek.
Wymaga to zakodowania opisu symulowanej maszyny Turinga jako
(ciągu bitów) słowa nad alfabetem {0,1}.
Nie opisujemy tutaj takiego kodowania, tylko zakładamy, że takie
kodowanie jest ustalone.
(Jest to problem czysto techniczny.)
Podobnie, symulowana maszyna może operować na słowach nad innym
alfabetem niż {0,1}.
Zakładamy że słowa te są, w miarę potrzeby, niejawnie kodowane --
to że znaki dowolnego alfabetu można zakodować jako ciągi bitów
odpowiedniej długości jest oczywiste.
Uniwersalną maszynę Turinga będziemy oznaczać przez U.
Przyjmujemy, że akceptuje ona następujący język:
Wynik działania maszyny uniwersalnej jest dokładnie taki sam,
jak wynik działania symulowanej maszyny M dla danego słowa
x.
Problem stopu to problem decyzyjny polegający na stwierdzeniu, czy dana
maszyna Turinga M zatrzyma się dla danego słowa x.
Problem ten możemy zdefiniować w formie języka:
Maszyna Turinga
akceptująca język
STOP to prosta przeróbka maszyny uniwersalnej.
działa tak samo jak U, z tą różnicą, że
gdy maszyna symulowana odrzuca (a więc zatrzymuje się), to
akceptuje.
Maszyna
nigdy nie odrzuca, natomiast zapętla
się gdy symulowana maszyna się zapętla.
Dowód przebiega nie wprost.
Załóżmy, że istnieje maszyna Turinga M', która
akceptuje język
i zawsze się
zatrzymuje.
Niech maszyna M'' będzie wynikiem przeróbki M':
M'' dostaje na wejściu kod maszyny M i
działa tak jak M' dla słowa
,
przy czym jeśli M' odrzuca, to M'' akceptuje, a jeśli
M' akceptuje, to M'' zapętla się.
Tak więc
Ponieważ M'' nie odrzuca, więc zatrzymuje się dla kodu M,
wtw., gdy M nie zatrzymuje się dla własnego kodu.
Czy M'' zatrzyma się dla własnego kodu?
Jeśli się zatrzymuje i akceptuje, to tym samym powinna się
zapętlić.
I na odwrót, jeżeli się zapętla, to powinna zaakceptować swój
własny kod.
Sprzeczność.
Tak więc założenie, że język STOP jest obliczalny
jest fałszywe.
Poznaliśmy przykład języka, który nie jest obliczalny.
Dalej poznamy przykład języka, który nie jest nawet częściowo
obliczalny.
Wcześniej jednak poznajmy trochę własności języków obliczalnych
i częściowo obliczalnych.
Powiedzmy, że mamy dwie maszyny Turinga, M1 i M2,
akceptujące odpowiednio A i
.
Możemy skonstruować maszynę M (z dwoma ścieżkami i głowicami),
która będzie symulować równocześnie działanie M1 i M2.
Maszyna ta patrzy, która z maszyn M1 i M2 zaakceptuje
słowo i odpowiednio akceptuje lub odrzuca.
Ponieważ któraś z maszyn M1, M2 musi zaakceptować, więc
M nie zapętla się.
Wiemy, że
jest częściowo obliczalny.
Gdyby
był częściowo obliczalny,
to
byłby obliczalny, a wiemy, że taki nie
jest.
Jak dotąd, dla każdej poznawanej klasy języków mieliśmy dwa
równoważne modele -- jeden nastawiony na opisywanie składni i
jeden na modelowanie obliczeń.
Czy istnieje formalizm nastawiony na opisywanie składni
równoważny maszynom Turinga?
Tak.
Są to Gramatyki ogólne (tzw. typu 0).
Ich definicja jest analogiczna do definicji gramatyk
bezkontekstowych, ale po obu stronach produkcji mogą stać
słowa.
Produkcja
pozwala w trakcie wyprowadzenia
zamienić dowolne podsłowo
na
.
Definicje wyprowadzenia i języka generowanego są analogiczne do
tych dla języków bezkontekstowych.
Jednak siła wyrazu gramatyk ogólnych jest dużo większa.
Nie będziemy tutaj szczegółowo dowodzić.
Dowód przebiega przez pokazanie implikacji w obie strony.
Zauważmy jednak, że język generowany przez gramatykę ogólną
może być nierozstrzygalny.
Jest tak dlatego, że dla każdego języka częściowo obliczalnego
mamy generującą go gramatykę ogólną, ale nie każdy język
częściowo obliczalny jest obliczalny.
Gramatyki kontekstowe to takie gramatyki ogólne, w których
prawe strony produkcji nie są krótsze od lewych.
Definicja
Powiemy, że język A jest kontekstowy, jeżeli istnieje
generująca go gramatyka kontekstowa.
Klasie języków kontekstowych odpowiadają maszyny Turinga ograniczone liniowo i
gramatyki kontekstowe.
Maszyny Turinga ograniczone liniowo to takie maszyny, których
głowica nie wyjedzie dalej niż (o jeden znak za) słowo dane na
wejściu.
Inaczej mówiąc, są to maszyny, które dysponują pamięcią
proporcjonalną do długości słowa danego na wejściu.
Zauważmy, że gramatyki bezkontekstowe w postaci normalnej Chomsky'ego są
równocześnie gramatykami kontekstowymi.
Przykład
Język

jest nie tylko obliczalny, ale
i kontekstowy.
Oto generująca go gramatyka:
Oto przykładowe wyprowadzenie słowa
aaabbbccc:
Dla gramatyki liniowej długość słowa w wyprowadzeniu nie może maleć.
Można więc prześledzić wszystkie możliwe do wyprowadzenia
słowa o długości nie przekraczającej długości danego na
wejściu słowa.
Hierarchia Chomsky'ego to hierarchia czterech poznanych przez
nas klas języków.
Została ona stworzona przez
Noama Chomsky'ego
w ramach jego badań lingwistycznych.
Są to języki: regularne, bezkontekstowe, kontekstowe i częściowo obliczalne.
Każdej z tych klas odpowiada jeden rodzaj gramatyk.
Odpowiadają im gramatyki: liniowe, bezkontekstowe kontekstowe
i ogólne.
Równocześnie tym samym klasom odpowiadają cztery różne modele
obliczeniowe: automaty skończone, automaty stosowe oraz maszyny
Turinga (ograniczone liniowo i dowolne).
To, że niezależnie stworzone modele do opisu języków odpowiadają
tym samym klasom wskazuje na ich głębsze znaczenie.
Zwyczajowo klasy języków tworzące hierarchię Chomsky'ego są
oznaczane liczbami od 0 do 3.
Następująca tabelka podsumowuje hierarchię Chomsky'ego oraz
pozostałe klasy języków, które poznaliśmy w trakcie tego kursu.
Są one podane w kolejności od klasy najszerszej do najwęższej.
Nr |
Gramatyki |
Maszyny |
Języki |
0 |
ogólne |
maszyny Turinga |
częściowo obliczalne |
-- |
-- |
maszyny Turinga
z własnością stopu |
obliczalne |
1 |
kontekstowe |
maszyny Turinga
ograniczone liniowo |
kontekstowe |
2 |
bezkontekstowe |
automaty stosowe |
bezkontekstowe |
3 |
liniowe oraz
wzorce |
automaty skończone |
regularne |
W tym wykładzie poznaliśmy przykłady języków, które nie są,
odpowiednio, obliczalne i częściowo obliczalne.
Jest to problem stopu i jego dopełnienie.
Poznaliśmy też gramatyki ogólne i kontekstowe.
Gramatyki ogólne są równoważne maszynom Turinga i opisują
dokładnie klasę języków częściowo obliczalnych.
Poznane przez nas w trakcie tego kursu gramatyki i klasy
języków (liniowe/regularne, bezkontekstowe, kontekstowe i
częściowo obliczalne/ogólne) tworzą hierarchię Chomsky'ego.
Hierarchia ta podsumowuje większość poznanych przez nas klas
języków, z wyjątkiem klasy języków obliczalnych.
- Gramatyki kontekstowe
to takie gramatyki ogólne, w których prawe strony produkcji
nie są krótsze od lewych.
- Gramatyki ogólne (typu 0)
to gramatyki, w których po obu stronach produkcji mogą stać
dowolne słowa.
- Hierarchia Chomsky'ego
to hierarchia czterech klas języków:
regularnych, bezkontekstowych, kontekstowych i częściowo
obliczalnych, którym odpowiadają cztery rodzaje gramatyk:
liniowe, bezkontekstowe, kontekstowe i ogólne.
Zobacz także
artykuł w Wikipedii.
- Problem stopu
to język (lub równoważnie problem decyzyjny) złożony z tych
kodów maszyn Turinga M i słów x, że M zatrzymuje się
dla x.
Język ten jest częściowo obliczalny, ale nie obliczalny.
Jego dopełnienie nie jest nawet częściowo obliczalne.
Napisz program, który wypisuje swój własny kod źródłowy.
Uwaga:
- Program ma wypisywać swój kod źródłowy po skompilowaniu,
usunięciu pliku źródłowego i uruchomieniu.
- Dopuszczalny jest dowolny, powszechnie dostępny,
kompilowany język programowania
(np. C, C++, Java, Pascal).
Nie może to być jednak język, w którym kod skompilowany
zawiera literalnie kod źródłowy.
- Pusty plik nie jest dobrym rozwiązaniem.