Złożoność obliczeniowa/Wykład 3: Klasy złożoności obliczeniowej: Różnice pomiędzy wersjami
Matiunreal (dyskusja | edycje) |
m Zastępowanie tekstu – „ </math>” na „</math>” |
||
(Nie pokazano 18 wersji utworzonych przez 3 użytkowników) | |||
Linia 15: | Linia 15: | ||
czas i pamięć potrzebne do klasyfikacji języka dają nam podstawowe klasy złożoności: | czas i pamięć potrzebne do klasyfikacji języka dają nam podstawowe klasy złożoności: | ||
{{definicja|1.1 [<math>\ | {{definicja|1.1 [<math>\text{TIME}(f(n))</math> ]|| | ||
Poprzez <math>\ | Poprzez <math>\text{TIME}(f(n))</math> oznaczamy zbiór języków <math>L</math> takich, że są akceptowane przez deterministyczną maszynę Turinga <math>M</math> o złożoności | ||
czasowej <math>f(n)</math>. | czasowej <math>f(n)</math>. | ||
}} | }} | ||
{{definicja|1.2 [<math>\ | {{definicja|1.2 [<math>\text{SPACE}(f(n))</math>]|| | ||
Poprzez <math>\ | Poprzez <math>\text{SPACE}(f(n))</math> oznaczamy zbiór języków <math>L</math> takich, że są akceptowane przez deterministyczną maszynę Turinga <math>M</math> o złożoności | ||
pamięciowej <math>f(n)</math>. | pamięciowej <math>f(n)</math>. | ||
}} | }} | ||
Linia 27: | Linia 27: | ||
Stosowne klasy można też zdefiniować dla niedeterministycznych maszyn: | Stosowne klasy można też zdefiniować dla niedeterministycznych maszyn: | ||
{{definicja|1.3 [<math>\ | {{definicja|1.3 [<math>\text{NTIME}(f(n))</math>]|| | ||
Poprzez <math>\ | Poprzez <math>\text{NTIME}(f(n))</math> oznaczamy zbiór języków <math>L</math> takich, | ||
że są akceptowane przez niedeterministyczną maszynę Turinga <math>M</math> o | że są akceptowane przez niedeterministyczną maszynę Turinga <math>M</math> o | ||
złożoności czasowej <math>f(n)</math>. | złożoności czasowej <math>f(n)</math>. | ||
}} | }} | ||
{{definicja|1.4 [<math>\ | {{definicja|1.4 [<math>\text{NSPACE}(f(n))</math>]|| | ||
Poprzez <math>\ | Poprzez <math>\text{NSPACE}(f(n))</math> oznaczamy zbiór języków <math>L</math> takich, że są akceptowane przez niedeterministyczną maszynę Turinga <math>M</math> o złożoności | ||
pamięciowej <math>f(n)</math>. | pamięciowej <math>f(n)</math>. | ||
}} | }} | ||
Linia 42: | Linia 42: | ||
Pierwsze dwa twierdzenia możemy nazwać "teoretycznymi podstawami" | Pierwsze dwa twierdzenia możemy nazwać "teoretycznymi podstawami" | ||
notacji <math>O</math>. Pokażemy bowiem, że w obu powyższych definicjach klas | notacji <math>O</math>. Pokażemy bowiem, że w obu powyższych definicjach klas | ||
funkcja <math>f(n)</math> 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 [ | funkcja <math>f(n)</math> 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 [[Nagroda_Turinga|nagrody Turinga]] z roku 1993, Jurisa Hartmanisa i Richarda Stearnsa. | ||
{{twierdzenie|2.1 [twierdzenie o liniowym przyspieszaniu]|| Jeśli język <math>L</math> jest rozpoznawany przez maszynę <math>M</math> o złożoności czasowej <math>f(n)</math> to może być rozpoznany przez maszynę <math>M'</math> o złożoności czasowej <math>f'(n)=\epsilon f(n) + (1+\epsilon) n</math>, gdzie <math>\epsilon>0</math>. | {{twierdzenie|2.1 [twierdzenie o liniowym przyspieszaniu]|| Jeśli język <math>L</math> jest rozpoznawany przez maszynę <math>M</math> o złożoności czasowej <math>f(n)</math> to może być rozpoznany przez maszynę <math>M'</math> o złożoności czasowej <math>f'(n)=\epsilon f(n) + (1+\epsilon) n</math>, gdzie <math>\epsilon>0</math>. | ||
Linia 48: | Linia 48: | ||
<!-- [[ZO-3-1-rys.jpg|(Liniowe przyspieszanie)]] --> | <!-- [[ZO-3-1-rys.jpg|(Liniowe przyspieszanie)]] --> | ||
[[File:ZO-3-1-ry.mp4|253x253px|thumb|right|Liniowe przyspieszanie]] | |||
<!-- [[ZO-3-2-rys.jpg (Obszar trzech)]] --> | <!-- [[ZO-3-2-rys.jpg (Obszar trzech)]] --> | ||
[[File:ZO-3-2-rys.svg|250x250px|thumb|right|Obszar trzech]] | |||
{{dowod||| | {{dowod||| | ||
Idea dowodu jest oparta na powiększeniu alfabetu maszyny, a tym samym wielkości "słowa" maszyny oraz liczby jej stanów. | 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. | Odpowiada to w praktyce podniesieniu technologii wytwarzania komputerów. W animacji [http://osilek.mimuw.edu.pl/images/a/aa/ZO-3-1-ry.swf Liniowe przyspieszanie] przedstawiono schematycznie zamianę 8 komórek pamięci na jedną większą nowej maszyny. | ||
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 74: | Linia 71: | ||
Czas działania tego etapu to około <math>n+\lceil n/c \rceil</math> (odczyt związany z kompresją + powrót). | Czas działania tego etapu to około <math>n+\lceil n/c \rceil</math> (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 <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> (rysunek [http://osilek.mimuw.edu.pl/images/1/1b/ZO-3-2-rys.swf Obszar trzech]). | ||
Zapamiętujemy odczyt dzięki stosownemu powiększeniu liczby stanów <math>M'</math>, które wykonujemy podobnie jak przy kompresji. Tym razem nowy zbiór stanów powiększamy o około <math>\Gamma^{3c}</math> | Zapamiętujemy odczyt dzięki stosownemu powiększeniu liczby stanów <math>M'</math>, które wykonujemy podobnie jak przy kompresji. Tym razem nowy zbiór stanów powiększamy o około <math>\Gamma^{3c}</math> | ||
Linia 107: | Linia 104: | ||
Mając dany język <math>L</math> poszukujemy najszybszego algorytmu, który go akceptuje. | Mając dany język <math>L</math> poszukujemy najszybszego algorytmu, który go akceptuje. | ||
Okazuje się, że jest język, dla którego nie istnieje algorytm asymptotycznie najszybszy! | Okazuje się, że jest język, dla którego nie istnieje algorytm asymptotycznie najszybszy! | ||
Autorem tego przeczącego intuicji twierdzenia jest Manuel Blum, laureat [ | Autorem tego przeczącego intuicji twierdzenia jest Manuel Blum, laureat [[Nagroda_Turinga|nagrody Turinga]] z roku 1995. | ||
{{twierdzenie|2.3 [twierdzenie Bluma o przyspieszaniu]|| | {{twierdzenie|2.3 [twierdzenie Bluma o przyspieszaniu]|| | ||
Istnieje język <math>L</math>, taki, że jeśli jest akceptowany przez maszynę Turinga o złożoności czasowej <math>f(n)</math>, to jest | Istnieje język <math>L</math>, taki, że jeśli jest akceptowany przez maszynę Turinga o złożoności czasowej <math>f(n)</math>, to jest | ||
również akceptowany, przez maszynę Turinga o złożoności czasowej <math>\ | również akceptowany, przez maszynę Turinga o złożoności czasowej <math>\text{log}(f(n))</math>. | ||
}} | }} | ||
Linia 118: | Linia 115: | ||
Teraz jesteśmy gotowi do wprowadzenia podstawowych klas złożoności, w których funkcje są wyłącznie asymptotyczne: | Teraz jesteśmy gotowi do wprowadzenia podstawowych klas złożoności, w których funkcje są wyłącznie asymptotyczne: | ||
* Klasa <math>\ | * Klasa <math>\text{P} = \bigcup_{j>0} \text{TIME}(n^j)</math>, to klasa tych języków, które mogą być akceptowane w deterministycznym czasie wielomianowym, | ||
* Klasa <math>\ | * Klasa <math>\text{NP} = \bigcup_{j>0} \text{NTIME}(n^j)</math>, to klasa tych języków, które mogą być akceptowane w niedeterministycznym czasie wielomianowym, | ||
* Klasa <math>\ | * Klasa <math>\text{EXP} = \bigcup_{j>0} \text{TIME}(2^{n^j})</math>, to klasa tych języków, które mogą być akceptowane w deterministycznym czasie wykładniczym, | ||
* Klasa <math>\ | * Klasa <math>\text{NEXP} = \bigcup_{j>0} \text{NTIME}(2^{n^j})</math>, to klasa tych języków, które mogą być akceptowane w niedeterministycznym czasie wykładniczym. | ||
dla klas pamięciowych: | dla klas pamięciowych: | ||
* Klasa <math>\ | * Klasa <math>\text{L} = \text{SPACE}(</math>log<math>n)</math>, to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci logarytmicznej, | ||
* Klasa <math>\ | * Klasa <math>\text{NL} = \text{NSPACE}(</math>log<math>n)</math>, to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci logarytmicznej, | ||
* Klasa <math>\ | * Klasa <math>\text{PSPACE} = \bigcup_{j>0} \text{SPACE}(n^j)</math>, to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci wielomianowej, | ||
* Klasa <math>\ | * Klasa <math>\text{NPSPACE} = \bigcup_{j>0} \text{NSPACE}(n^j)</math>, to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci wielomianowej, | ||
* Klasa <math>\ | * Klasa <math>\text{EXPSPACE} = \bigcup_{j>0} \text{SPACE}(2^{n^j})</math>, to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci wykładniczej, | ||
* Klasa <math>\ | * Klasa <math>\text{NEXPSPACE} = \bigcup_{j>0} \text{NSPACE}(2^{n^j})</math>, 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: | 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: | ||
* <math>\ | * <math>\text{TIME}(f(n))\subseteq\text{NTIME}(f(n))</math>, gdyż z definicji, każda maszyna deterministyczna jest maszyną niedeterministyczną, | ||
* <math>\ | * <math>\text{SPACE}(f(n))\subseteq\text{NSPACE}(f(n))</math>, jak wyżej, | ||
* <math>\ | * <math>\text{TIME}(f(n))\subseteq\text{SPACE}(f(n))</math>, gdyż maszyna nie może zapisać więcej komórek niż wynosi jej czas działania, | ||
* <math>\ | * <math>\text{NTIME}(f(n))\subseteq\text{NSPACE}(f(n))</math>, jak wyżej, | ||
* <math>\ | * <math>\text{NTIME}(f(n))\subseteq\text{TIME}(c^{f(n)})</math>, na podstawie twierdzenia z modułu pierwszego o symulacji | ||
maszyny niedeterministycznej przez maszynę deterministyczną. | 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 <math>f(n)</math>. Powiemy, że <math>f(n)</math> jest ''konstruowalna pamięciowo'', gdy <math>f(n)\geqslant \ | Aby powiedzieć więcej o relacjach pomiędzy klasami musimy narzucić pewne rozsądne ograniczenie na funkcję złożoności <math>f(n)</math>. Powiemy, że <math>f(n)</math> jest ''konstruowalna pamięciowo'', gdy <math>f(n)\geqslant \text{log}n</math> oraz istnieje deterministyczna maszyna Turinga, która mając na wejściu <math>n</math> zapisane unarnie potrafi | ||
zużyć dokładnie <math>f(n)</math> komórek pamięci i zatrzymać się. | zużyć dokładnie <math>f(n)</math> komórek pamięci i zatrzymać się. | ||
Zawężamy się w ten sposób do funkcji <math>f(n)\geqslant \ | Zawężamy się w ten sposób do funkcji <math>f(n)\geqslant \text{log}n</math>, 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 <math>o(\ | Warto dodać, że jeśli maszyna działa w pamięci <math>o(\text{log log}n)</math> 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. | 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. | ||
{{cwiczenie|3.1|cw 3.1| | {{cwiczenie|3.1|cw 3.1| | ||
Pokaż, że funkcje <math>\lceil \ | Pokaż, że funkcje <math>\lceil \text{log}n \rceil</math>, <math>n^k</math>, <math>2^{n}</math> są ''konstruowalne pamięciowo''. | ||
}} | }} | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none"> Zastosuj definicję i skonstruuj stosowne maszyny. | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none"> Zastosuj definicję i skonstruuj stosowne maszyny. | ||
Linia 156: | Linia 153: | ||
</div> | </div> | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> Dowód konstruowalności pamięciowej funkcji | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> Dowód konstruowalności pamięciowej funkcji | ||
<math>\lceil \ | <math>\lceil \text{log}n \rceil</math> opieramy na implementacji klasycznego licznika binarnego. Długość zapisu liczby binarnej <math>n</math> to właśnie <math>\lceil \text{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. | ||
Linia 164: | Linia 161: | ||
sytuacji: | sytuacji: | ||
{{twierdzenie|3.2 [twierdzenie o luce]|tw 3.2| Istnieje funkcja rekurencyjna <math>f(n)</math> taka, że <math>\ | {{twierdzenie|3.2 [twierdzenie o luce]|tw 3.2| Istnieje funkcja rekurencyjna <math>f(n)</math> taka, że <math>\text{TIME}(f(n))=\text{TIME}(2^{f(n)})</math>. | ||
}} | }} | ||
Linia 184: | Linia 181: | ||
na słowie musi trafić do obszaru zabronionego, co wobec ustalonej liczby słów i zwiększania <math>k</math> spowoduje, że <math>P(n,k)</math> w końcu będzie prawdą. Definiujemy wartość <math>f(n)</math> jako najmniejsze takie <math>k</math>. | na słowie musi trafić do obszaru zabronionego, co wobec ustalonej liczby słów i zwiększania <math>k</math> spowoduje, że <math>P(n,k)</math> w końcu będzie prawdą. Definiujemy wartość <math>f(n)</math> jako najmniejsze takie <math>k</math>. | ||
Weźmy dowolny język <math>L\in\ | Weźmy dowolny język <math>L\in\text{TIME}(2^{f(n)})</math>. Jest on akceptowany przez maszynę, którą oznaczmy <math>M_j</math> (w naszym porządku ustalonym w pierwszej | ||
części). Maszyna ma złożoność <math>2^{f(n)}</math>. Weźmy dowolne słowo o długości <math>l\geqslant j</math>. Wiemy, że <math>P(l,f(l))</math> jest spełnione, a tym | części). Maszyna ma złożoność <math>2^{f(n)}</math>. Weźmy dowolne słowo o długości <math>l\geqslant j</math>. Wiemy, że <math>P(l,f(l))</math> jest spełnione, a tym | ||
samym maszyna <math>M_j</math> działa w czasie co najwyżej <math>f(l)</math> (bo więcej niż <math>2^{f(l)}</math> nie może z definicji klasy). Zatem <math>L\in\ | samym maszyna <math>M_j</math> działa w czasie co najwyżej <math>f(l)</math> (bo więcej niż <math>2^{f(l)}</math> nie może z definicji klasy). Zatem <math>L\in\text{TIME}(f(n))</math>. | ||
Pominęliśmy działanie na słowach krótszych niż <math>j</math>, jednakże jest to stała liczba słów, które łatwo zaakceptować w czasie rzędu ich długości | Pominęliśmy działanie na słowach krótszych niż <math>j</math>, jednakże jest to stała liczba słów, które łatwo zaakceptować w czasie rzędu ich długości | ||
Linia 213: | Linia 210: | ||
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 \ | <math>f(n)\geqslant \text{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: | ||
* <math>\ | * <math>\text{SPACE}(f(n))\subseteq\text{TIME}(c^{f(n)})</math>, ze względu na fakt, iż liczba możliwych konfiguracji maszyny o złożoności pamięciowej <math>f(n)</math>, | ||
co pokazaliśmy przed chwilą wynosi <math>c^{f(n)}</math>, zatem maszyna, która się nie pętli może zostać | co pokazaliśmy przed chwilą wynosi <math>c^{f(n)}</math>, 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ę. | zasymulowana przez maszynę działającą co najwyżej tak długo. W przeciwnym wypadku wpadła by w nieskończoną pętlę. | ||
* <math>\ | * <math>\text{NTIME}(f(n))\subseteq\text{SPACE}(f(n))</math>, gdyż maszyna deterministyczna może zasymulować działanie maszyny niedeterministycznej. | ||
Wystarczy wygenerować po kolei każdy z ciągów <math>f(n)</math> niedeterministycznych wyborów (tu korzystamy z pamięciowej konstruowalności), których musi ona dokonać w trakcie obliczeń. | Wystarczy wygenerować po kolei każdy z ciągów <math>f(n)</math> 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 <math>f(n)</math> kroków. Wszystkie te operacje można dokonać w dostępnej pamięci <math>f(n)</math>, gdyż | Następnie dokonujemy już deterministycznej symulacji obliczeń przez <math>f(n)</math> kroków. Wszystkie te operacje można dokonać w dostępnej pamięci <math>f(n)</math>, gdyż | ||
każdy z ciągów niedeterministycznych wyborów możemy symulować w tej samej pamięci, | każdy z ciągów niedeterministycznych wyborów możemy symulować w tej samej pamięci, | ||
* <math>\ | * <math>\text{NSPACE}(f(n))\subseteq\text{TIME}(c^{f(n)})</math>, ponownie opierając się na symulacji maszyny. | ||
Jak poprzednio liczba wszystkich konfiguracji wynosi <math>c^{f(n)}</math>, jednak tym razem przejścia pomiędzy poszczególnymi | Jak poprzednio liczba wszystkich konfiguracji wynosi <math>c^{f(n)}</math>, 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ć | konfiguracjami tworzą graf. Wystarczy jednak obliczyć, czy istnieje ścieżka od konfiguracji początkowej do konfiguracji końcowej, co może być | ||
Linia 232: | Linia 229: | ||
{{cwiczenie|3.4|cw 3.4| | {{cwiczenie|3.4|cw 3.4| | ||
Uzasadnij każdą z poniższych relacji: | Uzasadnij każdą z poniższych relacji: | ||
* <math>\ | * <math>\text{L}\subseteq\text{NL}\subseteq\text{P}\subseteq\text{NP}\subseteq\text{PSPACE}</math> | ||
* <math>\ | * <math>\text{PSPACE}\subseteq\text{NPSPACE}\subseteq\text{EXP}\subseteq\text{NEXP}\subseteq\text{EXPSPACE}\subseteq\text{NEXPSPACE}</math> | ||
}} | }} | ||
Linia 240: | Linia 237: | ||
</div> | </div> | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> Wszystkie zawierania pochodzą z wypisanych wcześniej ogólnych zależności. Nietrywialne, to: | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> Wszystkie zawierania pochodzą z wypisanych wcześniej ogólnych zależności. Nietrywialne, to: | ||
* <math>\ | * <math>\text{NL}\subseteq\text{P}</math>, korzystamy z faktu, że <math>\text{NSPACE}(f(n))\subseteq\text{TIME}(c^{f(n)})</math>, w tym wypadku mamy bowiem <math>c^{\text{log}n}=n^k</math>, | ||
mamy bowiem <math>c^{\ | * <math>\text{NPSPACE}\subseteq\text{EXP}</math>, również korzystamy z faktu, że <math>\text{NSPACE}(f(n))\subseteq\text{TIME}(c^{f(n)})</math>, w tym wypadku mamy bowiem <math>c^{n^k}=2^{n^{k'}}</math>. | ||
* <math>\ | |||
mamy bowiem <math>c^{n^k}=2^{n^{k'}}</math>. | |||
</div> | </div> | ||
</div> | </div> | ||
Przyjrzyjmy się bliżej pierwszej serii relacji z poprzedniego ćwiczenia i zobrazujmy ją | Przyjrzyjmy się bliżej pierwszej serii relacji z poprzedniego ćwiczenia i zobrazujmy ją w animacji [http://osilek.mimuw.edu.pl/images/3/34/ZO-3-3-ry.swf Relacje pomiędzy podstawowymi klasami]. | ||
[[File:ZO-3-3-ry.mp4|253x253px|thumb|right]]|size=small</flashwrap><div.thumbcaption style="width:250;">Relacje pomiędzy podstawowymi klasami</div></div> | |||
<!-- [[ZO-3-3-rys.jpg(Rys.3.3. Relacje pomiędzy podstawowymi klasami)]] --> | <!-- [[ZO-3-3-rys.jpg(Rys.3.3. Relacje pomiędzy podstawowymi klasami)]] --> | ||
<center><math>\ | <center><math>\text{L}\subseteq\text{NL}\subseteq\text{P}\subseteq\text{NP}\subseteq\text{PSPACE}</math></center> | ||
W następnej części z twierdzenia o hierarchii pamięciowej dowiemy się, że <math>L\varsubsetneq \ | W następnej części z twierdzenia o hierarchii pamięciowej dowiemy się, że <math>L\varsubsetneq \text{PSPACE}</math>. 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 | 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 | jedno z czterech powyższych zawierań musi być ścisłe, jednakże o ''żadnym z nich'' tego nie wiadomo! Najsłynniejszy fragment to | ||
Linia 258: | Linia 253: | ||
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. | 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 <math>\ | Przypomnijmy, że do tej pory wiemy poprzez połączenie dwóch wymienionych własności, że <math>\text{NSPACE}(f(n))\subseteq\text{SPACE}(c^{f(n)})</math>. Okazuje | ||
się, że zachodzi twierdzenie dużo silniejsze: | się, że zachodzi twierdzenie dużo silniejsze: | ||
{{twierdzenie|3.5 [twierdzenie Savitcha]|tw 3.5| | {{twierdzenie|3.5 [twierdzenie Savitcha]|tw 3.5| | ||
Jeśli <math>f(n)</math> jest konstruowalna pamięciowo, to <math>\ | Jeśli <math>f(n)</math> jest konstruowalna pamięciowo, to <math>\text{NSPACE}(f(n))\subseteq\text{SPACE}(f^2(n))</math>. | ||
}} | }} | ||
[[File:ZO-3-4-rys.svg|250x200px|thumb|right|Wierzchołek pośredni]] | |||
<!-- [[ZO-3-4-rys.jpg(Wierzchołek pośredni)]] --> | <!-- [[ZO-3-4-rys.jpg(Wierzchołek pośredni)]] --> | ||
Linia 274: | Linia 266: | ||
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. | 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 | Problem sprawdzania czy dwa wierzchołki w grafie są połączone ścieżką jest znany jako REACHABILITY. Savitch udowodnił, że | ||
REACHABILITY należy do <math>SPACE </math>(<math>\ | REACHABILITY należy do <math>SPACE</math>(<math>\text{log}^2n</math>), gdzie <math>n</math> to rozmiar grafu. | ||
W naszym wypadku rozmiar grafu to liczba konfiguracji, czyli <math>c^{f(n)}</math>, zatem | W naszym wypadku rozmiar grafu to liczba konfiguracji, czyli <math>c^{f(n)}</math>, zatem | ||
REACHABILITY wymaga czasu <math>\ | REACHABILITY wymaga czasu <math>\text{log}^2(c^{f(n)})=O(f(n))^2</math>, co da nam tezę. | ||
Od tej pory przyjmijmy, że graf ma <math>n</math> wierzchołków. | Od tej pory przyjmijmy, że graf ma <math>n</math> wierzchołków. | ||
Wprowadźmy pomocniczą funkcję <math>\ | Wprowadźmy pomocniczą funkcję <math>\text{PATH}(x,y,i)</math> która ma zwrócić 1, gdy istnieje ścieżka od <math>x</math> do <math>y</math> w grafie zawierająca co najwyżej <math>i</math> | ||
wierzchołków. Nasze pytanie możemy przeformułować jako <math>PATH(b,e,n)</math>, gdzie <math>b</math> to stan początkowy, <math>e</math> końcowy, a <math>n</math> to | wierzchołków. Nasze pytanie możemy przeformułować jako <math>PATH(b,e,n)</math>, gdzie <math>b</math> to stan początkowy, <math>e</math> końcowy, a <math>n</math> to | ||
maksymalna długość ścieżki w grafie konfiguracji, czyli liczba wszystkich wierzchołków. | maksymalna długość ścieżki w grafie konfiguracji, czyli liczba wszystkich wierzchołków. | ||
Obliczymy <math>\ | Obliczymy <math>\text{PATH}(x,y,i)</math> w sposób rekurencyjny. Jeśli <math>i=1</math> to sprawdzamy bezpośrednie przejście od konfiguracji <math>x</math> do <math>y</math>. | ||
W przeciwnym wypadku najpierw wybieramy dowolny wierzchołek jako kandydata na wierzchołek pośredni na ścieżce pomiędzy <math>x</math> i <math>y</math>, który | W przeciwnym wypadku najpierw wybieramy dowolny wierzchołek jako kandydata na wierzchołek pośredni na ścieżce pomiędzy <math>x</math> i <math>y</math>, który | ||
oznaczmy przez <math>t</math> ([ | oznaczmy przez <math>t</math> (rysunek [http://osilek.mimuw.edu.pl/images/6/63/ZO-3-4-rys.swf Wierzchołek pośredni]). | ||
Potem wywołujemy rekurencyjnie <math>\ | Potem wywołujemy rekurencyjnie <math>\text{PATH}(x,t,i/2)</math> oraz <math>\text{PATH}(t,y,i/2)</math>. Jeśli obie ścieżki istnieją to zwracamy 1, | ||
w przeciwnym wypadku próbujemy kolejny wierzchołek <math>t</math>. Nie musimy się martwić o czas, tylko pamięć, więc policzmy ile jest nam potrzebne. | w przeciwnym wypadku próbujemy kolejny wierzchołek <math>t</math>. Nie musimy się martwić o czas, tylko pamięć, więc policzmy ile jest nam potrzebne. | ||
Głębokość rekurencji wyniesie <math>\ | Głębokość rekurencji wyniesie <math>\text{log}n</math>, 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ć <math>x</math>, <math>y</math>, bieżące <math>t</math> oraz <math>i</math>. Wszystkie te liczby | Aby zapamiętać wywołanie rekurencyjne (stos rekurencyjny) potrzebujemy pamiętać <math>x</math>, <math>y</math>, bieżące <math>t</math> oraz <math>i</math>. Wszystkie te liczby | ||
w zapisie binarnym potrzebują <math>\ | w zapisie binarnym potrzebują <math>\text{log}n</math> pamięci. Razem potrzebujemy <math>O(\text{log}^2n)</math> pamięci. | ||
}} | }} | ||
Linia 316: | Linia 308: | ||
Zdefiniujemy teraz pojęcie dopełnienia klasy złożoności. Przypomnijmy, że klasy złożoności składają się z języków. | Zdefiniujemy teraz pojęcie dopełnienia klasy złożoności. Przypomnijmy, że klasy złożoności składają się z języków. | ||
Jeśli <math>C</math> jest dowolną klasą złożoności to przez <math>\ | Jeśli <math>C</math> jest dowolną klasą złożoności to przez <math>\text{co}C</math> | ||
oznaczamy jej ''dopełnienie'', które jest złożone z dopełnień języków z klasy <math>C</math>. | oznaczamy jej ''dopełnienie'', które jest złożone z dopełnień języków z klasy <math>C</math>. | ||
Zauważmy od razu, że jeśli <math>C</math> jest klasą deterministyczną, to <math>\ | Zauważmy od razu, że jeśli <math>C</math> jest klasą deterministyczną, to <math>\text{co}C=C</math> ze względu na fakt, iż | ||
maszyna deterministyczna, która akceptuje język <math>L\in C</math> po zamianie rolami stanu akceptującego i odrzucającego | maszyna deterministyczna, która akceptuje język <math>L\in C</math> po zamianie rolami stanu akceptującego i odrzucającego | ||
stanie się maszyną akceptującą język <math>\overline{L}</math>. | stanie się maszyną akceptującą język <math>\overline{L}</math>. | ||
Linia 334: | Linia 326: | ||
{{twierdzenie|3.6 [twierdzenie o hierarchii pamięciowej]|tw 3.6| | {{twierdzenie|3.6 [twierdzenie o hierarchii pamięciowej]|tw 3.6| | ||
Jeśli <math>f(n)</math> jest konstruowalna pamięciowo oraz <math>g(n) \in o(f(n))</math> (czyli rośnie asymptotycznie wolniej) to | Jeśli <math>f(n)</math> jest konstruowalna pamięciowo oraz <math>g(n) \in o(f(n))</math> (czyli rośnie asymptotycznie wolniej) to | ||
<math>\ | <math>\text{SPACE}(g(n)) \subsetneq \text{SPACE}(f(n))</math>. | ||
}} | }} | ||
[[File:ZO-3-5-rys.svg|250x250px|thumb|right|Maszyna przekątniowa]] | |||
<!-- [[ZO-3-5-rys.jpg(Maszyna przekątniowa)]] --> | <!-- [[ZO-3-5-rys.jpg(Maszyna przekątniowa)]] --> | ||
{{dowod||| | {{dowod||| | ||
Pokażemy, że istnieje język <math>L\in\ | Pokażemy, że istnieje język <math>L\in\text{SPACE}(f(n))</math> taki, że <math>L\notin\text{SPACE}(g(n))</math>. | ||
Aby zapewnić pierwszy warunek, skonstruujemy maszynę <math>M</math> dla <math>L</math>, która ma złożoność pamięciową <math>f(n)</math>. | Aby zapewnić pierwszy warunek, skonstruujemy maszynę <math>M</math> dla <math>L</math>, która ma złożoność pamięciową <math>f(n)</math>. | ||
Drugi warunek zapewnimy pokazując, że żadna z maszyn o złożoności pamięciowej <math>g(n)</math> nie akceptuje <math>L</math>. | Drugi warunek zapewnimy pokazując, że żadna z maszyn o złożoności pamięciowej <math>g(n)</math> nie akceptuje <math>L</math>. | ||
Linia 351: | Linia 340: | ||
nie zużywają one więcej niż <math>f(n)</math> pamięci. Możemy to zrobić dzięki konstruowalności pamięciowej <math>f(n)</math>. | nie zużywają one więcej niż <math>f(n)</math> pamięci. Możemy to zrobić dzięki konstruowalności pamięciowej <math>f(n)</math>. | ||
Teraz następuje część przekątniowa ([ | Teraz następuje część przekątniowa (rysunek [http://osilek.mimuw.edu.pl/images/5/5f/ZO-3-5-rys.swf Maszyna przekątniowa]). | ||
W drugim etapie maszyna <math>M</math> interpretuje słowo <math>x</math> z wejścia jako kod maszyny <math>M'</math>. Maszyna <math>M</math> odrzuca słowa, które nie są poprawnymi kodami. | W drugim etapie maszyna <math>M</math> interpretuje słowo <math>x</math> z wejścia jako kod maszyny <math>M'</math>. Maszyna <math>M</math> odrzuca słowa, które nie są poprawnymi kodami. | ||
Linia 358: | Linia 347: | ||
to maszyna <math>M</math> odrzuca <math>x</math> i jeśli <math>M'</math> odrzuca <math>x</math> to <math>M</math> je akceptuje. | to maszyna <math>M</math> odrzuca <math>x</math> i jeśli <math>M'</math> odrzuca <math>x</math> to <math>M</math> je akceptuje. | ||
Maszyna <math>M</math> ma złożoność pamięciową <math>f(n)</math>, więc <math>L=L(M)\in\ | Maszyna <math>M</math> ma złożoność pamięciową <math>f(n)</math>, więc <math>L=L(M)\in\text{SPACE}(f(n))</math>. | ||
Zastanówmy się, czy język <math>L</math> należy do klasy <math>\ | Zastanówmy się, czy język <math>L</math> należy do klasy <math>\text{SPACE}(g(n))</math>. Wtedy jest akceptowany przez | ||
maszynę <math>M'</math> o złożoności <math>g(n)</math>. Zastanówmy się co dzieje się, gdy <math>M'</math> dostanie na wejściu swój kod <math> | maszynę <math>M'</math> o złożoności <math>g(n)</math>. Zastanówmy się co dzieje się, gdy <math>M'</math> dostanie na wejściu swój kod <math><M'></math>. | ||
Ponieważ <math>M'</math> akceptuje język w czasie <math>g(n)</math>, więc i na słowie <math>x</math> działa w takim czasie. Załóżmy, że | Ponieważ <math>M'</math> akceptuje język w czasie <math>g(n)</math>, więc i na słowie <math>x</math> działa w takim czasie. Załóżmy, że | ||
odrzuca <math>x</math>. Wtedy maszyna <math>M</math> zaakceptuje <math>x</math>, gdyż symulacja którą ona przeprowadza, tzn. działanie <math>M'</math> na <math>x</math> | odrzuca <math>x</math>. Wtedy maszyna <math>M</math> zaakceptuje <math>x</math>, gdyż symulacja którą ona przeprowadza, tzn. działanie <math>M'</math> na <math>x</math> | ||
może zostać przeprowadzona, jako że <math>g(n) </math> jest asymptotycznie mniejsze niż <math>f(n) </math> (pomijamy szczegóły techniczne, które pojawią się dla małych <math>n </math>). | może zostać przeprowadzona, jako że <math>g(n)</math> jest asymptotycznie mniejsze niż <math>f(n)</math> (pomijamy szczegóły techniczne, które pojawią się dla małych <math>n</math>). | ||
Analogicznie, gdy <math>M' </math> akceptuje <math>x </math> to <math>M</math> je odrzuca, czyli <math>M' </math> nie może akceptować <math>L </math>, gdyż myli się na <math>x=M' </math>. | Analogicznie, gdy <math>M'</math> akceptuje <math>x</math> to <math>M</math> je odrzuca, czyli <math>M'</math> nie może akceptować <math>L</math>, gdyż myli się na <math>x=M'</math>. | ||
Otrzymujemy sprzeczność, stąd <math>L \notin \ | Otrzymujemy sprzeczność, stąd <math>L \notin \text{SPACE}(g(n))</math>. | ||
}} | }} | ||
A teraz pora na analogiczne twierdzenie o hierarchii czasowej. | A teraz pora na analogiczne twierdzenie o hierarchii czasowej. | ||
Potrzebne jest nam do niego dodatkowe ograniczenie na funkcję złożoności. Powiemy, że <math>f(n)</math> | Potrzebne jest nam do niego dodatkowe ograniczenie na funkcję złożoności. Powiemy, że <math>f(n)</math> | ||
jest ''konstruowalna czasowo'', gdy <math>f(n)\geqslant n\ | jest ''konstruowalna czasowo'', gdy <math>f(n)\geqslant n\text{log}n</math> oraz istnieje deterministyczna maszyna Turinga, która mając na wejściu <math>n</math> zapisane unarnie potrafi | ||
działać przez dokładnie <math>f(n)</math> kroków i zatrzymać się. Również w tym wypadku większość znanych funkcji jest konstruowalna. | działać przez dokładnie <math>f(n)</math> kroków i zatrzymać się. Również w tym wypadku większość znanych funkcji jest konstruowalna. | ||
{{twierdzenie|3.7 [twierdzenie o hierarchii czasowej]|tw 3.7| | {{twierdzenie|3.7 [twierdzenie o hierarchii czasowej]|tw 3.7| | ||
Jeśli <math>f(n)</math> jest konstruowalna czasowo oraz <math>g(n) \in o(f(n)/\ | Jeśli <math>f(n)</math> jest konstruowalna czasowo oraz <math>g(n) \in o(f(n)/\text{log}f(n))</math> to | ||
<math>\ | <math>\text{TIME}(g(n)) \subsetneq \text{TIME}(f(n))</math>. | ||
}} | }} | ||
Dowód jest przedmiotem ćwiczenia końcowego. Teraz możemy wyciągnąć kilka ważnych wniosków o ''silnym'' zawieraniu się klas złożoności: | Dowód jest przedmiotem ćwiczenia końcowego. Teraz możemy wyciągnąć kilka ważnych wniosków o ''silnym'' zawieraniu się klas złożoności: | ||
* <math>\ | * <math>\text{SPACE}(n^{\epsilon_1})\subsetneq\text{SPACE}(n^{\epsilon_2})</math>, gdy <math>0\leqslant\epsilon_1 < \epsilon_2</math>, z własności funkcji wielomianowej, | ||
* <math>\ | * <math>\text{TIME}(n^{\epsilon_1})\subsetneq\text{TIME}(n^{\epsilon_2})</math>, gdy <math>1\leqslant\epsilon_1 < \epsilon_2</math>, jak wyżej, | ||
* <math>\ | * <math>\text{L}\subsetneq\text{PSPACE}</math>, gdyż logarytm rośnie wolniej niż funkcja wielomianowa, | ||
* <math>\ | * <math>\text{P}\subsetneq\text{EXP}</math>, korzystamy z własności, że każdy wielomian rośnie wolniej niż funkcja subwykładnicza <math>n^{\text{log}n}</math> a ta z kolei rośnie wolniej, niż każda funkcja wykładnicza. | ||
* <math>\ | * <math>\text{PSPACE}\subsetneq\text{EXPSPACE}</math>, jak wyżej. | ||
Widzimy zatem, że klasa P, pojmowana jako zbiór praktycznie rozwiązywalnych problemów również podlega hierarchii. Istnieją w niej języki | Widzimy zatem, że klasa P, pojmowana jako zbiór praktycznie rozwiązywalnych problemów również podlega hierarchii. Istnieją w niej języki | ||
Linia 395: | Linia 384: | ||
{{cwiczenie|4.1|| | {{cwiczenie|4.1|| | ||
Udowodnij twierdzenie o hierarchii czasowej: | Udowodnij twierdzenie o hierarchii czasowej: | ||
Jeśli <math>f(n)</math> jest konstruowalna czasowo oraz <math>g(n) \in o(f(n)/\ | Jeśli <math>f(n)</math> jest konstruowalna czasowo oraz <math>g(n) \in o(f(n)/\text{log}f(n))</math> to | ||
<math>\ | <math>\text{TIME}(g(n)) \subsetneq \text{TIME}(f(n))</math>. | ||
}} | }} | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none"> Przeprowadź rozumowanie podobne jak w dowodzie twierdzenia o hierarchii pamięciowej. | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none"> Przeprowadź rozumowanie podobne jak w dowodzie twierdzenia o hierarchii pamięciowej. | ||
Linia 402: | Linia 391: | ||
</div> | </div> | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> Jak w dowodzie twierdzenia o hierarchii pamięciowej skonstruujemy maszynę <math>M</math>, której język <math>L=L(M)</math> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> Jak w dowodzie twierdzenia o hierarchii pamięciowej skonstruujemy maszynę <math>M</math>, której język <math>L=L(M)</math> | ||
należy do <math>\ | należy do <math>\text{TIME}(f(n))</math>, ale nie należy do <math>\text{TIME}(g(n))</math>. | ||
Konstruujemy <math>M</math> tak, że bierze <math>x</math> z wejścia i traktuje jako kod maszyny. Następnie | Konstruujemy <math>M</math> tak, że bierze <math>x</math> z wejścia i traktuje jako kod maszyny. Następnie | ||
symuluje jej działanie nie jak poprzednio w pamięci <math>f(n)</math> lecz w czasie <math>f(n)</math>. | symuluje jej działanie nie jak poprzednio w pamięci <math>f(n)</math> lecz w czasie <math>f(n)</math>. | ||
Aby jednak dokonać takiej symulacji maszyna <math>M</math> musi zliczać liczbę wykonanych kroków | Aby jednak dokonać takiej symulacji maszyna <math>M</math> musi zliczać liczbę wykonanych kroków | ||
symulacji. Symulacja każdego kroku zajmuje teraz <math>\ | symulacji. Symulacja każdego kroku zajmuje teraz <math>\text{log}(f(n))</math> potrzebnych do zaimplementowania | ||
licznika. Stąd można zasymulować każdą maszynę o złożoności asymptotycznie mniejszej niż <math>f(n)\ | licznika. Stąd można zasymulować każdą maszynę o złożoności asymptotycznie mniejszej niż <math>f(n)\text{log}f(n)</math> | ||
a tym samym wykazać przy pomocy przekątniowej metody, że język <math>M</math> jest różny od języka każdej z tych maszyn. | a tym samym wykazać przy pomocy przekątniowej metody, że język <math>M</math> jest różny od języka każdej z tych maszyn. | ||
</div> | </div> | ||
Linia 415: | Linia 404: | ||
{{cwiczenie|4.2|| | {{cwiczenie|4.2|| | ||
Udowodnij następujące fakty: | Udowodnij następujące fakty: | ||
* <math>\ | * <math>\text{NTIME}(n)\subsetneq \text{PSPACE}</math> | ||
* <math>\ | * <math>\text{TIME}(2^n) = \text{TIME}(2^{n+1})</math> | ||
* <math>\ | * <math>\text{TIME}(2^n)\subsetneq \text{TIME}(2^{2n})</math> | ||
}} | }} | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none"> Użyj poznanych twierdzeń o przyspieszaniu i hierarchii. | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none"> Użyj poznanych twierdzeń o przyspieszaniu i hierarchii. | ||
Linia 424: | Linia 413: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"> | ||
Ad. 1: | Ad. 1: | ||
Wiemy, że <math>\ | Wiemy, że <math>\text{NTIME}(n) \subseteq \text{SPACE}(n)</math>. | ||
Następnie na podstawie twierdzenia o hierarchii pamięciowej <math>\ | Następnie na podstawie twierdzenia o hierarchii pamięciowej <math>\text{SPACE}(n) \subsetneq \text{PSPACE}</math>, co kończy dowód. | ||
Ad. 2: | Ad. 2: | ||
Oczywiście <math>\ | Oczywiście <math>\text{TIME}(2^n) \subseteq \text{TIME}(2^{n+1})</math>. | ||
Weźmy teraz dowolny język <math>L</math> należący do klasy <math>\ | Weźmy teraz dowolny język <math>L</math> należący do klasy <math>\text{TIME}(2^{n+1})</math>. Istnieje zatem maszyna działająca w czasie <math>2^{n+1}</math>. | ||
Z twierdzenia o przyspieszaniu wiemy, że istnieje inna maszyna <math>M'</math> | Z twierdzenia o przyspieszaniu wiemy, że istnieje inna maszyna <math>M'</math> | ||
akceptująca ten sam język <math>L</math>, ale działająca w czasie <math>2^{n+1}/4</math>, gdy zastosujemy <math>\epsilon = 1/4</math> (czynnik liniowy | akceptująca ten sam język <math>L</math>, ale działająca w czasie <math>2^{n+1}/4</math>, gdy zastosujemy <math>\epsilon = 1/4</math> (czynnik liniowy | ||
obok funkcji wykładniczej jest nieistotny). Lecz <math>2^{n+1}/4=2^n/2</math>, a zatem <math>L</math> należy do <math>\ | obok funkcji wykładniczej jest nieistotny). Lecz <math>2^{n+1}/4=2^n/2</math>, a zatem <math>L</math> należy do <math>\text{TIME}(2^n)</math> co dowodzi tezy. | ||
Ad. 3: | Ad. 3: | ||
Zastosujemy twierdzenie o hierarchii czasowej dla funkcji <math>f(n)=2^{2n}</math> oraz <math>g(n)=2^n</math>. | Zastosujemy twierdzenie o hierarchii czasowej dla funkcji <math>f(n)=2^{2n}</math> oraz <math>g(n)=2^n</math>. | ||
Musimy tylko wykazać, że funkcje spełniają założenia twierdzenia. Mamy jednak <math>f(n)/\ | Musimy tylko wykazać, że funkcje spełniają założenia twierdzenia. Mamy jednak <math>f(n)/\text{log}(f(n))=2^{2n}/2n=(2^n/2n)2^n</math> | ||
zatem dominuje asymptotycznie <math>2^n</math> co kończy dowód. | zatem dominuje asymptotycznie <math>2^n</math> co kończy dowód. | ||
</div> | </div> |
Aktualna wersja na dzień 11:03, 5 wrz 2023
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 1.1 [ ]
Poprzez oznaczamy zbiór języków takich, że są akceptowane przez deterministyczną maszynę Turinga o złożoności czasowej .
Definicja 1.2 []
Poprzez 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 1.3 []
Poprzez oznaczamy zbiór języków takich, że są akceptowane przez niedeterministyczną maszynę Turinga o złożoności czasowej .
Definicja 1.4 []
Poprzez 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 2.1 [twierdzenie o liniowym przyspieszaniu]

Dowód
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. W animacji Liniowe przyspieszanie przedstawiono schematycznie zamianę 8 komórek pamięci na jedną większą nowej maszyny.
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 (rysunek 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:
Ćwiczenie 2.2 [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 .
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 2.3 [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 .
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 , to klasa tych języków, które mogą być akceptowane w deterministycznym czasie wielomianowym,
- Klasa , to klasa tych języków, które mogą być akceptowane w niedeterministycznym czasie wielomianowym,
- Klasa , to klasa tych języków, które mogą być akceptowane w deterministycznym czasie wykładniczym,
- Klasa , to klasa tych języków, które mogą być akceptowane w niedeterministycznym czasie wykładniczym.
dla klas pamięciowych:
- Klasa log, to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci logarytmicznej,
- Klasa log, to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci logarytmicznej,
- Klasa , to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci wielomianowej,
- Klasa , to klasa tych języków, które mogą być akceptowane w niedeterministycznej pamięci wielomianowej,
- Klasa , to klasa tych języków, które mogą być akceptowane w deterministycznej pamięci wykładniczej,
- Klasa , 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:
- , gdyż z definicji, każda maszyna deterministyczna jest maszyną niedeterministyczną,
- , jak wyżej,
- , gdyż maszyna nie może zapisać więcej komórek niż wynosi jej czas działania,
- , jak wyżej,
- , 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 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 , 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 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.
Ćwiczenie 3.1
Pokaż, że funkcje , , są konstruowalne pamięciowo.
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 3.2 [twierdzenie o luce]
Dowód
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 . 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 .
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:
Ćwiczenie 3.3
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 ?
Razem możemy to ograniczyć przez , dla pewnego zależnego tylko od maszyny oraz korzystając z założenia, że , gdyż jest konstruowalna pamięciowo.
Teraz jesteśmy gotowi do wypowiedzenia kolejnych interesujących relacji pomiędzy wprowadzonymi klasami:
- , 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ę.
- , 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,
- , 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:
Ćwiczenie 3.4
Uzasadnij każdą z poniższych relacji:
Przyjrzyjmy się bliżej pierwszej serii relacji z poprzedniego ćwiczenia i zobrazujmy ją w animacji Relacje pomiędzy podstawowymi klasami.
|size=small</flashwrap><div.thumbcaption style="width:250;">Relacje pomiędzy podstawowymi klasami
W następnej części z twierdzenia o hierarchii pamięciowej dowiemy się, że . 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 . Okazuje się, że zachodzi twierdzenie dużo silniejsze:
Twierdzenie 3.5 [twierdzenie Savitcha]
Jeśli jest konstruowalna pamięciowo, to .

Dowód
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 (), gdzie to rozmiar grafu. W naszym wypadku rozmiar grafu to liczba konfiguracji, czyli , zatem REACHABILITY wymaga czasu , co da nam tezę. Od tej pory przyjmijmy, że graf ma wierzchołków.
Wprowadźmy pomocniczą funkcję 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 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 (rysunek Wierzchołek pośredni).
Potem wywołujemy rekurencyjnie oraz . 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 , 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ą pamięci. Razem potrzebujemy 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!
Dopełnienia klas
W tym rozdziale przyjrzymy się dopełnieniom języków i klas. Jeśli jest językiem to przez oznaczamy jego dopełnienie. W przypadku problemów decyzyjnych dopełnienie (ang. COMPLEMENT) to problem decyzyjny, w którym odpowiedzi są odwrócone.
Jeśli rozważymy SAT, w którym pytamy czy formuła może zostać spełniona, to jego dopełnienie to SAT COMPLEMENT. Jest to problem bardzo blisko spokrewniony z TAUTOLOGY, w którym pytamy czy każde wartościowanie formuły ją spełnia. SAT COMPLEMENT to pytanie, czy formuła nie ma wartościowań spełniających, co jest równoważne temu, że jest formułą zawsze spełnioną, czyli jest tautologią logiczną.
COMPLEMENT nie jest ściśle dopełnieniem języka, gdyż zawiera także wszystkie słowa, które nie są poprawnymi opisami formuł. Te słowa nie stanowią jednak problemu w rozpoznawaniu.
Zdefiniujemy teraz pojęcie dopełnienia klasy złożoności. Przypomnijmy, że klasy złożoności składają się z języków. Jeśli jest dowolną klasą złożoności to przez oznaczamy jej dopełnienie, które jest złożone z dopełnień języków z klasy .
Zauważmy od razu, że jeśli jest klasą deterministyczną, to ze względu na fakt, iż maszyna deterministyczna, która akceptuje język po zamianie rolami stanu akceptującego i odrzucającego stanie się maszyną akceptującą język .
W module dotyczącym pamięci logarytmicznej dowiemy się, że klasy niedeterministycznej złożoności pamięciowej również zamknięte są na dopełnienia, natomiast w przypadku klas niedeterministycznych złożoności czasowej nie wiemy jakie są relacje pomiędzy nimi i jest to problem otwarty.
Twierdzenia o hierarchii czasowej i pamięciowej
W tej części poznamy dwa ważne twierdzenia, które wprowadzają pojęcia hierarchii czasowej i pamięciowej, tzn. pokażemy, że większe złożoności rzeczywiście istotnie pozwalają akceptować więcej języków.
Twierdzenie 3.6 [twierdzenie o hierarchii pamięciowej]
Jeśli jest konstruowalna pamięciowo oraz (czyli rośnie asymptotycznie wolniej) to .

Dowód
Pokażemy, że istnieje język taki, że . Aby zapewnić pierwszy warunek, skonstruujemy maszynę dla , która ma złożoność pamięciową . Drugi warunek zapewnimy pokazując, że żadna z maszyn o złożoności pamięciowej nie akceptuje .
Maszyna działa następująco. Gdy na wejściu dostanie słowo o długości to w pierwszym etapie oznakowuje komórek pamięci na każdej z taśm. Będziemy bowiem symulować inne maszyny i chcemy się upewnić, że nie zużywają one więcej niż pamięci. Możemy to zrobić dzięki konstruowalności pamięciowej .
Teraz następuje część przekątniowa (rysunek Maszyna przekątniowa).
W drugim etapie maszyna interpretuje słowo z wejścia jako kod maszyny . Maszyna odrzuca słowa, które nie są poprawnymi kodami. Gdy kod jest poprawny, to rozpoczyna się symulacja na tym samym słowie . Jeśli symulacja przekroczy oznakowaną pamięć to maszyna również odrzuca słowo . Jeśli maszyna zaakceptuje to maszyna odrzuca i jeśli odrzuca to je akceptuje.
Maszyna ma złożoność pamięciową , więc . Zastanówmy się, czy język należy do klasy . Wtedy jest akceptowany przez maszynę o złożoności . Zastanówmy się co dzieje się, gdy dostanie na wejściu swój kod . Ponieważ akceptuje język w czasie , więc i na słowie działa w takim czasie. Załóżmy, że odrzuca . Wtedy maszyna zaakceptuje , gdyż symulacja którą ona przeprowadza, tzn. działanie na może zostać przeprowadzona, jako że jest asymptotycznie mniejsze niż (pomijamy szczegóły techniczne, które pojawią się dla małych ). Analogicznie, gdy akceptuje to je odrzuca, czyli nie może akceptować , gdyż myli się na .
Otrzymujemy sprzeczność, stąd .

A teraz pora na analogiczne twierdzenie o hierarchii czasowej. Potrzebne jest nam do niego dodatkowe ograniczenie na funkcję złożoności. Powiemy, że jest konstruowalna czasowo, gdy oraz istnieje deterministyczna maszyna Turinga, która mając na wejściu zapisane unarnie potrafi działać przez dokładnie kroków i zatrzymać się. Również w tym wypadku większość znanych funkcji jest konstruowalna.
Twierdzenie 3.7 [twierdzenie o hierarchii czasowej]
Jeśli jest konstruowalna czasowo oraz to .
Dowód jest przedmiotem ćwiczenia końcowego. Teraz możemy wyciągnąć kilka ważnych wniosków o silnym zawieraniu się klas złożoności:
- , gdy , z własności funkcji wielomianowej,
- , gdy , jak wyżej,
- , gdyż logarytm rośnie wolniej niż funkcja wielomianowa,
- , korzystamy z własności, że każdy wielomian rośnie wolniej niż funkcja subwykładnicza a ta z kolei rośnie wolniej, niż każda funkcja wykładnicza.
- , jak wyżej.
Widzimy zatem, że klasa P, pojmowana jako zbiór praktycznie rozwiązywalnych problemów również podlega hierarchii. Istnieją w niej języki które są akceptowane w czasie natomiast w już nie. To sprawia, że należy patrzeć na praktyczność klasy P z pewną ostrożnością.
Ćwiczenia dodatkowe
Ćwiczenie 4.1
Udowodnij twierdzenie o hierarchii czasowej: Jeśli jest konstruowalna czasowo oraz to .
Ćwiczenie 4.2
Udowodnij następujące fakty: