http://vitas.matfyz.cz/cv/slozitost.html

Složitost programu

aneb počítání hvězdiček.

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 E 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ý TeX, 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:

Množina funkcí O(g(n))

O(g(n)) = { f(n) | \existsc ,n0 \foralln > 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á ).

Příklady:

3n + 5 \in O(n)

5 n 2 + 7 n - 15 \in 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 = Eni=1  i = 1/2 (n+1)n

1 + 4 + 9 + ... + n 2 = Eni=1  i 2 = 1/6 n(n + 1)( 2 n + 1)

Eni=1  i a \in O(n a+1)

Eni=0  2 i = 2 n+1 - 1


Příklad č.1


	for i:= 1 to N do 
		write('*');

Hvězdičky: h = n .

Složitost: O(n) .


Příklad č.2


	for i := 1 to N do 
		for j := 1 to N do 
			write('*');

Hvězdičky: h = n 2 .

Složitost: O(n 2.


Příklad č.3


	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


Příklad č.4


	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


Příklad č.5


	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 Eni=1 i = 1/2 n 2(n+1) 

Složitost: O(n 3


Příklad č.6


	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 = Eni=1  ( Eij=1  j) = Eni=1  1/2 i (i+1) = 1/2 (Eni=1  i 2 + Eni=1  i ) = 1/2 ( 1/6 n(n+1)(2n+1) + 1/2 n(n+1)) < n 3  )

Složitost: O(n 3


Příklad č.7


	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 ) se ve výsledné (asymptotické) složitosti ztratí.


Příklad č.8


	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í.


Příklad č.9


	i := 1;	
	h := 0;
	while i <= N do begin
		i := i * 2;

		h := h + 1;
		write('*');
	end;

Hvězdičky: po vytisknutí 'té hvězdičky bude i = 2 h . Podmínka n >= i = 2 h  je splněna pro log2(n) >=, nejmenší , které je vyšší je: h = [log2(n)] + 1 

Složitost: O(log2(n)) = O(log(n)) 


Příklad č.10


	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 'krát. tj. h = ([log2(n)]+1)n .

Složitost: O(n log(n))


Příklad č.11


	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 = ELi=1  2 i = 2 L+1 - 1 = 2 [log2(n)] + 1 = nejbližší_vyšší_mocnina_dvou_než(n) < 2n .

Složitost: O(2n) = O(n) 


Příklad č.12


	for j := 1 to N do begin
		i := 1;	
		while i <= j do begin
			write('*');
			i := i * 2;
		end;
	end;

Hvězdičky: Eni=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 vytisklo by se n log2(n)  hvězdiček. Předchozí odstavec naznačuje, že tento odhad je i minimální.


Příklad č.13


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 ) 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 -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ě jedniček je 2 N-i-1 . Pro číslo končící na právě jedniček vytiskne program hvězdiček (pro i=N  je jich N). Takže dohromady: N+EN-1i=0 i 2 N-i-1= N + EN-1i=0  EN-1j=i 2 N-j-1= N + EN-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.


Příklad č.14


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 hvězdiček (až na poslední patro, které nemusí být úplné). Proto N [log2(N)] <= h <= N [log2(N)] \in 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)) .


Příklad č.15


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) = \sqrt5/5(((1+\sqrt5)/2)^n-((1-\sqrt5)/2)^n)

Druhý člen v závorce se dá odhadnout konstantou, takže počet '@' bude O((1+\sqrt5) 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+\sqrt5) 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+\sqrt5) N


Zpět: http://vitas.matfyz.cz/cv/slozitost.html