Złożoność obliczeniowa/Wykałd 3: Klasy złożoności obliczeniowej: Różnice pomiędzy wersjami
Nie podano opisu zmian |
|||
Linia 1: | Linia 1: | ||
{|l|l|} | {|l|l|} | ||
Linia 104: | Linia 78: | ||
wytwarzania komputerów. Na rysunku przedstawiono schematycznie zamianę 8 komórek pamięci na jedną większą nowej maszyny: | wytwarzania komputerów. Na rysunku przedstawiono schematycznie zamianę 8 komórek pamięci na jedną większą nowej maszyny: | ||
{ZO-3-1-rys}{Liniowe przyspieszanie} | |||
Liniowe przyspieszanie | |||
Nasza nowa maszyna o powiększonym alfabecie rozpoczyna działanie od skompresowania słowa wejściowego na drugiej taśmie i zamiany ról taśmy drugiej i wejściowej. Następnie | Nasza nowa maszyna o powiększonym alfabecie rozpoczyna działanie od skompresowania słowa wejściowego na drugiej taśmie i zamiany ról taśmy drugiej i wejściowej. Następnie | ||
Linia 128: | Linia 97: | ||
W drugim etapie dokonujemy symulacji. Najpierw odczytujemy "obszar trzech" komórek na każdej taśmie co zajmuje 4 kroki <math>M'</math>: | W drugim etapie dokonujemy symulacji. Najpierw odczytujemy "obszar trzech" komórek na każdej taśmie co zajmuje 4 kroki <math>M'</math>: | ||
{ZO-3-2-rys}{Obszar trzech} | |||
Obszar trzech | |||
Zapamiętujemy odczyt | Zapamiętujemy odczyt | ||
Linia 154: | Linia 118: | ||
Analogicznie twierdzenie zachodzi dla złożoności pamięciowej: | Analogicznie twierdzenie zachodzi dla złożoności pamięciowej: | ||
(twierdzenie o liniowej kompresji pamięci) Jeśli język <math>L</math> jest rozpoznawany przez maszynę <math>M</math> o złożoności pamięciowej <math>f(n)</math> to może | |||
być rozpoznany przez maszynę <math>M'</math> o złożoności pamięciowej <math>f'(n)=\epsilon f(n)</math>, gdzie <math>\epsilon>0</math>. | być rozpoznany przez maszynę <math>M'</math> o złożoności pamięciowej <math>f'(n)=\epsilon f(n)</math>, gdzie <math>\epsilon>0</math>. | ||
Skorzystaj z dowodu twierdzenia o liniowym przyspieszaniu. | Skorzystaj z dowodu twierdzenia o liniowym przyspieszaniu. | ||
Dowód przebiega dokładnie tak samo jeśli chodzi o ideę. Wystarczy tylko dokonać obliczenia zużycia pamięci. Każda komórka nowej maszyny <math>M'</math> | Dowód przebiega dokładnie tak samo jeśli chodzi o ideę. Wystarczy tylko dokonać obliczenia zużycia pamięci. Każda komórka nowej maszyny <math>M'</math> | ||
pamięta <math>c</math> symboli maszyny <math>M</math>, więc koszt pamięciowy to <math>f'(n)=\lceil f(n)/c \rceil<\epsilon f(n)</math>, jeśli <math>c=\lceil 1/\epsilon \rceil</math>, | pamięta <math>c</math> symboli maszyny <math>M</math>, więc koszt pamięciowy to <math>f'(n)=\lceil f(n)/c \rceil<\epsilon f(n)</math>, jeśli <math>c=\lceil 1/\epsilon \rceil</math>, | ||
dla odpowiednio dużych <math>n</math> (ponownie dla małych <math>n</math> można stosownie rozbudować maszynę). | dla odpowiednio dużych <math>n</math> (ponownie dla małych <math>n</math> można stosownie rozbudować maszynę). | ||
Na koniec ciekawostka dotycząca przyspieszania maszyn. | Na koniec ciekawostka dotycząca przyspieszania maszyn. | ||
Linia 227: | Linia 186: | ||
Okazuje się, że większość interesujących funkcji spełnia tą własność. Jest to także własność zamknięta ze względu na dodawanie, mnożenie i potęgowanie. | Okazuje się, że większość interesujących funkcji spełnia tą własność. Jest to także własność zamknięta ze względu na dodawanie, mnożenie i potęgowanie. | ||
Pokaż, że funkcje <math>\lceil \textnormal{log}n \rceil</math>, <math>n^k</math>, <math>2^n</math> są konstruowalne pamięciowo. | Pokaż, że funkcje <math>\lceil \textnormal{log}n \rceil</math>, <math>n^k</math>, <math>2^n</math> są konstruowalne pamięciowo. | ||
Zastosuj definicję i skonstruuj stosowne maszyny. | Zastosuj definicję i skonstruuj stosowne maszyny. | ||
Dowód konstruowalności pamięciowej funkcji | Dowód konstruowalności pamięciowej funkcji | ||
<math>\lceil \textnormal{log}n \rceil</math> opieramy na implementacji klasycznego licznika binarnego. Długość zapisu liczby binarnej <math>n</math> to właśnie <math>\lceil \textnormal{log}n \rceil</math>. | <math>\lceil \textnormal{log}n \rceil</math> opieramy na implementacji klasycznego licznika binarnego. Długość zapisu liczby binarnej <math>n</math> to właśnie <math>\lceil \textnormal{log}n \rceil</math>. | ||
Dla funkcji wielomianowej wykonujemy serię prostych działań arytmetycznych polegających na mnożeniu i dodawaniu liczb naturalnych zapisanych binarnie. | Dla funkcji wielomianowej wykonujemy serię prostych działań arytmetycznych polegających na mnożeniu i dodawaniu liczb naturalnych zapisanych binarnie. | ||
W przypadku funkcji wykładniczej wystarczy zaprogramować maszynę, aby <math>n</math> razy dokonała podwojenia liczby zużytych komórek. | W przypadku funkcji wykładniczej wystarczy zaprogramować maszynę, aby <math>n</math> razy dokonała podwojenia liczby zużytych komórek. | ||
Poniżej przedstawiamy twierdzenie, które zachodzi, jeśli nie narzucimy dodatkowego warunku na funkcję złożoności. Wprowadzając go, chcemy uniknąć podobnych | Poniżej przedstawiamy twierdzenie, które zachodzi, jeśli nie narzucimy dodatkowego warunku na funkcję złożoności. Wprowadzając go, chcemy uniknąć podobnych | ||
Linia 283: | Linia 236: | ||
Przeanalizujmy teraz możliwe konfiguracje maszyny Turinga <math>M</math>, które tworzą tzw. graf przejść maszyny: | Przeanalizujmy teraz możliwe konfiguracje maszyny Turinga <math>M</math>, które tworzą tzw. graf przejść maszyny: | ||
W jak wielu konfiguracjach może znaleźć się maszyna Turinga o złożoności pamięciowej | W jak wielu konfiguracjach może znaleźć się maszyna Turinga o złożoności pamięciowej | ||
<math>f(n)</math> (konstruowalnej pamięciowo) przeprowadzając obliczenie na słowie o długości <math>n</math>? | <math>f(n)</math> (konstruowalnej pamięciowo) przeprowadzając obliczenie na słowie o długości <math>n</math>? | ||
Policz liczbę stanów maszyny, położeń głowic i zawartości taśm. | Policz liczbę stanów maszyny, położeń głowic i zawartości taśm. | ||
Liczba możliwych konfiguracji to oczywiście iloczyn: | Liczba możliwych konfiguracji to oczywiście iloczyn: | ||
Linia 303: | Linia 251: | ||
Razem możemy to ograniczyć przez <math>c^{f(n)}</math>, dla pewnego <math>c</math> zależnego tylko od maszyny oraz korzystając z założenia, że | Razem możemy to ograniczyć przez <math>c^{f(n)}</math>, dla pewnego <math>c</math> zależnego tylko od maszyny oraz korzystając z założenia, że | ||
<math>f(n)\geqslant \textnormal{log}n</math>, gdyż jest konstruowalna pamięciowo. | <math>f(n)\geqslant \textnormal{log}n</math>, gdyż jest konstruowalna pamięciowo. | ||
Teraz jesteśmy gotowi do wypowiedzenia kolejnych interesujących relacji pomiędzy wprowadzonymi klasami: | Teraz jesteśmy gotowi do wypowiedzenia kolejnych interesujących relacji pomiędzy wprowadzonymi klasami: | ||
Linia 323: | Linia 270: | ||
Dzięki powyższym relacjom możemy wypisać kilka podstawowych zależności pomiędzy wprowadzonymi klasami: | Dzięki powyższym relacjom możemy wypisać kilka podstawowych zależności pomiędzy wprowadzonymi klasami: | ||
Uzasadnij każdą z poniższych relacji:<br> | |||
* <math>\textnormal{L}\subseteq\textnormal{NL}\subseteq\textnormal{P}\subseteq\textnormal{NP}\subseteq\textnormal{PSPACE}</math> | * <math>\textnormal{L}\subseteq\textnormal{NL}\subseteq\textnormal{P}\subseteq\textnormal{NP}\subseteq\textnormal{PSPACE}</math> | ||
Linia 329: | Linia 276: | ||
* <math>\textnormal{PSPACE}\subseteq\textnormal{NPSPACE}\subseteq\textnormal{EXP}\subseteq\textnormal{NEXP}\subseteq\textnormal{EXPSPACE}\subseteq\textnormal{NEXPSPACE}</math> | * <math>\textnormal{PSPACE}\subseteq\textnormal{NPSPACE}\subseteq\textnormal{EXP}\subseteq\textnormal{NEXP}\subseteq\textnormal{EXPSPACE}\subseteq\textnormal{NEXPSPACE}</math> | ||
Skorzystaj z poznanych już własności klas oraz własności funkcji logarytmicznej, wielomianowej i wykładniczej. | Skorzystaj z poznanych już własności klas oraz własności funkcji logarytmicznej, wielomianowej i wykładniczej. | ||
Wszystkie zawierania pochodzą z wypisanych wcześniej ogólnych zależności. Nietrywialne, to: | Wszystkie zawierania pochodzą z wypisanych wcześniej ogólnych zależności. Nietrywialne, to: | ||
Linia 343: | Linia 285: | ||
* <math>\textnormal{NPSPACE}\subseteq\textnormal{EXP}</math>, również korzystamy z faktu, że <math>\textnormal{NSPACE}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})</math>, w tym wypadku | * <math>\textnormal{NPSPACE}\subseteq\textnormal{EXP}</math>, również korzystamy z faktu, że <math>\textnormal{NSPACE}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})</math>, w tym wypadku | ||
mamy bowiem <math>c^{n^k}=2^{n^{k'}}</math>. | mamy bowiem <math>c^{n^k}=2^{n^{k'}}</math>. | ||
Przyjrzyjmy się bliżej pierwszej serii relacji z poprzedniego ćwiczenia i zobrazujmy ją na rysunku: | Przyjrzyjmy się bliżej pierwszej serii relacji z poprzedniego ćwiczenia i zobrazujmy ją na rysunku: | ||
{ZO-3-3-rys}{Relacje pomiędzy podstawowymi klasami} | |||
Relacje pomiędzy podstawowymi klasami | |||
<math>\textnormal{L}\subseteq\textnormal{NL}\subseteq\textnormal{P}\subseteq\textnormal{NP}\subseteq\textnormal{PSPACE}</math> | <math>\textnormal{L}\subseteq\textnormal{NL}\subseteq\textnormal{P}\subseteq\textnormal{NP}\subseteq\textnormal{PSPACE}</math> | ||
W następnej części z twierdzenia o hierarchii pamięciowej dowiemy się, że <math>L\varsubsetneq \textnormal{PSPACE}</math>. Tym samym wiemy, że | W następnej części z twierdzenia o hierarchii pamięciowej dowiemy się, że <math>L\varsubsetneq \textnormal{PSPACE}</math>. Tym samym wiemy, że | ||
Linia 386: | Linia 320: | ||
oznaczmy przez <math>t</math>: | oznaczmy przez <math>t</math>: | ||
{ZO-3-4-rys}{Wierzchołek pośredni} | |||
Wierzchołek pośredni | |||
Potem wywołujemy rekurencyjnie <math>\textnormal{PATH}(x,t,i/2)</math> oraz <math>\textnormal{PATH}(t,y,i/2)</math>. Jeśli obie ścieżki istnieją to zwracamy 1, | Potem wywołujemy rekurencyjnie <math>\textnormal{PATH}(x,t,i/2)</math> oraz <math>\textnormal{PATH}(t,y,i/2)</math>. Jeśli obie ścieżki istnieją to zwracamy 1, | ||
Linia 409: | Linia 338: | ||
czyli, że niedeterminizm w złożoności pamięciowej dla większych funkcji nic nie daje. | czyli, że niedeterminizm w złożoności pamięciowej dla większych funkcji nic nie daje. | ||
Dla klas złożoności czasowej takiej wiedzy nie posiadamy! | Dla klas złożoności czasowej takiej wiedzy nie posiadamy! | ||
Wersja z 23:08, 26 lip 2006
Moduł & 3
Tytuł & Klasy złożoności obliczeniowej
Opracowanie & Przemysław Broniek
{ Syllabus} Klasy złożoności obliczeniowej
- klasy złożoności czasowej i pamięciowej,
- twierdzenia o liniowym przyspieszaniu i kompresji pamięci,
- relacje między klasami, twierdzenie Savitcha i dopełnienia klas,
- twierdzenia o hierarchii czasowej i pamięciowej.
Klasy złożoności czasowej i pamięciowej
W poprzednich modułach zostały wprowadzone maszyny Turinga oraz zdefiniowane pojęcie problemu obliczeniowego. Przypomnijmy, że problem obliczeniowy to dla nas język, czyli zbiór słów. Poznaliśmy także szczegółowo maszynę w wersji deterministycznej i niedeterministycznej oraz jej miarę złożoności czasowej i pamięciowej w każdej z wersji. W tym module zajmiemy się klasyfikacją języków przy pomocy maszyn. W naszych dalszych rozważaniach przyjmujemy model obliczeń w postaci maszyny Turinga o taśmach.
Klasa złożoności obliczeniowej to zbiór problemów (języków) spełniających określone kryterium. Najbardziej podstawowe kryteria, tzn. czas i pamięć potrzebne do klasyfikacji języka dają nam podstawowe klasy złożoności:
Definicja [Uzupelnij]
Poprzez Parser nie mógł rozpoznać (nieznana funkcja „\textsc”): {\displaystyle \textsc{TIME}(f(n))} oznaczamy zbiór języków takich, że są akceptowane przez deterministyczną maszynę Turinga o złożoności czasowej .
Definicja [Uzupelnij]
Poprzez Parser nie mógł rozpoznać (nieznana funkcja „\textsc”): {\displaystyle \textsc{SPACE}(f(n))} oznaczamy zbiór języków takich, że są akceptowane przez deterministyczną maszynę Turinga o złożoności pamięciowej .
Stosowne klasy można też zdefiniować dla niedeterministycznych maszyn:
Definicja [Uzupelnij]
Poprzez Parser nie mógł rozpoznać (nieznana funkcja „\textsc”): {\displaystyle \textsc{NTIME}(f(n))} oznaczamy zbiór języków takich, że są akceptowane przez niedeterministyczną maszynę Turinga o złożoności czasowej .
Definicja [Uzupelnij]
Poprzez Parser nie mógł rozpoznać (nieznana funkcja „\textsc”): {\displaystyle \textsc{NSPACE}(f(n))} oznaczamy zbiór języków takich, że są akceptowane przez niedeterministyczną maszynę Turinga o złożoności pamięciowej .
Twierdzenia o liniowym przyspieszaniu i kompresji pamięci
Pierwsze dwa twierdzenia możemy nazwać "teoretycznymi podstawami" notacji . Pokażemy bowiem, że w obu powyższych definicjach klas funkcja może być rozpatrywana z dokładnością do stałej, ze względu na specyfikę samego modelu obliczeń. Zostały one udowodnione w latach 60 przez pionierów badań nad klasami złożoności, laureatów nagrody Turinga z roku 1993, Jurisa Hartmanisa i Richarda Stearnsa.
Twierdzenie [Uzupelnij]
być rozpoznany przez maszynę o złożoności czasowej , gdzie .
Dowód [Uzupelnij]
Idea dowodu jest oparta na powiększeniu alfabetu maszyny, a tym samym wielkości "słowa" maszyny oraz liczby jej stanów. Odpowiada to w praktyce podniesieniu technologii wytwarzania komputerów. Na rysunku przedstawiono schematycznie zamianę 8 komórek pamięci na jedną większą nowej maszyny:
{ZO-3-1-rys}{Liniowe przyspieszanie}
Nasza nowa maszyna o powiększonym alfabecie rozpoczyna działanie od skompresowania słowa wejściowego na drugiej taśmie i zamiany ról taśmy drugiej i wejściowej. Następnie głowica powraca na początek słowa i rozpoczyna obliczenia.
W każdym kroku obliczeń odczytuje bieżącą komórkę oraz dwie sąsiednie (na wszystkich taśmach), a następnie symuluje zachowanie w obszarze tych trzech komórek dzięki zwiększonej stosownie liczbie stanów. Jako efekt następuje modyfikacja bieżącej komórki i komórek sąsiednich oraz stosowne przesunięcie głowicy.
Policzmy, czy taka symulacja rzeczywiście może być opłacalna. Załóżmy, że każda z komórek odpowiada komórkom maszyny , gdzie - stopień kompresji, który ostatecznie ustalimy na końcu (będzie on oczywiście zależał od i ). Alfabet nowej maszyny to alfabet starej maszyny powiększony o symbole postaci , które zapewniają nam zapis symboli w jednym symbolu .
W pierwszym etapie musi dokonać kompresji, czyli odczytać symboli i zapisać 1 symbol skompresowany. Do tego celu wystarczy jej istnienie około dodatkowych stanów. W ostatnim skompresowanym symbolu dokładamy symbole puste dla wyrównania. Czas działania tego etapu to około (odczyt związany z kompresją + powrót).
W drugim etapie dokonujemy symulacji. Najpierw odczytujemy "obszar trzech" komórek na każdej taśmie co zajmuje 4 kroki :
{ZO-3-2-rys}{Obszar trzech}
Zapamiętujemy odczyt dzięki stosownemu powiększeniu liczby stanów , które wykonujemy podobnie jak przy kompresji. Tym razem nowy zbiór stanów powiększamy o około (trzeba jeszcze pamiętać o zapamiętaniu pozycji każdej głowic wewnątrz obszarów, te szczegóły techniczne pomijamy). W tym momencie mamy pełną wiedzę kiedy maszyna opuści "obszar trzech" i jak zostanie on zmodyfikowany. Oczywiście jeśli w tym czasie maszyna kończy działanie, to tak robi również . W przeciwnym wypadku "obszar trzech" jest modyfikowany kolejnymi 4 ruchami i cykl rozpoczyna się od nowa. Wiemy jednak, że aby opuściła obszar trzech komórek to musi wykonać przynajmniej ruchów. Przyspieszyliśmy zatem działanie przynajmniej o czynnik .
Podsumowując koszt czasowy to , jeśli , dla odpowiednio dużych (dla małych można stosownie rozbudować maszynę).

Twierdzenie nie ma zastosowania dla subliniowych funkcji złożoności, jednak maszyny, które nie czytają całego wejścia wydają się mało interesujące. W przypadku liniowej funkcji złożoności oznacza to, że stała może być dowolnie bliska 1.
Analogicznie twierdzenie zachodzi dla złożoności pamięciowej:
(twierdzenie o liniowej kompresji pamięci) Jeśli język jest rozpoznawany przez maszynę o złożoności pamięciowej to może być rozpoznany przez maszynę o złożoności pamięciowej , gdzie .
Skorzystaj z dowodu twierdzenia o liniowym przyspieszaniu.
Dowód przebiega dokładnie tak samo jeśli chodzi o ideę. Wystarczy tylko dokonać obliczenia zużycia pamięci. Każda komórka nowej maszyny pamięta symboli maszyny , więc koszt pamięciowy to , jeśli , dla odpowiednio dużych (ponownie dla małych można stosownie rozbudować maszynę).
Na koniec ciekawostka dotycząca przyspieszania maszyn. Mając dany język poszukujemy najszybszego algorytmu, który go akceptuje. Okazuje się, że jest język, dla którego nie istnieje algorytm asymptotycznie najszybszy! Autorem tego przeczącego intuicji twierdzenia jest Manuel Blum, laureat nagrody Turinga z roku 1995.
Twierdzenie [Uzupelnij]
(twierdzenie Bluma o przyspieszaniu) Istnieje język , taki, że jeśli jest akceptowany przez maszynę Turinga o złożoności czasowej , to jest również akceptowany, przez maszynę Turinga o złożoności czasowej Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{log}(f(n))} .
Relacje między klasami, twierdzenie Savitcha
Teraz jesteśmy gotowi do wprowadzenia podstawowych klas złożoności, w których funkcje są wyłącznie asymptotyczne:
- Klasa P = Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{TIME}(n^k) = \bigcup_{j>0} \textnormal{TIME}(n^j)} , to klasa tych języków, które mogą być akceptowane w deterministycznym czasie wielomianowym,
- Klasa NP = Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NTIME}(n^k) = \bigcup_{j>0} \textnormal{NTIME}(n^j)} , to klasa tych języków, które mogą być akceptowane w niedeterministycznym czasie wielomianowym,
- Klasa EXP = TIME(), to klasa tych języków, które mogą być akceptowane w deterministycznym czasie wykładniczym,
- Klasa NEXP = NTIME(), to klasa tych języków, które mogą być akceptowane w niedeterministycznym czasie wykładniczym.
dla klas pamięciowych:
- Klasa L = SPACE({log}), to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci logarytmicznej,
- Klasa NL = NSPACE({log}), to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci logarytmicznej,
- Klasa PSPACE = SPACE(), to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci wielomianowej,
- Klasa NPSPACE = NSPACE(), to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci wielomianowej,
- Klasa EXPSPACE = SPACE(), to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci wykładniczej,
- Klasa NEXPSPACE = NSPACE(), to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci wykładniczej.
Teraz zajmiemy się relacjami pomiędzy poszczególnymi klasami złożoności. Najbardziej podstawowe zależności, łączące czas, pamięć i niedeterminizm to:
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{TIME}(f(n))\subseteq\textnormal{NTIME}(f(n))} , gdyż z definicji, każda maszyna deterministyczna jest maszyną niedeterministyczną,
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{SPACE}(f(n))\subseteq\textnormal{NSPACE}(f(n))} , jak wyżej,
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{TIME}(f(n))\subseteq\textnormal{SPACE}(f(n))} , gdyż maszyna nie może zapisać więcej komórek niż wynosi jej czas działania,
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NTIME}(f(n))\subseteq\textnormal{NSPACE}(f(n))} , jak wyżej,
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NTIME}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})} , na podstawie twierdzenia z modułu pierwszego o symulacji
maszyny niedeterministycznej przez maszynę deterministyczną.
Aby powiedzieć więcej o relacjach pomiędzy klasami musimy narzucić pewne rozsądne ograniczenie na funkcję złożoności . Powiemy, że jest konstruowalna pamięciowo, gdy Parser nie mógł rozpoznać (błąd składni): {\displaystyle f(n)\geqslant \textnormal{log}n} oraz istnieje deterministyczna maszyna Turinga, która mając na wejściu zapisane unarnie potrafi zużyć dokładnie komórek pamięci i zatrzymać się.
Zawężamy się w ten sposób do funkcji Parser nie mógł rozpoznać (błąd składni): {\displaystyle f(n)\geqslant \textnormal{log}n} , lecz mniejszych złożoności nie będziemy tutaj rozważać (mimo, iż można). Warto dodać, że jeśli maszyna działa w pamięci Parser nie mógł rozpoznać (błąd składni): {\displaystyle o(\textnormal{log log}n)} to działa w pamięci stałej.
Okazuje się, że większość interesujących funkcji spełnia tą własność. Jest to także własność zamknięta ze względu na dodawanie, mnożenie i potęgowanie.
Pokaż, że funkcje Parser nie mógł rozpoznać (błąd składni): {\displaystyle \lceil \textnormal{log}n \rceil} , , są konstruowalne pamięciowo.
Zastosuj definicję i skonstruuj stosowne maszyny.
Dowód konstruowalności pamięciowej funkcji Parser nie mógł rozpoznać (błąd składni): {\displaystyle \lceil \textnormal{log}n \rceil} opieramy na implementacji klasycznego licznika binarnego. Długość zapisu liczby binarnej to właśnie Parser nie mógł rozpoznać (błąd składni): {\displaystyle \lceil \textnormal{log}n \rceil} . Dla funkcji wielomianowej wykonujemy serię prostych działań arytmetycznych polegających na mnożeniu i dodawaniu liczb naturalnych zapisanych binarnie. W przypadku funkcji wykładniczej wystarczy zaprogramować maszynę, aby razy dokonała podwojenia liczby zużytych komórek.
Poniżej przedstawiamy twierdzenie, które zachodzi, jeśli nie narzucimy dodatkowego warunku na funkcję złożoności. Wprowadzając go, chcemy uniknąć podobnych sytuacji:
Twierdzenie [Uzupelnij]
(twierdzenie o luce) Istnieje funkcja rekurencyjna taka, że Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{TIME}(f(n))=\textnormal{TIME}(2^{f(n)})} .
Dowód [Uzupelnij]
Przedstawimy bardzo specyficzną definicję funkcji , dla której każda maszyna na słowie o długości działa w czasie co najwyżej lub działa przynajmniej kroków lub pętli się. W ten sposób pokażemy stosowną równość klas. Dowód opiera się na bardzo użytecznej i często stosowanej technice przekątniowej.
Będziemy rozważać wszystkie możliwe maszyny w pewnej ustalonej kolejności wynikającej np. z leksykograficznego porządku na ich kodach zdefiniowanych w module pierwszym. Ponieważ każda maszyna może być opisana skończonym słowem, więc wygenerowanie takiego ciągu wszystkich maszyn jest wykonalne.
Zdefiniujmy relację binarną w ten sposób, by była spełniona, gdy każda maszyna od do działając na dowolnym słowie o długości działa w czasie co najwyżej lub działa przynajmniej kroków lub pętli się. Tą relację jesteśmy w stanie obliczyć poprzez stosowną symulację maszyn na wszystkich słowach długości przez co najwyżej kroków (oczywiście jest to dosyć czasochłonne), tym samym ewentualne pętlenie się maszyn nie stanowi przeszkody.
Teraz jesteśmy gotowi do zdefiniowania . Ustalmy . Zauważmy, że musi być prawdziwa dla pewnego . Dzieje się tak dlatego, gdyż wraz ze wzrostem zmieniamy zabroniony obszar czasu działania maszyn od do . Liczba słów które testujemy jest jednak ograniczona -- są to wszystkie słowa o długości dokładnie dla tych maszyn. Aby nie było prawdą to czas działania maszyny na słowie musi trafić do obszaru zabronionego, co wobec ustalonej liczby słów i zwiększania spowoduje, że w końcu będzie prawdą. Definiujemy wartość jako najmniejsze takie .
Weźmy dowolny język Parser nie mógł rozpoznać (błąd składni): {\displaystyle L\in\textnormal{TIME}(2^{f(n)})} . Jest on akceptowany przez maszynę, którą oznaczmy (w naszym porządku ustalonym w pierwszej części). Maszyna ma złożoność . Weźmy dowolne słowo o długości . Wiemy, że jest spełnione, a tym samym maszyna działa w czasie co najwyżej (bo więcej niż nie może z definicji klasy). Zatem Parser nie mógł rozpoznać (błąd składni): {\displaystyle L\in\textnormal{TIME}(f(n))} .
Pominęliśmy działanie na słowach krótszych niż , jednakże jest to stała liczba słów, które łatwo zaakceptować w czasie rzędu ich długości po prostu wbudowując ich przynależność do w maszynę.

W literaturze rozważa się wiele wersji "normujących" dopuszczalne funkcji złożoności, np. właściwie funkcje złożoności lub funkcje uczciwe. Różnice między nimi są dosyć techniczne. Przyjmijmy zatem, że funkcje złożoności są konstruowalne pamięciowo.
Przeanalizujmy teraz możliwe konfiguracje maszyny Turinga , które tworzą tzw. graf przejść maszyny:
W jak wielu konfiguracjach może znaleźć się maszyna Turinga o złożoności pamięciowej (konstruowalnej pamięciowo) przeprowadzając obliczenie na słowie o długości ?
Policz liczbę stanów maszyny, położeń głowic i zawartości taśm.
Liczba możliwych konfiguracji to oczywiście iloczyn:
- liczby stanów ,
- położeń głowic na wszystkich taśmach, jest rzędu jednak nie mniej niż (dokładny wynik zależy od przyjętych wyróżnień taśmy wejściowej i wyjściowej),
- zawartości taśm, czyli .
Razem możemy to ograniczyć przez , dla pewnego zależnego tylko od maszyny oraz korzystając z założenia, że Parser nie mógł rozpoznać (błąd składni): {\displaystyle f(n)\geqslant \textnormal{log}n} , gdyż jest konstruowalna pamięciowo.
Teraz jesteśmy gotowi do wypowiedzenia kolejnych interesujących relacji pomiędzy wprowadzonymi klasami:
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{SPACE}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})} , ze względu na fakt, iż liczba możliwych konfiguracji maszyny o złożoności pamięciowej ,
co pokazaliśmy przed chwilą wynosi , zatem maszyna, która się nie pętli może zostać zasymulowana przez maszynę działającą co najwyżej tak długo. W przeciwnym wypadku wpadła by w nieskończoną pętlę.
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NTIME}(f(n))\subseteq\textnormal{SPACE}(f(n))} , gdyż maszyna deterministyczna może zasymulować działanie maszyny niedeterministycznej.
Wystarczy wygenerować po kolei każdy z ciągów niedeterministycznych wyborów (tu korzystamy z pamięciowej konstruowalności), których musi ona dokonać w trakcie obliczeń. Następnie dokonujemy już deterministycznej symulacji obliczeń przez kroków. Wszystkie te operacje można dokonać w dostępnej pamięci , gdyż każdy z ciągów niedeterministycznych wyborów możemy symulować w tej samej pamięci,
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NSPACE}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})} , ponownie opierając się na symulacji maszyny.
Jak poprzednio liczba wszystkich konfiguracji wynosi , jednak tym razem przejścia pomiędzy poszczególnymi konfiguracjami tworzą graf. Wystarczy jednak obliczyć, czy istnieje ścieżka od konfiguracji początkowej do konfiguracji końcowej, co może być obliczone w czasie wielomianowym ze względu na rozmiar grafu, a zatem w czasie asymptotycznym .
Dzięki powyższym relacjom możemy wypisać kilka podstawowych zależności pomiędzy wprowadzonymi klasami:
Uzasadnij każdą z poniższych relacji:
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{L}\subseteq\textnormal{NL}\subseteq\textnormal{P}\subseteq\textnormal{NP}\subseteq\textnormal{PSPACE}}
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{PSPACE}\subseteq\textnormal{NPSPACE}\subseteq\textnormal{EXP}\subseteq\textnormal{NEXP}\subseteq\textnormal{EXPSPACE}\subseteq\textnormal{NEXPSPACE}}
Skorzystaj z poznanych już własności klas oraz własności funkcji logarytmicznej, wielomianowej i wykładniczej.
Wszystkie zawierania pochodzą z wypisanych wcześniej ogólnych zależności. Nietrywialne, to:
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NL}\subseteq\textnormal{P}} , korzystamy z faktu, że Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NSPACE}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})} , w tym wypadku
mamy bowiem Parser nie mógł rozpoznać (błąd składni): {\displaystyle c^{\textnormal{log}n}=n^k} ,
- Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NPSPACE}\subseteq\textnormal{EXP}} , również korzystamy z faktu, że Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NSPACE}(f(n))\subseteq\textnormal{TIME}(c^{f(n)})} , w tym wypadku
mamy bowiem .
Przyjrzyjmy się bliżej pierwszej serii relacji z poprzedniego ćwiczenia i zobrazujmy ją na rysunku:
{ZO-3-3-rys}{Relacje pomiędzy podstawowymi klasami} Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{L}\subseteq\textnormal{NL}\subseteq\textnormal{P}\subseteq\textnormal{NP}\subseteq\textnormal{PSPACE}} W następnej części z twierdzenia o hierarchii pamięciowej dowiemy się, że Parser nie mógł rozpoznać (błąd składni): {\displaystyle L\varsubsetneq \textnormal{PSPACE}} . Tym samym wiemy, że pierwszy i ostatni element są różne. Jedną z najbardziej fascynujących rzeczy w teorii złożoności jest fakt, że przynajmniej jedno z czterech powyższych zawierań musi być ścisłe, jednakże o żadnym z nich tego nie wiadomo! Najsłynniejszy fragment to oczywiście pytanie o zawieranie pomiędzy P i NP.
Ostatnią i najciekawszą relacją pomiędzy klasami jest ta odkryta przez Savitcha, mówiąca o niewielkiej przewadze niedeterministycznej złożoności pamięciowej. Przypomnijmy, że do tej pory wiemy poprzez połączenie dwóch wymienionych własności, że Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NSPACE}(f(n))\subseteq\textnormal{SPACE}(c^{f(n)})} . Okazuje się, że zachodzi twierdzenie dużo silniejsze:
Twierdzenie [Uzupelnij]
(twierdzenie Savitcha) Jeśli jest konstruowalna pamięciowo, to Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{NSPACE}(f(n))\subseteq\textnormal{SPACE}(f^2(n))} .
Dowód [Uzupelnij]
Wspominaliśmy już o tym, że kluczowym elementem symulacji maszyny niedeterministycznej jest sprawdzanie czy istnieje ścieżka od konfiguracji początkowej do końcowej maszyny. Problem sprawdzania czy dwa wierzchołki w grafie są połączone ścieżką jest znany jako REACHABILITY. Savitch udowodnił, że REACHABILITY należy do SPACE(Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{log}^2n} ), gdzie to rozmiar grafu. W naszym wypadku rozmiar grafu to liczba konfiguracji, czyli , zatem REACHABILITY wymaga czasu Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{log}^2(c^{f(n)})=O(f(n))^2} , co da nam tezę. Od tej pory przyjmijmy, że graf ma wierzchołków.
Wprowadźmy pomocniczą funkcję Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{PATH}(x,y,i)} która ma zwrócić 1, gdy istnieje ścieżka od do w grafie zawierająca co najwyżej wierzchołków. Nasze pytanie możemy przeformułować jako , gdzie to stan początkowy, końcowy, a to maksymalna długość ścieżki w grafie konfiguracji, czyli liczba wszystkich wierzchołków.
Obliczymy Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{PATH}(x,y,i)} w sposób rekurencyjny. Jeśli to sprawdzamy bezpośrednie przejście od konfiguracji do . W przeciwnym wypadku najpierw wybieramy dowolny wierzchołek jako kandydata na wierzchołek pośredni na ścieżce pomiędzy i , który oznaczmy przez :
{ZO-3-4-rys}{Wierzchołek pośredni}
Potem wywołujemy rekurencyjnie Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{PATH}(x,t,i/2)} oraz Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{PATH}(t,y,i/2)} . Jeśli obie ścieżki istnieją to zwracamy 1, w przeciwnym wypadku próbujemy kolejny wierzchołek . Nie musimy się martwić o czas, tylko pamięć, więc policzmy ile jest nam potrzebne.
Głębokość rekurencji wyniesie Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{log}n} , ze względu na fakt iż na każdym poziomie zmniejszamy długość o czynnik 2. Aby zapamiętać wywołanie rekurencyjne (stos rekurencyjny) potrzebujemy pamiętać , , bieżące oraz . Wszystkie te liczby w zapisie binarnym potrzebują Parser nie mógł rozpoznać (błąd składni): {\displaystyle \textnormal{log}n} pamięci. Razem potrzebujemy Parser nie mógł rozpoznać (błąd składni): {\displaystyle O(\textnormal{log}^2n)} pamięci.

Na podstawie twierdzenia Savitcha wiemy, że niektóre z poznanych klas są sobie równe:
- PSPACE = NPSPACE,
- EXPSPACE = NEXPSPACE.
czyli, że niedeterminizm w złożoności pamięciowej dla większych funkcji nic nie daje. Dla klas złożoności czasowej takiej wiedzy nie posiadamy!