Algoritmizace

9 - Třídy P/NP a Dynamické programování III

Připomenutí teorie

Přednáška 9 v oddíle Přednášky.

Třídy složitosti

Třídy složitosti jsou množiny úloh, které splňují určitou vlastnost. Tato vlastnost typicky souvisí s časovou či prostorovou náročností jejich řešení. Dvě nejznámější třídy složitosti jsou třída P a třída NP.

Než si uvedeme jejich zjednodušené definice, tak je nutné říci, že třídy složitosti jsou zavedeny pro rozhodovací verze úloh. To znamená, že třeba v případě hledání kostry grafu bychom se neptali, jaká je nejlevnější kostra grafu, ale zda existuje kostra s cenou menší rovno \(k\).

Pro definování tříd složitosti je nutné zavést pojem velikost vstupu úlohy. Velikost vstupu úlohy je počet bitů, který potřebujeme k zakódování vstupu dané úlohy.

Úloha patří do třídy P (Polynomial), pokud existuje algoritmus, který ji řeší v polynomiálním čase vůči velikosti vstupu.

Úloha patří do třídy NP (Non-deterministic Polynomial), pokud existuje algoritmus (verifikátor), který dokáže v polynomiálním čase vůči velikosti vstupu ověřit, zda libovolný polynomiálně dlouhý řetězec je řešením úlohy.

Prakticky vzato verifikátor musí být napojen na generátor možných řešení. Možný generátor je třeba brute force průchod všemi možnými řešeními. Odsud také plyne onen název Nedeterministicky Polynomiální, kdybychom dokázali všechny výstupy našeho generátoru vyzkoušet najednou, tak úlohu řešíme v polynomiálním čase.

Povšimněme si, že pokud máme přímo polynomiální postup, který generuje přímo řešení úlohy, tak tento postup můžeme použít jako generátor. Platí tedy, že \(P \subseteq NP\). Otázka, zda \(NP \subseteq P\) a tedy \(P = NP\) je otevřená.

Dále máme také třídu NP-těžké (NP-hard), což jsou všechny úlohy alespoň tak těžké, jako všechny úlohy v NP. Formálně se jedná o třídu úloh, na které dokážeme v polynomiálním čase převést libovolnou úlohu z NP.

Velmi důležitá je také třída NP-úplné (NP-Complete), do té patří úlohy, které jsou ve třídě NP i třídě NP-hard.

Pro žádnou NP-úplnou úlohu zatím neznáme polynomiální algoritmus. Protože NP-úplné úlohy lze mezi sebou převádět v polynomiálním čase, tak kdybychom našli polynomiální postup řešení jedné z nich, tak jej máme pro celou třídu NP. Mnozí proto předpokládají, že \(P \neq NP\), ale nemáme k tomu důkaz.

Třídami složitosti se detailně zabývá magisterský předmět Teorie Algoritmů (TAL); zde uvedený text je jen hrubé shrnutí pro přehled. Cílem je poskytnout základní přehledovou znalost i těm, kteří nebudou pokračovat na magisterské studium, či těm, kteří se o algoritmy zajímají i ve volném čase a mohou tedy na tyto koncepty narazit.

Úloha batohu (Knapsack)

Problém batohu (knapsack problem) je klasický NP-těžký problém. Máme batoh s kapacitou \(C\) a \(n\) předmětů, kde každý předmět \(i\) má váhu/velikost \(c_i\) a cenu/hodnotu (value) \(v_i\). Chceme maximalizovat hodnotu předmětů tak, aby předměty, které zvolíme nepřeplnily batoh, tedy součet jejich vah byl menší nebo rovný kapacitě \(C\).

Uvažují se 2 varianty, 0/1 knapsack znamená, že každý předmět máme jen jeden a rozhodujeme, zda ho vzít, či nevzít (0/1). Neomezená varianta pak znamená, že každý předmět můžeme vzít kolikrát chceme. Obě varianty řešíme tak, že uvažujeme optimální řešení pro batoh s menší kapacitou a do nich zkoušíme přidat další předměty.

Pro neomezený knapsack si toto lze představit převodem na hledání nejdelší cesty v DAGu s \(C\) uzly a hranami s cenou předmětů mezi každými uzly, které reprezentují odpovídající změnu ve zbývající kapacitě. Na obrázku vpravo je sestrojen DAG pro \(C = 10, n = 4, v = (9, 14, 16, 30), c = (2, 3, 4, 6)\).

10_knapsack_illustr.png

Pro 0/1 knapsack pak vyplňujeme tabulku, kde řádky znamenají přidání konkrétního předmětu a sloupce jsou změny v kapacitě. Do tabulky pak zapisujeme maximální hodnotu, kterou můžeme dostat s batohem dané kapacity a s danými předměty.

\(h(i, j) = \begin{cases} 0 & \mathrm{pokud} \ i = 0 \lor j = 0 \\ \max(h(i-1, j), h(i-1, j-c_i) + v_i) & \mathrm{jinak} \end{cases}\)

Knapsack je tedy převoditelný na problém nejdelší cesty v DAGu či tabelaci, což jsme již řešili na minulých cvičeních.

Řešení neomezeného i 0/1 knapsacku má složitost \(\Theta(C \cdot n)\), protože vyplňujeme tabulku o dimenzích \(C \times n\), či procházíme DAG s \(\Theta(C \cdot n)\) hranami.

Optimální vyhledávací strom

Toto je úloha, kde máme sestrojit BVS, pro který známe pravděpodobnosti dotazů na jednotlivé klíče. Chceme minimalizovat očekávaný počet operací, kdy nalezení klíče \(k\) v hloubce \(h_k\) vyžaduje \(h_k+1\) operací. Pokud je pravděpodobnost dotazu na klíč \(k\) rovna \(p_k\), celková cena umístění klíče \(k\) do hloubky \(h_k\) bude \(p_k(h_k + 1)\). Minimalizujeme součet přes všech \(n\) klíčů.

Podobně jako při závorkování, optimální strom o \(n\) uzlech lze vidět jako kořen a 2 optimální podstromy v levé a pravé větvi. Toto je naše “optimal substructure”. Když budeme hledat optimální strom o \(n\) uzlech tak, že budeme zkoušet každý z \(n\) uzlů jako kořen, budeme využívat stejné podstromy vícekrát - “overlapping subproblems”. Cenu stromu vypočteme vždy jako součet pravděpodobností výskytu všech uzlů + ceny obou podstromů.

Složitost bude také \(\Theta(n^3)\), výpočet je podobný výpočtu závorkování.

Řešené úlohy

Úloha batohu (Knapsack)

Řešená úloha 1:

Při popisu úlohy batohu jsme si řekli, že se jedná o klasickou NP-těžkou úlohu, ale zároveň jsme si řekli, že její složitost je \(\Theta(C \cdot n)\). Ale to je přeci polynom! Vysvětlete, proč zdánlivě polynomiální složitost úlohy batohu není v rozporu s tvrzením, že se jedná o NP-těžkou úlohu.

Nápověda

Vzpomeňte si, že pro určení třídy složitosti uvažujeme složitost algoritmu vůči velikosti vstupu. Jaká je velikost vstupu úlohy batohu? Pro připomenutí: Velikost vstupu úlohy je počet bitů, který potřebujeme k zakódování vstupu dané úlohy.

Řešení

Kapacitu batohu \(C\) zapíšeme pomocí \(\log C\) bitů, velikost vstupu je tedy alespoň \(\log C\). Když vyjádříme \(C \cdot n\) pomocí \(\log C\), dostaneme \(2^{\log C} \cdot n\). Počet kroků tedy roste exponenciálně s počtem bitů, kterými je kapacita zapsána. A protože kapacita je samostatný vstupní údaj, který není nijak omezen počtem předmětů, může počet těchto bitů růst bez omezení. Algoritmus proto není polynomiální vůči velikosti vstupu.

Pokračování

Povšimněte si, že jsme předpokládali, že k zapsání čísla \(C\) potřebujeme jen \(\log C\) bitů. Kdybychom zvolili méně efektivní zápis, například unární kódování, které zapíše číslo \(C\) pomocí \(C\) jedniček a jedné nuly, tak potřebujeme \(C + 1\) bitů pro zápis. Najednou je \(C \cdot n\) polynomiální ve velikosti vstupu. Ovšem za cenu neefektivního kódování informace.

Z tohoto důvodu je postup řešení úlohy batohu pseudopolynomiálním algoritmem. Algoritmus je pseudopolynomiální, pokud je jeho složitost omezena polynomem ve dvou parametrech: velikosti vstupu a největší číselné hodnotě na vstupu. Třídy P/NP jsou definovány pouze vůči velikosti vstupu a číselná hodnota vstupu může být exponenciální vůči velikosti vstupu. Úlohy, které nemají polynomiální algoritmus, ani když jejich vstup zakódujeme neefektivně, jsou silně NP-těžké (strongly NP-hard). Úlohy, které při efektivním kódování vstupu nemají polynomiální algoritmus, ale při neefektivním ho mají, jsou slabě NP-těžké (weakly NP-hard).

Prakticky je rozdíl v tom, do kdy je úloha upočitatelná. Slabě NP-těžké úlohy dokážeme vyřešit, pokud jsou vstupem malá čísla. Obtížné začínají být až pro velká čísla na vstupu. Oproti tomu jsou silně NP-těžké úlohy obtížně řešitelné i pro malá čísla na vstupu. Pokud platí \(P \neq NP\), tak pro silně NP-těžké úlohy nemůže existovat pseudopolynomiální algoritmus.

Řešená úloha 2: Řešte neomezenou variantu úlohy batohu pro kapacitu 10 a tři předměty, jejichž váhy jsou postupně 1, 3, 6 a ceny jsou ve stejném pořadí předmětů 11, 33, 65.

Jaká bude maximální cena předmětů v batohu?

Převodem na DAG

Nalezením nejdelší (dle ohodnocení hran) cesty v tomto DAGu.

10_5_knapsack_dag.png
Tabulkou

Pro každou buňku se zkusím pro každý předmět podívat na hodnotu na indexu nižším o váhu předmětu. K té hodnotě přičtu cenu předmětu a z těchto čísel vyberu maximum a vložím do buňky.

Zaplněná kapacita

0

1

2

3

4

5

6

7

8

9

10

Nejlepší cena

0

11

22

33

44

55

66

77

88

99

110

Index právě přidaného předmětu

0

1

1

2

1

1

2

1

1

2

1

Indexujeme předměty od 1.

Jaké bude řešení pro 0/1 knapsack pro stejné předměty a kapacitu 7?

Řešení

Za každou řádku přidávám jeden předmět. Přičítám k předchozí řádce, s posunem dle váhy předmětu.

Zaplněná kapacita

0

1

2

3

4

5

6

7

Žádný předmět

0

0

0

0

0

0

0

0

Předmět 1

0

11

11

11

11

11

11

11

Předměty 1-2

0

11

11

33

44

44

44

44

Předměty 1-3

0

11

11

33

44

44

65

76

Obsah batohu lze rekonstruovat z tabulky takto:

  1. začneme vpravo dole,

  2. posuneme se doleva, dokud bychom nesnížili cenu batohu (hodnotu v buňce) - toto je “volná” kapacita

  3. pokud je v buňce o 1 výš stejné číslo:

    • předmět z řádky nepřidáme a posuneme se o řádek výš do stejného sloupce

    • pokud ne, předmět přidáme a posuneme se o řádek výš, s posunem dle váhy přidaného předmětu

  4. opakujeme proces od bodu 2


Optimální vyhledávací strom

Řešená úloha 3: Určete, jak bude vypadat optimální BVS, když jej vybudujeme pro 7 klíčů s frekvencemi:

  • A: 0.10

  • B: 0.10

  • C: 0.25

  • D: 0.35

  • E: 0.10

  • F: 0.05

  • G: 0.05

Řešení

Vyplňujeme od diagonály směrem doprava nahoru. Pro každou buňku projdeme páry existujících hodnot od buňky v řádku zleva a ve sloupci shora, a najdeme minimální hodnotu vypočtenou dle předpisu výpočtu výše.

-

A

B

C

D

E

F

G

A

0

0.10

0.30

0.75

1.45

1.75

1.90

2.10

B

-

0

0.10

0.45

1.15

1.35

1.50

1.70

C

-

-

0

0.25

0.85

1.05

1.20

1.40

D

-

-

-

0

0.35

0.55

0.70

0.90

E

-

-

-

-

0

0.10

0.20

0.35

F

-

-

-

-

-

0

0.05

0.15

G

-

-

-

-

-

-

0

0.05

-

-

-

-

-

-

-

-

0

Diagonála obsahuje nuly, které reprezentují prázdné stromy.

Strom lze rekonstruovat, pokud si zapamatujeme např. který index v řádku jsme využili pro výpočet minimální hodnoty v buňce. To nám zároveň přesně určí i pozici ve sloupci. Když pak konstruujeme (pod)strom, kořen bude vždy klíč na pozici \(i+1\), kde \(i\) je index zapsaný v buňce.

i

1

2

3

4

5

6

7

8

-

A

B

C

D

E

F

G

A

\(\color{#6f00ff}0\)

1

\(\color{#9f00ff}1\)

\(\color{#Cf00ff}3\)

3

3

4

\(\color{#ff00ff}4\)

B

-

\(\color{#2f00ff}0\)

\(\color{#6f00ff}2\)

3

4

4

4

4

C

-

-

\(\color{#2f00ff}0\)

3

4

4

4

4

D

-

-

-

\(\color{#9f00ff}0\)

4

4

4

4

E

-

-

-

-

\(\color{#6f00ff}0\)

\(\color{#9f00ff}5\)

5

\(\color{#Cf00ff}6\)

F

-

-

-

-

-

\(\color{#6f00ff}0\)

6

7

G

-

-

-

-

-

-

\(\color{#6f00ff}0\)

\(\color{#9f00ff}7\)

-

-

-

-

-

-

-

-

\(\color{#6f00ff}0\)

10_03_tree.svg

Další úlohy

Úloha batohu (Knapsack)

Úloha 4: Řešte 0/1 variantu úlohy batohu pro kapacitu 130 a pět předmětů, jejichž váhy jsou postupně 20, 30, 40, 50, 60 a ceny jsou ve stejném pořadí předmětů 11, 33, 45, 58, 65.

Jaká bude maximální cena předmětů v batohu?

Uvažte, jak se vyhnout tabulce se cca 130 sloupci a upravte postup řešení tak, aby stačilo řádově méně sloupců.


Optimální vyhledávací strom

Úloha 5: Pravděpodobnost dotazu na jednotlivé klíče v obou daných BVS je tato:

A: 0.10 B: 0.20 C: 0.25 D: 0.05 E: 0.10 F: 0.25 G: 0.05

Vypočtěte, který strom je výhodnější pro operaci FIND.


Úloha 6: Určete, jak bude vypadat optimální BVS, když jej vybudujeme pro 7 klíčů s frekvencemi:

E: 0.04 F: 0.05 G: 0.22 H: 0.04 I: 0.06 J: 0.05 K: 0.15


Ostatní úlohy

Úloha 7: Každá zásilka ve skladu má určenou váhu a je nutno všechny zásilky naložit na dvě přistavené dodávky tak, aby se váhy nákladů na obou dodávkách lišily co nejméně.

Zdůvodněte, zda lze či nelze metodu DP pro řešení úlohy batohu využít i v této situaci.


Úloha 8: Kryštof staví malé sportovní lodě. Vyrábí jich několik typů a vždy se věnuje stavbě jen jedné lodě, dokud není hotová. Pro každý typ lodě zná počet dní potřebný ke stavbě lodě a zná svůj zisk z jejího prodeje. Ví, kolik dní bude letos v sezóně věnovat své práci.

Navrhněte metodu dynamického programování, která bude maximalizovat Kryštofův výdělek za letošní sezónu.