Vyčíslitelnost a složitost výpočtů

Podmínky absolvování, přehled studijních materiálů, závěrečný projekt

Kapitola obsahuje:
5
Web

Chceme-li zkoumat výpočetní limity počítačů, potřebujeme k tomu co nejjednodušší a současně univerzální matematický model počítače, aby bylo možno snadno zkoumat jeho vlastnosti. Z řady modelů, které v minulosti vznikly, se jednoznačně nejvíce rozšířil Turingův stroj. Je velmi jednoduchý a přitom v principů dokáže spočítat totéž, co kterýkoli dnešní počítač.

Kapitola obsahuje:
1
Odevzdávárna
Stroj RAM: realistický model počítače
Kapitola obsahuje:
2
Web

Úvodní informace o předmětu

Požadavky na studenta
  • Individuální projekt - Turingův stroj, termín: 31.10.
    Nutná podmínka k zápočtu. Musí být odevzdán online přes níže uvedený odkaz, s využitím simulátoru Turingova stroje.
  • Domácí úkoly zadávané na seminářích.
  • Semestrální domácí práce, zadání obdržíte od vyučujícího. Termín odevzdání 31.1.
    Musí být odevzdána osobně nebo elektronickou poštou, chybně vyřešené příklady budou vraceny k přepracování, dokud nebude práce v pořádku. Maximum 40 bodů, nutno získat nejméně 20 bodů.
  •  Individuální projekt z dynamického programování, termín odevzdání 31.1. na odkazu níže. Maximum 20 bodů, nutno získat nejméně 10 bodů. 

  • Celkové hodnocení podle součtu bodů: 30-35 bodů E, 36-41 bodů D, 42-47 bodů C, 48-53 bodů B, 54-60 bodů A.
Turingův stroj - zápočtový příklad

Turingův stroj - model mechanického výpočtu

Shrnutí kapitoly

Pojem mechanický výpočet označuje proces, který probíhá samočinně podle předem stanovené posloupnosti kroků. Nemusí být nutně prováděn mechanickým strojem, i když první počítače mechanické byly. Vývoj výpočetních strojů ve 20. století vyprovokoval řadu otázek týkajících se limitů jejich schopností.

Původní představy, že počítače mohou vyřešit jakýkoli matematický nebo fyzikální problém, byly vyvráceny už v prvních dekádách 20. století. Pro zodpovězení této otázky vznikla ve 20. století celá řada matematických modelů výpočtu, ovšem ukázalo se, že všechny řeší tutéž třídu úloh, přičemž řada úloh zůstává neřešitelných. Turingova-Churchova teze tvrdí, že třída řešitelných úloh je stejná pro všechny počítače, které můžeme zkonstruovat.

Postupně se pro svou jednoduchost a praktičnost etabloval jako matematický model výpočtu tzv. Turingův stroj. V poslední době sílí snahy o jeho modernizaci nebo nahrazení jiným modelem. Původní představy totiž nepočítaly s propojenými sítěmi typu Internet, které dovolují počítačům nepředvídatelně komunikovat a neustále se vyvíjet. Tyto vlastnosti zvětšují výpočetní potenciál počítačové sítě jako celku.

Turingův stroj se v literatuře vyskytuje v několika variantách, jež se však neliší svou výpočetní silou. Jinými slovy, každá z variant může simulovat kteroukoli jinou, byť někdy za cenu zpomalení nebo větší spotřeby paměti (pásky). Existence více variant je užitečná: když chceme činnost stroje popsat matematicky, je výhodné použít co nejjednodušší variantu. Naopak když ukazujeme, že stroj zvládne jistý algoritmus, je pohodlné použít variantu s mocnějšími operacemi, jako např. vícepáskový stroj.

Univerzální Turingův stroj může simulovat činnost kteréhokoli jiného Turingova stroje. Jeho existence spolu s Turingovou-Churchovou tezí dává důležitý důsledek: je-li počítač schopen simulovat Turingův stroj, pak může simulovat i kterýkoli jiný počítač.

Studijní materiál

Skripta P. Sosík, Teorie vyčíslitelnosti, od začátku skript až po kapitolu 2.1.2 včetně.

Příklady a problémy
  1. Navrhněte TS, který vypočte celou část dekadického logaritmu z čísla zapsaného v desítkové soustavě. TS bude mít na vstupu číslo v desítkové soustavě a jeho výstupem bude celá část dekadického logaritmu zapsaná v unární soustavě. 
    • Řešení tohoto příkladu je překvapivě jednoduché, proto podáme návod k řešení v podobě jednoduché otázky: čím se dekadický logaritmus čísla v desítkové soustavě podobá tvaru tohoto čísla?

  2. Navrhněte TS, který vypočte celou část podílu binárního čísla děleného dvěma. TS bude mít na vstupu číslo v binární soustavě a jeho výstupem bude celá část podílu zapsaná v binární soustavě. 
    • I tento příklad je velmi jednoduchý. Návod: zkuste totéž s číslem v dekadické soustavě, které vydělíte desíti.

  3. Navrhněte TS, který sečte dvě čísla v unární soustavě.
    • Bez nápovědy

  4. Navrhněte TS, který vynásobí dvě čísla v unární soustavě.
    • Nápověda: Použijte více pásek.

  5. Navrhněte TS, který rozpoznává mocniny dvou zapsané (a) v unární soustavě, (b) v binární soustavě.

    • Nápověda: Jaké číslo získáme, budeme-li mocninu dvou neustále dělit dvěma? Zamyslete se, jak je zapsáno sudé číslo v unární soustavě a jak pomocí TS číslo vydělit dvěma. Použijte více pásek.

  6. Navrhněte TS, který rozpoznává faktoriály nezáporných celých čísel v unární soustavě.
      • Nápověda: Použijeme více pásek. Násobit dvě čísla již umíme, takže umíme na jedné pásce postupně generovat faktoriály a porovnávat je se vstupem. Je-li číslo na vstupu větší, než vygenerovaný faktoriál, pokračujeme v generování, pokud je vstup shodný s vygenerovaným faktoriálem, pak číslo přijmeme, pokud je vstup menší, pak se nejedná o faktoriál žádného čísla.

    1. Navrhněte TS, který přijímá jazyk L1 = {ai bi ci | i ≥ 0}.
      • Bez nápovědy.

    2. Navrhněte TS, který přijímá jazyk L2 = {ww | w ∈ {a, b, c}* }.
      • Použijeme více pásek. Nejprve je třeba zjistit, zdali je slovo na vstupní pásce sudé délky a při té příležitosti si na jednu pásku zapíšeme jeden pomocný symbol za každý druhý znak na vstupní pásce. Tímto nalezneme předěl jednotlivých slov. Jedno slovo si zapíšeme na jinou pásku a slova porovnáme.

    Ukázka Turingova stroje počítajícího faktoriál

    Vstup: 1. páska = n jedniček

    Výstup: 2. páska, n! jedniček

    Krokujte stroj a všímejte si, jakým způsobem funguje, sledujte dílčí procedury v průběhu výpočtu, jako např. násobení.

    d(0, , )=(99, , ,1, )
    d(0,1, )=(1, ,R,1, )
    d(1, ,1)=(10, , ,1,R)
    d(1,1,1)=(2,1, ,x,R)
    d(2,1, )=(3,1, ,0,L)
    d(2,1,?)=(2,1, ,?,R)
    d(3,1,0)=(3,1, ,0,L)
    d(3,1,1)=(4,1, ,1,L)
    d(4,1,1)=(4,1, ,1,L)
    d(4,1,x)=(1,1, ,x,R)
    d(3,1,x)=(5,1, ,1,L)
    d(5,1,x)=(5,1, ,1,L)
    d(5,1, )=(1,0,R, ,R)
    d(10, ,1)=(10, , ,1,R)
    d(10, ,0)=(10, , ,1,R)
    d(10, , )=(11, , , ,L)
    d(11, ,1)=(11, , ,1,L)
    d(11, , )=(12, ,L, ,R)
    d(12, ,1)=(99, , ,1, )
    d(12,0,1)=(13,1,L,1, )
    d(13,0,1)=(13,1,L,1, )
    d(13, ,1)=(13, ,R,1, )
    d(13,1,1)=(1, ,R,1, )

    Rozhodnutelné a nerozhodnutelné problémy, metoda diagonalizace

    Shrnutí kapitoly

    Rozhodovací problém je množina otázek (případů) s odpověďmi ANO/NE. Každý rozhodovací problém se dá popsat (charakterizovat) nějakým formálním jazykem. Tento jazyk obsahuje popisy všech případů problému s odpovědí ANO.

    Jazyk je rekurzívně spočetný, je-li přijímán nějakým Turingovým strojem (takových strojů může být více). Pokud existuje Turingův stroj, který jazyk přijímá a který se zastaví pro jakýkoli vstup (i tehdy, když nedojde do koncového stavu), nazývá se jazyk rekurzívní. Každý rekurzívní jazyk je i rekurzívně spočetný, obráceně to platit nemusí.

    Intuitivně nazveme problém rozhodnutelným, jestliže existuje algoritmus, který umí správně zodpovědět (rozhodnout) každý jeho případ. To je ekvivalentní požadavku, aby jazyk charakterizující problém byl rekurzívní. Proto je pro nás důležité studovat vlastnosti rekurzívních a rekurzívně spočetných jazyků.

    Existují jazyky, které nejsou rekurzívně spočetné, dokonce je jich velké množství. Existence aspoň jednoho takového jazyka označeného , který není akceptován žádným Turingovým strojem, se dá dokázat sporem, tzv. diagonalizační metodou. Název metody vychází z použití rovinného grafu s dvěma kolmými osami.  Na jednu osu umístíme všechny Turingovy stroje a na druhou osu jejich vstupní slova. Budeme (mylně) předpokládat, že existuje Turingův stroj přijímající  class= a na diagonále (úhlopříčce) grafu dojdeme k logickému paradoxu, čili sporu. Tím je předpoklad vyvrácen, tedy jazyk  class= skutečně není rekurzívně spočetný.

    Studijní materiál

    Skripta P. Sosík, Teorie vyčíslitelnosti, kapitola 3, od začátku až po část 3.2 včetně.

    Příklady a problémy
    1. Ukažte, že třída rekurzívních jazyků je uzavřená na sjednocení, průnik a doplněk. Jinými slovy, jestliže L_1 a L_2 jsou rekurzívní jazyky, pak i L_1\cup L_2 , L_1\cap L_2 a \overline{L_1} jsou rekurzívní.

      • Návod: jsou-li jazyky  L_1 a L_2 rekurzívní, pak existují Turingovy stroje  M1 a M2, které je přijímají. Jejich úpravou, resp. kombinací získáme takové TS, které budou přijímat sjednocení, průnik a doplněk těchto  jazyků. V případě doplňku je situace nejjednodušší. Pro TS  M1 stačí prohodit stavy qaccept a qreject a získáme stroj, který zamítne všechna slova z jazyka  L_1 a přijme ta, která do něj nepatří, čili doplněk. V případě sjednocení sestrojíme TS, který paralelně simuluje běh M1 a M2 . Jestliže alespoň jeden skončí pro dané slovo v akceptujícím stavu, pak slovo náleží do sjednocení. Pro průnik obdobně sestrojíme TS který paralelně simuluje běh  M1 a M2. Jestliže oba skončí pro dané slovo v akceptujícím stavu, pak slovo náleží do průniku. Řešením příkladu je podrobně popsat, jak se tyto stroje sestrojí.

    2. Uveďte příklad a) alespoň dvou rekurzivních jazyků, b) jazyka, který je rekurzivně spočetný, ale není rekurzivní, c) jazyka, který není rekurzivně spočetný.

      • Návod:  lze použít jazyky popsané ve skriptech.

    Metoda redukce


    Shrnutí kapitoly

    Rozhodovací problém definujeme jako množinu případů, které lze chápat jako otázky s odpovědí ano/ne. Každý případ se musí dát popsat/zakódovat slovem nad nějakou pevně zvolenou abecedou. Problém je potom charakterizován jazykem obsahujícím slova - případy s odpovědí ano. Problém je algoritmicky rozhodnutelný, je-li tento jazyk rekurzívní.

    Z výsledků předchozího tématu plyne, že řada problémů je nerozhodnutelných. K důkazu jejich (ne)rozhodnutelnosti není nutno užívat vždy diagonalizační metodu, která může být pracná. Bývá jednodušší použít metodu redukce. Redukce je vlastně zobrazení R jednoho problému do druhého (připomeňme, že problémy jsou množiny případů). Přitom odpověď ano/ne na každý případ x prvního problému musí být shodná jako odpověď na redukovaný případ R(x) druhého problému. Říkáme, že případy x a R(x) jsou ekvivalentní. Potom každý algoritmus rozhodující redukovaný problém vlastně rozhoduje i problém původní.

    Studijní materiál

    Skripta P. Sosík, Teorie vyčíslitelnosti, kapitola 3.3.

    Příklady a problémy

    1.    (Problém tisku) Dokažte, že pro danou trojici (M,x,y), kde je Turingův stroj a x, jsou slova nad jeho vstupní abecedou, je nerozhodnutelné, zda M se vstupem x vypíše na pásku výstup y(Ekvivalentní tvrzení: neexistuje testovací algoritmus, který by ověřil, zda testovaný program pro všechny možné vstupy vydá správné výstupy.)

    o    Návod: zkuste redukci problému zastavení na náš problém. Začněte s dvojicí (stroj M1, vstup x,) která tvoří případ problému zastavení. Pozměňte stroj M1 tak, aby vznikl stroj M2, který vypíše výstup y na pásku tehdy a jen tehdy, když se M1 zastaví. K tomu musí stroj M2 mít přidány nové stavy a pravidla, pomocí nichž vyjde z koncového stavu stroje M1, smaže obsah pásky, který vytvořil M1, a zapíše na pásku slovo y. Pokud se M1 nezastaví, tak se tím pádem nezastaví ani M2. Popište detaily naznačené konstrukce M2. Můžete předpokládat, že M1 nikdy nezapíše na pásku prázdný symbol.

    2.    Dokažte, že pro daný stroj M je nerozhodnutelné, zda M(ϵ)=↘. (Ekvivalentní tvrzení: neexistuje testovací algoritmus, který by zjistil, zda se testovaný program s prázdným vstupem zastaví nebo zacyklí.)

    o    Návod: viz sekce Výsledky cvičení  ve skriptech

    3.    Dokažte, že pro daný stroj M je nerozhodnutelné, zda L(M) = ∅. (Ekvivalentní tvrzeníneexistuje testovací algoritmus, který by zjistil, zda testovaný program pro všechny vstupy odpoví „NE.")

    o    Návod: viz sekce Výsledky cvičení  ve skriptech

    4.    Ukažte redukci problému k-nezávislá podmnožina na problém k-klika.

    o    Návod: vyjděte z opačné redukce vysvětlené ve skriptech, promyslete, co je případně třeba změnit.

    5.    Problém k-vrcholové pokrytí je definován takto: případ je dvojice (G,k), otázka zní - existuje v grafu G množina k vrcholů („vrcholové pokrytí") taková, že každá hrana má alespoň jeden vrchol v této množině? (Tato množina tedy svými vrcholy „pokrývá" všechny hrany grafu.) Sestrojte redukci problému k-vrcholové pokrytí na problém k-nezávislá podmnožina.

    o    Návod: všimněte si, že když vezmeme v grafu vrcholy, které nepatří do vrcholového pokrytí, pak tyto vrcholy tvoří určitě nezávislou množinu. Žádná hrana totiž nemůže mít oba své vrcholy v této množině.

    6.    Sestrojte redukci problému k-vrcholové pokrytí na problém k-klika.

    o    Návod: stačí zkombinovat redukce ze dvou předcházejících příkladů.

    Riceho věta

    Shrnutí kapitoly

    Riceho věta říká, že když zvolíme jakoukoli netriviální vlastnost, pak je nerozhodnutelné, zda jazyk akceptovaný nějakým Turingovým strojem tuto vlastnost má nebo ne. Poměrně snadno lze pomocí redukce tuto nerozhodnutelnost rozšířit i na jiné aspekty chování Turingových strojů, například zda mohou dojít do určitých konfigurací, vydat na výstup určité hodnoty a podobně. Vzpomeňme, že podle Turingovy-Churchovy teze se dá každý počítač s libovolným programem simulovat Turingovým strojem. Pak z Riceho věty vyplývá, že všechny netriviální aspekty chování počítačů a počítačových programů jsou nerozhodnutelné. To má dalekosáhlé důsledky pro ladění a testování software.

    Studijní materiál

    Skripta P. Sosík, Teorie vyčíslitelnosti, kapitola 3.4.

    Příklady a problémy
    1. Dokažte pomocí Riceho věty, že pro daný stroj M je nerozhodnutelné, zda L(M) = (Ekvivalentní tvrzení: neexistuje testovací algoritmus, který by zjistil, zda testovaný program pro všechny vstupy odpoví „NE.")
    2. Důsledkem Riceho věty například je, že pro mikroprocesorem řízenou pračku je nerozhodnutelné, zda při nastavení programu na jemné prádlo (vstup) nebude dvě hodiny jenom ždímat (výstup). Zdůvodněte nerozhodnutelnost tohoto tvrzení pomocí redukce, inspirujte se příkladem za důkazem Riceho věty ve skriptech.
    3. Vysvětlete vlastními slovy důsledky Riceho věty pro testování a ladění počítačových programů, uveďte alespoň 3 příklady (jiné než ve skriptech) v praxi se vyskytujících problémů, jejichž nerozhodnutelnost plyne z Riceho věty.

    Úvod do výpočetní složitosti, asymptotiky

    Shrnutí kapitoly

    V kapitole 2 (skripta prof. Černá) jsme se seznámili s několika problémy různé složitosti, která je dána časem potřebným k vyřešení problému. Ten je intuitivně dán rychlostí nejrychlejšího sekvenčního algoritmu, který umí problém vyřešit. Který algoritmus je ale ten nejrychlejší? Co když pro některé případy téhož problému je nejrychlejší algoritmus A a pro jiné algoritmus B?

    Abychom porovnali rychlost dvou (nebo více algoritmů), budeme brát za rozhodující, jak se budou chovat pro vstupy zvětšujících se délek, tj. případy rostoucí velikosti. Jinými slovy, jak rychle roste čas výpočtu braný jako funkce délky vstupu algoritmu.

    Asymptotiky představují matematický nástroj pro porovnávání rychlosti růstu funkcí. Při takovém srovnání dvou funkcí f(n) a g(n) (jejichž definičním oborem jsou přirozená čísla) nás zajímá jejich chování pro hodnoty argumentů n≥n0, tedy od n0 do nekonečna. Malé hodnoty argumentu n<n0 ignorujeme. Přitom hranici n0 si můžeme zvolit sami. Její význam je v tom, abychom oddělili počáteční část definičního oboru, kde se funkce může chovat atypicky, od zbytku, kde by její růst měl být stabilní. Výsledek srovnání, zjednodušeně řečeno, závisí na tom, jak se vyvíjí poměr f(n)/g(n) pro neomezeně rostoucí argument n: zda klesá k nule, nebo se blíží k nějaké konstantě, nebo zda jde k nekonečnu.

    Studijní materiály

    Skripta J. Wiedermann, kapitola 1.

    Skripta I. Černá, kapitola 1, odstavec Funkce, asymptotiky, a dále kapitola 2.

    Časová a prostorová složitost algoritmů

    Shrnutí kapitoly

    Časová složitost algoritmu je funkce, jejímž argumentem je velikost vstupu (v bitech), a hodnotou funkce je počet kroků algoritmu. Samozřejmě pro různé vstupy téže velikosti může algoritmus pracovat různě dlouho: rozhodující je nejhorší případ, pro který výpočet trvá nejdelší dobu.

    Časovou složitost problému pak definujeme jako časovou složitost nejrychlejšího algoritmu (Turingova stroje) schopného tento problém rozhodovat. (Nejrychlejší algoritmus je ten, jehož časová složitost je dána nejpomaleji rostoucí funkcí.) Analogicky je tomu u prostorové složitosti.

    Složitostní třída DTIME(f(n)) je množina všech problémů, jejichž časová složitost je nejvýše f(n). (Hm, přísně vzato to není množina, ale v tomto předmětu se nic nestane, když ji budeme za množinu považovat)

    Z toho plyne, že jestliže máme dvě třídy DTIME(f(n)) a DTIME(g(n)), a platí, že f(n) ≤ g(n), pak DTIME(f(n)) 
    DTIME(g(n)): každý problém, který umíme vyřešit v čase f(n), je možné vyřešit taky v delším čase g(n).

    Analogicky pak zavedeme i složitostní třídu pro prostor DSPACE(f(n)). Zde je třeba dát pozor na to, že do spotřeby paměti Turingova stroje se nezapočítává délka vstupu a výstupu na speciální vstupní a výstupní pásce.

    Speciální význam má nedeterministický Turingův strojkterý zavádí koncepci nedeterministického výpočtu: stroj může v každém kroku náhodně vybírat z více aplikovatelných pravidel. V důsledku toho mohou některé výpočty s tímtéž vstupem být akceptující a jiné zamítající. Vstup x je strojem akceptován tehdy, jestliže pro něj existuje alespoň jeden akceptující výpočet. Přitom ale spotřebovaný čas není brán jako součet všech možných výpočtů (kterých může být exponenciálně mnoho), ale jako čas nejdelšího z nich. Tím pádem je nedeterministický stroj zvýhodněn oproti deterministickému, který by musel při jeho simulaci projít postupně všechny možné výpočty, a přitom spotřeboval exponenciálně delší čas. Proto platí pro libovolnou funkci f(n):

    DTIME(f(n))  NTIME(f(n))  DTIME(2f(n))

    Deterministický stroj je vlastně speciálním případem nedeterministického, když v každém kroku existuje právě jedno aplikovatelné pravidlo. Přestože v praxi většinou používáme deterministické algoritmy, mají nedeterministické Turingovy stroje v teorii složitosti velký význam. Jen ony dovolují přesně charakterizovat skupinu v praxi velmi důležitých tzv. NP-úplných problémů, pro které se neví, jak rychle je lze řešit deterministickými algoritmy.

    Studijní materiály

    Skripta Černá, kapitoly 3.1.1 a 3.3.

    Skripta Wiedermann, kapitoly 2.1 – 2.3, 4.1. – 4.2.

    Stroj RAM: realistický model počítače

    Stroj RAM: realistický model počítače

    Obsah není zveřejněný.

    Výpočetní složitost běžných programů

    Shrnutí kapitoly

    Pro určení časové složitosti (času výpočtu v nejhorším případě) běžných počítačových programů si musíme všímat programových konstrukcí jako jsou podmínky, cykly a vzájemné volání funkcí, zejména rekurzívní. Výpočty času u jednoduchých nebo vnořených cyklů se provádějí většinou pomocí (vícenásobných) sum, výpočet často vede na aritmetickou nebo geometrickou řadu. Problémem mohou být cykly while, kde je někdy obtížné určit počet opakování.

    U rekurzívních funkcí se čas výpočtu zapíše pomocí speciální soustavy rovnic, tzv. rekurence. K jejímu vyřešení existuje v literatuře (např. Graham, Knuth, Patashnik: Concrete mathematics) řada speciálních metod. Nejjednodušší a často použitelnou metodou je opakované vzájemné dosazování rekurentních rovnic, anebo tzv. Mistrovská metoda (Master method)

    Z praktického hlediska je nejdůležitější identifikovat programy nebo metody, jejichž délka výpočtu roste exponenciálně vzhledem k některému vstupnímu parametru. Takové programy mohou být výpočetně nezvládnutelné už pro poměrně nízké hodnoty parametrů.

    Základní složitostní třídy

    Shrnutí kapitoly

    Cílem studia složitosti výpočtů je rozdělit úlohy řešené na počítačích do několika kategorií (složitostních tříd) podle jejich časové a paměťové náročnosti. Cesta k tomu vede přes zdánlivě kontra-intuitivní výsledek, že čas výpočtu na Turingově stroji a spotřebu políček pásky je možné urychlit libovolnou konstantou.

    Urychlení stroje a komprese pásky se dosahuje rozšířením páskové abecedy, kdy například na jedno políčko umístíme informace ze dvou původních. To v praxi odpovídá rozšíření délky operandů instrukce v mikroprocesoru. Jestliže instrukce pracuje např. s 64-bitovými operandy namísto 32-bitových, lze v jednom kroku zpracovat dvojnásobně více informace.

    U prostorové složitosti lze takto neomezeně snižovat spotřebu políček pracovních pásek (když nepočítáme obsah vstupní a výstupní pásky). U časové složitosti požadujeme, aby stroj vždy přečetl celý obsah vstupní pásky, a proto nemůže vykonat méně kroků, než je délka vstupu.

    Věta o urychlení nám říká, že například třída DTIME(nk) je totožná se všemi třídami DTIME(f(n)), kde f(n) je libovolný polynom k-tého stupně. Obdobný vztah platí i pro prostorovou složitost. Vidíme také, že násobení konstantou u složitostních tříd nehrají žádnou roli.

    Pro rozdělení problémů na zvládnutelné, tj. řešitelé na počítači v rozumném čase (paměti), a nezvládnutelné budeme považovat za klíčové, zda výpočet probíhá v logaritmickém, polynomiálním či exponenciálním čase (prostoru). Tím dospějeme k definici základních složitostních tříd jako PNP, LOGSPACEPSPACE a dalších.

    Z praktického hlediska jsou nejdůležitější třídy PNPPSPACE. Několik známých problémů z těchto tříd:

    • Třída P
      • veškeré vyhledávací a třídicí algoritmy (v poli, v souborech a v databázích) 
      • řada grafových algoritmů (hledání [nejkratších] cest mezi dvěma vrcholy, souvislost grafu, planarita grafu a další - aplikace v sítích všeho druhu, jízdní řády, doprava...) 
      • vyhledávání vzorku v textu s případnými mutacemi (aplikace: antivirová analýza s pomocí virové databáze)
      • test na prvočíselnost, nalezení největšího společného dělitele (aplikace: šifrování)
      • lineární programování (jednoduché optimalizační problémy s lineární účelovou funkcí a omezujícími podmínkami) 
      • příslušnost slova do regulárního nebo bezkontextového jazyka (aplikace: kompilátory)
      • LZW kompresní algoritmus a další (aplikace: gzip, rar...)
    • Třída NP
      • problém obchodního cestujícího a jeho aplikace v dopravních problémech
      • mnoho dalších grafových problémů (k-klika, k-nezávislá podmnožina, vrcholové barvení...)
      • velká většina optimalizačních problémů (plnění batohu, vytěžování vozidel, optimalizace výroby, problém čínského pošťáka, všechny problémy kvadratického programování...)
      • nejdelší společné podslovo v množině slov (aplikace: analýza DNA, proteinů)
      • mnohé hry jako sudoku,tetris, Rubikova kostka...
    • Třída PSPACE
      • splnitelnost výrokové formule s kvantifikátory
      • příslušnost slova do kontextového jazyka (aplikace: analýza přirozených jazyků)
      • řada problémů automatického plánování a rozvrhování, např. nalezení nejjednoduššího plánu
      • řada grafových problémů, např. týkajících se routingu na Internetu
      • mnohé hry jako dáma, piškvorky, Super Mario Bros...
    Studijní materiály
    • Skripta Černá, kapitola 4.1.
    • Skripta Wiedermann, kapitola 4.3, začátek kapitoly 5.1.

    Konkrétně z kapitoly 4.1 ze skript prof. Černé je třeba nastudovat:

    • Úvod
    • Věta 4.3 a 4.4 (důkazy vět 4.3 a 4.4 nejsou požadovány, ale student musí umět vysvětlit vlastními slovy, co tyto věty říkají, a jak může Turingův stroj dosáhnout urychlení, resp. komprese)
    • Text od věty 4.3 až do konce kapitoly 4.1, včetně definic tříd P, NP EXPTIME, NEXPTIME, DLOG, NLOG, PSPACE, NPSPACE + umět vysvětlit, čím jsou charakterizovány problémy v těchto třídách.

    Vztahy mezi složitostními třídami

    Shrnutí kapitoly
    V této kapitole uspořádáme dosud probrané časové a prostorové  třídy do jednotné hierarchie. Toto uspořádání nám umožňuje série poznatků ve větě 4.1 ve skriptech I. Černé. Východiskem je, že pro tutéž funkci f(n) je vždy deterministická složitostní třída obsažena v nedeterministické, a časová v prostorové. Prostor (velikost paměti) je tedy mocnější zdroj výpočetní síly než čas.

    Pozoruhodný a v teoretické informatice naprosto ojedinělý fakt je, že v této hierarchii tříd je dodnes mnoho základních problémů otevřených. Neumíme dokázat, zda mezi deterministickými a nedeterministickými časovými třídami (např. P a NP) je měřitelný rozdíl. Obdobná situace je v porovnání času a prostoru (např. P a PSPACE). Na vyřešení těchto problémů jsou vypsány finanční prémie v řádu miliónu dolarů. Pokud by se ukázalo, že P=NP, mělo by to v IT obrovský dopad na řešení výpočetně obtížných problémů.

    Studijní materiály

    Skripta Černá, kapitola 4.3. Zejména je třeba nastudovat větu 4.17, důkaz jejích částí (a), (b), (d), a část na str. 34 mezi koncem důkazu věty 4.17 a větou 4.19 (tu už ne). Student musí být schopen všechna tvrzení (a) - (e) věty 4.17 schopen vysvětlit a použít při řešení příkladů.

    Skripta Wiedermann, kapitoly 4.7 – 4.11. a konec kapitoly 5.1.

    NP-úplné problémy

    Shrnutí kapitoly

    Redukce je základním nástrojem pro porovnání složitosti dvou problémů. S redukcí jsme se už setkali v souvislosti s vyčíslitelností problémů. Víme, že redukce konstruuje z případů jednoho problému ekvivalentní případy druhého problému. V této kapitole navíc požadujeme, aby tato konstrukce proběhla v polynomiálním čase. Redukce je základem pro definici třídy tzv. C-úplných problémů, kde C je složitostní třída. 

    Nejdůležitější fakta k zapamatování (C je některá z tříd P, NP, EXPTIME, NEXPTIME, PSPACE):

    • A ≤ B intuitivně znamená, že problém B je nejméně tak těžký jako A
    • pokud A ≤ B, a problém B patří do třídy C, tak i problém A patří do třídy C
    • každý C-těžký problém je nejméně tak těžký jako kterýkoli problém ve třídě C
    • C-těžký problém ale sám do třídy C patřit nemusí (může patřit do větší třídy obsahující C)
    • pokud A ≤ B, a A je C-těžký, tak i B je C-těžký
    • pokud C-těžký problém patří do třídy C, tak je C-úplný
    • C-úplné problémy jsou ty nejtěžší ve třídě C, a jsou všechny vzájemně redukovatelné
    • když chceme ukázat o nějakém problému B ze třídy C, že je C-úplný, tak stačí vzít vhodný C-úplný problém A a sestrojit redukci A ≤ B

    Největší praktický význam má třída NP-úplných problémů, kam patří velké množství v praxi řešených problémů (optimalizační, dopravní, síťové...). Nejznámějšími reprezentanty jsou problém SAT a HAM (známý též jako problém obchodního cestujícího). Příklady dalších NP-úplných problémů naleznete v tématu "Základní složitostní třídy".

    Všechny NP-úplné problémy se v praxi považují za nezvládnutelné - na počítači jsme většinou schopni najít jen jejich přibližná řešení, přesná řešení jsou příliš výpočetně náročná.

    Studijní materiály

    Skripta Černá, kapitola 5. Důkazy vět a lemmat nejsou požadovány. Studenti ale musejí znát:

    • Veškeré definice, věty a lemmata vztahující se obecně k redukci a úplnosti (začátky kapitol 5.1 a 5.2), umět je vysvětlit a použít při řešení příkladů.
    • Definice a neformální popisy (umět vysvětlit) problémů SAT, HAM, CNF.
    • Definice a neformální popisy problémů k-KLIKA, k-VRCHOLOVÉ POKRYTÍ a k-NEZÁVISLÁ MNOŽINA a umět sestrojit všechny jejich vzájemné redukce (i ty, co nejsou ve skriptech).
    • Definice a neformální popisy problémů H-BATOH, SUBSET-SUM a PARTITION a umět sestrojit všechny jejich vzájemné redukce (i ty, co nejsou ve skriptech), s výjimkou redukcí H-BATOH ≤ SUBSET-SUM a H-BATOH ≤ PARTITION.


    Skripta Wiedermann, kapitoly 5.2 – 5.6.

    Aplikace: Dynamické programování

    Shrnutí kapitoly

    Existuje početná skupina problémů (často založených na rekurzi), které vyžadují rozdělit problém na části, a jejich řešení využít pro výpočet celkového řešení. Přitom se často zdá, že množství nutných výpočtů (rekurzívních volání) roste exponenciálně s velikostí zadání, a že tedy problém je NP-těžký. Ve skutečnosti tomu ale tak není: stačí si všimnout, že mnoho částečných řešení se počítá opakovaně, a jejich celkový počet je jen polynomiální. Dynamické programování je obecná strategie, kdy místo rekurzívního algoritmu postupně vypočítáváme řešení podproblémů od nejmenších k větším, a mezivýsledky ukládáme, až se dostaneme k výpočtu původního zadání.

    Typickým příkladem je výpočet , kde je tento princip názorně předveden.

    Studijní materiály

    Doporučuji kurz dynamického programování na wiki stránkách ČVUT. Pro nastudování základů si projděte prezentace č. 09 a 10a, nejdůležitější vybrané partie např.

    - slidy 4-10 v sadě 09, 

    - slidy 7-23 v sadě 10a, klíčová myšlenka je na slidu 16. 

    Příklady a problémy

    Problém reprezentace obrázku binárním stromem (klikněte pro zobrazení zadání). Kromě kódu programu odevzdejte i výpis vstupů a výstupů pro všech 10 příkladů ve veřejném datasetu, na který je na konci zadání odkaz.

    Nápověda řešení pro variantu minimálního počtu uzlů ve stromě:

    Označím MU(x1, x2, y1, y2) minimální počet uzlů ve stromě reprezentujícím obraz mezi sloupci x1-x2 (včetně) a řádky y1-y2 (včetně). Například pro obdélník 2x3 v pravém horním rohu obrázku je to MU(W-2, W, 1, 2).

    Teď lze napsat naivní algoritmus, který by řešil úlohu hrubou silou:

    INPUT: char image[][];       // obrázek rozměrů HxW s hodnotami '0', '1'

    // v tomto kódu ho používám jako globální proměnnou, i když se to nemá

    int MU(int x1, int x2, int y1, int y2)  {

    // metoda vrací počet uzlů v podstromu reprezentujícím výřez obrázku (obdélník)

    // ve sloupcích x1 až x2 a řádcích y1 až y2 včetně

    bool black = false;

    bool white = false;

    for (int i = x1; i <= x2; i++) {

         for (int j = y1; i <= y2; j++) {

             black |= image[i,j] == '1';

             white |= image[i,j] == '0';

         }

    }

    // pokud mají všechny pixly v obdélníku stejnou barvu, je výsledek 1

    // obrázek je pak reprezentován grafem s jedním uzlem

    if ! (black & white) {   return 1 }


    // nastupuje dělení: v nejhorším případě budeme potřebovat pro každý pixel zvláštní list stromu

    // celkový  počet uzlů ve stromě pak bude:

    int vysledek = 2 * (x2 - x1+1) * (y2 - y1+1) - 1;


    // vyzkoušíme všechna možná horizontální dělení a vybereme z nich nejlepší

    for (int i = y1; i < y2; i++) {

         vysledek = Math.Min(vysledek, MU(x1, x2, y1, i) + MU(x1, x2, i+1, y2) + 1    }


    // totéž pro vertikální dělení

    for (int i = x1; i < x2; i++) {

         vysledek = Math.Min(vysledek, MU(x1, i, y1, y2) + MU(i+1, x2, y1, y2) + 1    }

    return vysledek

    }

    Řešení úlohy dostanu zavoláním MU(0, W-1, 0, H-1).

    Jaká je vada tohohle řešení? Že má exponenciální časovou složitost, to se dá snadno zjistit. Je to tak proto, že se různé mezivýsledky MU(x1, x2, y1, y2) pro stejnou kombinaci x1, x2, y1, y2 počítají opakovaně. Přitom, kolik je všech možných mezivýsledků, tj. všech možných obdélníků v obraze? Přesně W*(W+1)*H*(H+1)/4 (proč? zkuste odvodit).

    Takže to chce algoritmus upravit - uchovávat někde potřebné mezivýsledky a postupně je počítat jako v úloze tabelace na slidech alg09, nebo při násobení matic na slidech alg10a na wiki stránkách ČVUT.