More actions
Bez shrnutí editace Značka: editor wikitextu 2017 |
Bez shrnutí editace Značka: editor wikitextu 2017 |
||
Riadok 178: | Riadok 178: | ||
<math>T(n) = \sum_{i={\color{lime}0}}^{{\color{orange}n-1}} 1 = 1 \times ({\color{orange}n-1} - {\color{lime}0} + 1)</math> | <math>T(n) = \sum_{i={\color{lime}0}}^{{\color{orange}n-1}} 1 = 1 \times ({\color{orange}n-1} - {\color{lime}0} + 1)</math> | ||
Prečo: vieme, že horná hranica je <math>{\color{orange}n-1}</math> a dolná hranica je <math>{\color{lime}0}</math>. Počet prvkov teda získame ako: <math>{\color{orange}\text{horná hranica}} - {\color{lime}\text{dolná hranica}} + 1</math>. Potom iba spočítame, koľko'''krát''' sa tam nachádza číslo jedna | Prečo: vieme, že horná hranica je <math>{\color{orange}n-1}</math> a dolná hranica je <math>{\color{lime}0}</math>. Počet prvkov (jednotiek) teda získame ako: <math>{\color{orange}\text{horná hranica}} - {\color{lime}\text{dolná hranica}} + 1</math>. Potom iba spočítame, koľko'''krát''' sa tam nachádza číslo jedna (toľkokrát, koľko je tam prvkov, keďže všetky prvky sú 1). Teda, <math>1 \times ({\color{orange}n-1} - {\color{lime}0} + 1)</math>. | ||
Po zjednodušení je konečný výsledok: <math>T(n) = n</math>. Hmm, kto by povedal že takýto kód bude volať <code>print</code> presne <code>n</code>-krát 😱? Ale aspoň sme dokázali, že naše matematické metódy fungujú... | Po zjednodušení je konečný výsledok: <math>T(n) = n</math>. Hmm, kto by povedal že takýto kód bude volať <code>print</code> presne <code>n</code>-krát 😱? Ale aspoň sme dokázali, že naše matematické metódy fungujú... | ||
Skúsme | Teraz skúsime niečo iné: | ||
<syntaxhighlight lang="python3" line="1"> | |||
for i in range(1, 2 * n): | |||
print("*") | |||
print(".") | |||
</syntaxhighlight> | |||
Úlohou je (opäť) zistiť počet volaní na funkciu <code>print</code> (vyjadriť to všeobecne ako matematickú funkciu časovej zložitosti – <math>T(n)</math>). Máme jeden cyklus s dvomi volaniami na <code>print</code>. Tento cyklus generuje čísla od <code>1</code> po <code>2 * n</code>. Keďže jazyk Python nezahŕňa hornú hranicu do čísiel ktoré generuje, cyklus sa zastaví na čísle <code>(2 * n) - 1</code>. No a dva <code>print</code>y v cykle iba znamenajú, že sčítavame dve operácie (dve jednotky) – sčítavanie robíme vždy ak máme za sebou sekvenciu operácií na rovnakej úrovni odsadenia. Matematicky vieme cyklus vyššie zapísať ako: | |||
<math display="block">\sum_{i=1}^{2n-1} (1 + 1)</math> | |||
Skúsme ešte niečo ťažšie. Určme počet výpisov (volaní na <code>print</code>) pre nasledovný kód: | |||
<syntaxhighlight lang="python3" line="1"> | <syntaxhighlight lang="python3" line="1"> |
Verzia z 08:43, 23. marec 2025
Matematický operátor sumácie (), aritmetická postupnosť, určovanie počtu vykonaných operácií na základe kódu v určitom programovacom jazyku.
Matematický operátor sumácie ( – Sigma)
Keďže pre spracovanie určitého objemu údajov na vstupe (zväčša uložených v množine, poli, zozname, a podobne) sa používajú v programovacích jazykoch zväčša cykly, počet operácií sa dá vyjadriť pomocou matematického operátora sumácie – .
Napríklad: znamená, že začína na čísle 1 a pokračuje po 8 (vrátane). S každou iteráciou sa sčítava časť za operátorom (v tomto prípade sa teda číslo 1 sčíta presne 8 krát: , výsledok: ).
Toto by sa dalo vyjadriť v kóde takto:
vysledok = 0
for i in range(8): # vykoná sa 8 krát
vysledok += 1
print(vysledok) # 8
Sumácia môže mať rôzny názov a definíciu riadiacej premennej, ako aj hornú hranicu. Všetky nasledujúce zápisy sú platné:
Matematický zápis | Zápis v Python kóde |
suma = 0
for i in range(5, 10):
suma += 1
| |
suma = 0
for i in range(n+1):
suma += 1
| |
suma = 0
i = ... # napr.: môže pochádzať z vonkajšieho cyklu
for j in range(1, n+2):
suma += (i + 1)
|
Avšak, nemôžeme definovať negatívny krok. Napríklad, tento zápis nie je matematicky správny: ( by nikdy nenabudlo hodnotu 0).
Aritmetická postupnosť
Príklady vyššie sa dajú vypočítať manuálne. Ale čo ak by sme mali napríklad postupnosť 100 prvkov, napríklad: .
V tomto prípade by sa postupnosť rozbalila ako . Ručne, číslo po čísle, by sme to sčítavali asi iba dlho...
Avšak, múdri matematici si všimli, že medzi číslami platí určitý vzťah. Ak zoberieme v (rozbalenej) postupnosti vždy dva protiľahlé prvky a sčítame ich, výsledkom bude vždy rovnaké číslo. To znamená, že pre výpočet súčtu všetkých členov aritmetickej postupnosti vyššie nám stačí sčítať dva protiľahlé prvky (spravidla prvý a posledný prvok), vynásobiť ich počtom prvkov a vydeliť dvomi:

Výsledok: . Samozrejme, je dôležité že číselný odstup od každého prvku (respektíve, súčet dvoch protiľahlých párov) je rovnaký. Napríklad, postupnosť čísiel nie je aritmetickou postupnosťou a teda by sme nemohli aplikovať metódu ktorá je znázornená vyššie.
Všeobecný vzorec pre výpočet súčtu prvkov aritmetickej postupnosti je:
Horná a dolná hranica postupnosti
Každá aritmetická postupnosť má hornú a dolnú hranicu, ktorou vyjadrujeme maximálny možný rozsah hodnôt ktoré môže postupnosť obsahovať. Spravidla:
- dolnou hranicou je prvý prvok aritmetickej postupnosti;
- hornou hranicou je posledný prvok aritmetickej postupnosti;
Počet prvkov aritmetickej postupnosti () definovanej pomocou sumácie () môžeme vypočítať ako:
(plus 1 – pretože berieme do úvahy aj prvý prvok postupnosti)
Sumy s riadiacou premennou
Častokrát sa stane, že počas cyklu používame aj riadiacu premennú (napríklad ) ktorá mení svoju hodnotu s každou iteráciou. Určenie funkcie môže byť pre takéto cykly zložité, pretože nemôžme jednoznačne určiť akú hodnotu bude mať (na rozdiel od ktoré sa nemení).
Majme napríklad cyklus . Všeobecne je to jednoducho , avšak my odtiaľ potrebujeme dať preč premennú (pretože sa mení s každou iteráciou, a napokon, funkcia ani neobsahuje miesto pre ďalší parameter kam by sa zmestilo).
Keď nevieme, ako sa pohnúť ďalej tak je dobré si cyklus rozpísať s konkrétnymi číslami namiesto (objaví sa nám vzor, podľa ktorého dokážeme identifikovať vzťahy medzi jednotlivými iteráciami a odvodiť všeobecný vzorec). Teda, cyklus vyššie je v podstate:
Prvým prvkom je vždy číslo 1 (pretože tak je to definované pod sumou: ) a posledným prvkom je (čo vidíme hore). Počet prvkov vieme zistiť pomocou vzorca: (je to očividné, avšak tento vzorec pomôže v prípade ak cyklus začína od čísla iného ako 1, aby sme sa zbytočne nepomýlili).
Ak máme tieto informácie, vieme ich vložiť do vzorca pre výpočet súčtu prvkov aritmetickej postupnosti a získame funkciu (výsledok):
Počet operácií pre ľubovoľnú veľkosť vstupu môžeme určiť ako: .
Pri takomto type úloh sa treba zamyslieť, že keď čísla rozpisujeme, tak namiesto troch bodiek tam môže byť akákoľvek veľká postupnosť čísiel – preto rozpíšeme iba zopár, aby sme lepšie videli vzory ktoré sa opakujú a vedeli pre ne odvodiť všeobecnú funkciu. Zameriavame sa na to, o aký typ postupnosti sa jedná (aritmetická/geometrická), ktorý prvok je prvý a ktorý posledný a koľko je prvkov spolu.
Určovanie počtu vykonaných operácií v kóde
Majme napríklad tento kód pre zistenie názvu dňa podľa jeho poradia v týždni (čiže od 1 pre pondelok až po 7 pre nedeľu):
DNI = ["Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota", "Nedeľa"]
# 0 1 2 3 4 5 6
poradie_v_tyzdni = 3 # tretí deň v týždni
den = DNI[poradie_v_tyzdni-1] # -1, pretože indexujeme od 0
print(den)
# "Streda" – tretím dňom v týždni je streda
Tuto sa ešte nenachádza žiadna premenná , keďže vstup sa pre kód nikdy nemení – vždy máme pevne danú množinu v ktorej je 7 prvkov (dní) a vyberáme práve jeden, v kóde nie sú žiadne cykly ani iné vetvy).
Nezáleží na tom, koľkokrát kód vyššie spustíme, môžeme tvrdiť že vždy sa vykonajú práve 4 operácie:
- vytvorenie premennej
DNI
; - vytvorenie premennej
poradie_v_tyzdni
; - vytvorenie premennej
den
; - výpis tejto premennej na terminál;
Teda, .
Čo sa vlastne myslí pod pojmom "operácia"? Napríklad, kód vysledok = (1 + 2) * (2 ** 5)
má hneď niekoľko operácií – je potrebné ich ďalej deliť?
Operácia je iba abstrakciou nad nejakou časťou kódu ktorá niečo robí. Môže ísť napríklad o vykonanie nejakej funkcie, priradenie premennej, ale aj o porovnanie a výmenu prvkov v množine (napríklad pri algoritme Bubble sort).
Operácia vyššie síce vykonáva viacero aritmetických operácií (sčítanie, umocnenie, vynásobenie), ale rátame ju iba ako jednu operáciu. Je to z toho dôvodu, že nikdy nemôžeme presne určiť časovú zložitosť algoritmu (rôzne procesory a architektúry majú rôznu rýchlosť, a tak ďalej), vnímame to na vyššej abstraktnej úrovni. Väčšinou sa jedna operácia berie ako jeden riadok kódu ktorý niečo robí.
Určovanie počtu operácií pre rôzne veľkosti vstupu
Aby sme dokázali vyjadriť časovú zložitosť algoritmu, musíme všeobecne vyjadriť funkciu pre zistenie počtu operácií pre dané (veľkosť vstupu) – algoritmus ktorý má menší počet operácií je efektívnejší. Funkcia, ktorá udáva počet operácií v závislosti od veľkosti vstupu je definovaná matematicky ako . Jej výsledná hodnota pre dané predstavuje najväčší počet operácií ktorý vykoná daný kód (pri vstupe o veľkosti ).
Pri týchto typoch úloh máme zadaný konkrétny kód v nejakom programovacom jazyku (napríklad v Pythone) a musíme určiť funkciu . Kľúčom je prepísať si kód na matematický zápis ktorý vyjadruje počet operácií a potom to čo najviac zjednodušiť a zovšeobecniť (aby sme tam mali nanajvýš iba premennú , bez žiadnych riadiacich premenných ako , , a podobne).
Majme kód v Pythone. Chceme vyjadriť funkciu pre počet výpisov:
for i in range(n):
print("*", end="")
Už vieme, že print
má konštantnú časovú zložitosť (tvárime sa, že je to jedna operácia v kóde, teda akoby číslo 1). Matematicky môžeme teda cyklus vyššie vyjadriť ako: .
Využívame pritom vlastnosti funkcie range
– začíname od čísla 0 a ideme až pokým nedosiahneme číslo (ktoré ale už v cykle nie je zahrnuté, pretože range
vynecháva posledný prvok: range(3) => [0, 1, 2] # spolu tri prvky, ale bez 3
). Dostali sme matematický zápis aritmetickej postupnosti, ktorú už vieme vyjadriť:
Prečo: vieme, že horná hranica je a dolná hranica je . Počet prvkov (jednotiek) teda získame ako: . Potom iba spočítame, koľkokrát sa tam nachádza číslo jedna (toľkokrát, koľko je tam prvkov, keďže všetky prvky sú 1). Teda, .
Po zjednodušení je konečný výsledok: . Hmm, kto by povedal že takýto kód bude volať print
presne n
-krát 😱? Ale aspoň sme dokázali, že naše matematické metódy fungujú...
Teraz skúsime niečo iné:
for i in range(1, 2 * n):
print("*")
print(".")
Úlohou je (opäť) zistiť počet volaní na funkciu print
(vyjadriť to všeobecne ako matematickú funkciu časovej zložitosti – ). Máme jeden cyklus s dvomi volaniami na print
. Tento cyklus generuje čísla od 1
po 2 * n
. Keďže jazyk Python nezahŕňa hornú hranicu do čísiel ktoré generuje, cyklus sa zastaví na čísle (2 * n) - 1
. No a dva print
y v cykle iba znamenajú, že sčítavame dve operácie (dve jednotky) – sčítavanie robíme vždy ak máme za sebou sekvenciu operácií na rovnakej úrovni odsadenia. Matematicky vieme cyklus vyššie zapísať ako:
Skúsme ešte niečo ťažšie. Určme počet výpisov (volaní na print
) pre nasledovný kód:
for i in range(1, n):
print(i + n)
for j in range(n-2):
for k in range(n-2):
print((i * n + j) * n + k)
print(j * 2)
Ha, konečne nejaká výzva! Plno vnorených cyklov, len pre nás! A dokonca viacero print
ov, na rôznych úrovniach! Toľko radosti...
.