http://vitas.matfyz.cz/cv/slozitost.html
Tato stránka se snaží na jednoduchých příkladech ukázat počítání složitosti algoritmu. Byla vytvořena jako doplněk k cvičení Programování I. pro Informatiky.
Poznámka 1: Příklady jsou různě těžké. Myslím, že prvák informatik by je mohl zvládat asi asi takto:
Příklady 1 a 2: úšklebek nad tím, proč vás otravuji s takovou trivkou.
Příklady 3, 5, 7 a 8: na první pohled jasná třída složitosti (O(.) . Po kratším zamyšlení přesný počet hvězdiček.
Příklad 4: na první pohled jasná složitost. Kdybyste znali vzoreček na
i 2 přesný počet hvězdiček není problém.
Příklad 9: po chvilce počítání na papíře třída složitosti, kdyby někdo moc chtěl tak vypotíte i přesný počet hvězdiček.
Příklady 6, 11, 12 a 13: po krátkém zápase třída složitosti.
Příklady 14 a 15: Když si přečtete důkaz, pochopíte ho a jste ho schopni vysvětlit někomu jinému.
Poznámka 2: Protože html není žádný , nejsem si jistý jak
mnou vysázené vzorce zobrazí váš prohlížeč (dělal jsem co jsem mohl).
Pro jistotu však uvedu slovní překlad:
O(g(n)) = {
f(n) | c ,n0
n > n0:
f(n) < c . g(n)}
Lidsky řečeno: f(n) je z O(g(n)) , pokud je (až na multiplikativní konstantu) menší než g(n) (pro všechna dostatečně velká n ).
Příklady:
3n + 5 O(n)
5 n 2 + 7 n - 15 O(n 2)
O(5 n 2 + 7 n - 15) = O(n 2)
O(n 50) < O(1.5 n) < O(2 n ) < O(2 2n)
Dobré vědět:
1 + 2 + ... + n = ni=1 i = 1/2 (n+1)n
1 + 4 + 9 + ... + n 2 = ni=1 i 2 =
1/6 n(n + 1)( 2 n + 1)
ni=1 i a
O(n a+1)
ni=0 2 i = 2 n+1 - 1
for i:= 1 to N do write('*'); |
Hvězdičky: h = n .
Složitost: O(n) .
for i := 1 to N do for j := 1 to N do write('*'); |
Hvězdičky: h = n 2 .
Složitost: O(n 2) .
for i := 1 to N do for j := 1 to i do write('*'); |
Hvězdičky: h = 1 + 2 + ... + n = 1/2 n(n+1) .
Složitost: O(n 2)
for i := 1 to N do begin for j := 1 to i * i do write('*'); end; |
Hvězdičky: h = 1 + 4 + 9 + .. n 2 = 1/6 n(n+1)(2n+1)
Složitost: O(n 3)
for i := 1 to N do begin for j := 1 to N do for k := 1 to j do write('*'); end; |
Hvězdičky: h = n ni=1 i = 1/2 n 2(n+1)
Složitost: O(n 3)
for i := 1 to N do begin for j := 1 to i do for k := 1 to j do write('*'); end; |
Hvězdičky: h = ni=1 (
ij=1 j) =
ni=1 1/2 i (i+1)
= 1/2 (
ni=1 i 2 +
ni=1 i )
= 1/2 ( 1/6 n(n+1)(2n+1) + 1/2 n(n+1)) < n 3
)
Složitost: O(n 3)
for i := 1 to N do begin for j := 1 to N * N do write('*'); for k := 1 to N do write('*'); end; |
Hvězdičky: h = n 3 + n 2
Složitost: O(n 3 + n 2 ) = O(n 3) . Třetí for-cyklus (s proměnou k ) se ve výsledné (asymptotické) složitosti ztratí.
for i := 1 to N do begin for j := 1 to 100 do write('*'); end; |
Hvězdičky: h = 100 * n
Složitost: O(100n) = O(n) . For-cyklus s konstantním počtem průchodů výslednou (asymptotickou) složitost také neovlivní.
i := 1; h := 0; while i <= N do begin i := i * 2; h := h + 1; write('*'); end; |
Hvězdičky: po vytisknutí h 'té hvězdičky bude i = 2 h .
Podmínka n i = 2 h je splněna
pro log2(n)
h , nejmenší h , které je vyšší je:
h =
log2(n)
+ 1
Složitost: O(log2(n)) = O(log(n))
i := 1; while i <= N do begin for j := 1 to N do write('*'); i := i * 2; end; |
Hvězdičky: while-cyklus se provede (log2(n)
+1) )'krát. V každém
z kroků pak proběhne vnitřní for-cyklus n 'krát. tj. h = (
log2(n)
+1)n .
Složitost: O(n log(n))
i := 1; while i <= N do begin for j := 1 to i do write('*'); i := i * 2; end; |
Hvězdičky: Počet otoček while-cyklu označme L = log2(n)
+1 .
Počet hvězdiček pak bude: h =
Li=1 2 i = 2 L+1 - 1 =
2
log2(n)
+ 1 = nejbližší_vyšší_mocnina_dvou_než(n) < 2n .
Složitost: O(2n) = O(n)
for j := 1 to N do begin i := 1; while i <= j do begin write('*'); i := i * 2; end; end; |
Hvězdičky: ni=1
log2(i)
+ 1 =(asi) log2(n!) =(asi) n log2(n)
Složitost: O(n log2(n)) , to je vidět už jen z toho, že kdyby while-cyklus běžel až do n vytisklo by se n log2(n) hvězdiček. Předchozí odstavec naznačuje, že tento odhad je i minimální.
var a: array [1..N+1] of Boolean; { pole a je na začátku vyplněno hodnotou False} begin while not a[N+1] do begin i := 1; while a[i] do begin a[i] := False; i := i + 1; write('*'); end; a[i] := True; write('@'); end; end. |
Hvězdičky: Nejprve se zamyslíme, co vlastně algoritmus děla: vnitřní while-cyklus (+následující řádek) přičte jedničku k binárnímu číslu zapsanému v poli a (nejvýznamnější bity jsou na konci). Pokud si toto uvědomíme, zjistíme, že vnější while cyklus skončí až se napočítá do 1000...00 (nul je N ) je zřejmé, že počet znaků '@' bude přesně @ = 2 N .
Zajímavější je určit počet hvězdiček. Počet N -bitových čísel končících (v
binárním zápise) na nulu je 2 N/2=2 N-1 , končících na
právě 1 jedničku 2 N-2 . Obecněji: čísel končících na pravě i
jedniček je 2 N-i-1 . Pro číslo končící na právě i jedniček
vytiskne program i hvězdiček (pro i=N je jich N).
Takže dohromady: N+N-1i=0 i 2 N-i-1=
N +
N-1i=0
N-1j=i 2 N-j-1=
N +
N-1i=0 2 N-i-1=
N + 2 N - 1 - N
= 2 N - 1 .
Tedy (možná poněkud překvapivě) hvězdiček bude méně (přesně o jednu) než '@'.
Složitost: O(2 N) .
procedure rozdel_a_panuj(a,b:Integer); var i:Integer; begin write('@') if a = b then write('*') else begin for i := a to b do write('*'); rozdel_a_panuj(a, (a + b)div 2); rozdel_a_panuj((a + b) div 2 + 1, b); end; end; begin rozdel_a_panuj(1,N); end. |
Hvězdičky: Průběh procedury rozdel_a_panuj
si můžeme představit, jako
binární strom jehož listy odpovídají jednomu z čísel 1 .. N, a vnitřní vrcholy
sjednocení svých synů. Znak '@' se vytiskne přesně tolikrát kolik má tento
strom vrcholů. Protože počet listů je o jedna větší než počet
vnitřních vrcholů (snadné cvičení na matematickou indukci) je @=2N-1 ,
Strom bude mít hloubku log2(N)
, v každém patře se vytiskne N
hvězdiček (až na poslední patro, které nemusí být úplné). Proto
N
log2(N)
h
N
log2(N)
O(N log(N)) .
(pokud bychom chtěli být přesní:
h = N
log2(N)
+ (N+1) - 2
log2(N)
)
Složitost: O(h) = O(N log(N)) .
function Fibonacci(N:Integer) : Integer; begin write('*') if N = 0 then Fibonacci := 0 else if N = 1 then begin write('@'); Fibonacci := 1; end else Fibonacci := Fibonacci(N-1) + Fibonacci(N-2); end; end; |
Hvězdičky: Toto je odstrašující příklad, jak se nemají počítat Fibonačiho čísla. Je vidět, že znak '@' se vytiskne přesně tolikrát, kolik bude výsledek (za každou jedničku jednou). Všeobecně se ví, že N-té Fibonačiho číslo je:
Fib(n) =
Druhý člen v závorce se dá odhadnout konstantou, takže počet '@' bude
O((1+) N) (<O(1.62 N) ).
Počet hvězdiček je dán rekurzivním předpisem (trošku jiným než Fibonačiho
čísla, ale podobným) h(0)=1; h(2)=1; h(N) = 1 + h(N-1) + h(N-2) . Je vidět,
že Fib(N) h(N) . Nebudeme se trápit přesným explicitním vyjádřením
h(N) , bude nám stačit odhad: h(N)<3 Fib(N) . Důkaz indukcí:
h(N+2)=
h(N+1) + h(N) + 1 <
3 Fib(N+1) + h(N) + 1
3 Fib(N+1) + h(N) <
3 Fib(N+1) + 3 Fib(N) = 3 Fib(N+2) (qed).
Počet hvězdiček je tedy také O((1+
) N) .
Paměťové nároky jsou O(N) . Při každém volání si totiž musíme na zásobníku
pamatovat minimálně výsledek Fibonacci(N-1)
.
Implementovat tuto fci v O(N) (a s konstantní pamětí) necháme pro laskavého čtenáře jako cvičení.
Složitost:
O((1+) N)
http://vitas.matfyz.cz/cv/slozitost.html