7.3 Big O un Sarežģītība
Stundas uzdevums: Tu proti uzrakstīt kodu, kas strādā, bet vai tas strādā labi? Mācīsimies skatīties uz kodu kā profesionāli inženieri, izmantojot Big O notāciju, lai paredzētu programmas uzvedību, kad tai jāapstrādā nevis desmit, bet desmit miljoni datu vienību.
70 min plāns: Teorija un paraugs (~10 min) · 1. uzdevums (~15 min) - izmēri O(1) darbību · 2. uzdevums (~25 min) - salīdzini O(n) ar O(n²) · 3. uzdevums (~20 min) - nosaki sarežģītību svešam kodam. Papildu uzdevumu sāc tikai tad, ja pārējie trīs ir gatavi.
Pirms sāc: atver VS Code un izveido failu big_o.py. Laiku mērīsi ar time bibliotēku, bet secinājumus izdarīsi par soļu skaitu, nevis sekundēm.
Teorija: Kas ir Big O?
- Kāpēc nemērīt sekundēs? Katrs dators ir atšķirīgs. Sekundes neko neizsaka par pašu algoritmu. Tā vietā mēs mēram, kā pieaug veikto operāciju (soļu) skaits, palielinoties datu apjomam (n).
- Galvenās Big O klases:
- O(1) - Konstants laiks: Zibensātrs. Cik datu, nav svarīgi. Piemērs: paņemt saraksta pirmo elementu (
saraksts[0]). - O(log n) - Logaritmisks laiks: Ļoti ātrs. Dati ar katru soli tiek dalīti uz pusēm. Piemērs: Binārā meklēšana.
- O(n) - Lineārs laiks: Pieaug proporcionāli datu apjomam. 100 elementi = 100 soļi. Piemērs: Lineārā meklēšana, viens
forcikls. - O(n²) - Kvadrātisks laiks: Lēns. Katram elementam jāpārbauda katrs cits elements. 100 elementi = 10 000 soļi. Piemērs: Burbuļkārtošana, divi iegulti
forcikli.
- O(1) - Konstants laiks: Zibensātrs. Cik datu, nav svarīgi. Piemērs: paņemt saraksta pirmo elementu (
Vides sagatavošana (Windows)
- Atver PowerShell.
- Ieej algoritmu mapē:
cd Tema7_Algoritmi - Izveido jaunu failu un atver to kodu redaktorā:
ni big_o.py; code big_o.py
Praktiskie uzdevumi
1. uzdevums -
Izmēri O(1) darbību
Beigās tu redzēsi, ka daži darbi neatkarīgi no datu apjoma aizņem vienādu laiku.
- Ieraksti
import time. - Uzraksti funkciju
def iegut_pirmo(dati):, kas atgrieždati[0]. - Izveido sarakstu ar 10 elementiem un otru ar 1 000 000 elementiem.
- Izmēri laiku ar
sakums = time.time()pirms un pēc izsaukuma. - Izdrukā abus laikus.
- Pieraksti, vai lielākais saraksts prasīja manāmi vairāk laika.
Gatavs, kad: abi laiki ir gandrīz vienādi, lai gan viens saraksts ir 100 000 reižu lielāks.
2. uzdevums -
Salīdzini O(n) ar O(n kvadrātā)
Beigās tu redzēsi, cik strauji aug ligzdota cikla laiks.
- Uzraksti funkciju ar vienu ciklu, kas saskaita visus saraksta elementus.
- Uzraksti otru funkciju ar diviem ligzdotiem cikliem pār to pašu sarakstu.
- Palaid abas ar 100 elementiem un pieraksti laikus.
- Palaid abas ar 1000 elementiem un pieraksti laikus.
- Izrēķini, cik reižu pieauga katras funkcijas laiks.
- Pieraksti, kura funkcija palēninājās vairāk.
Gatavs, kad: palielinot datus 10 reizes, viena cikla laiks aug ~10 reizes, bet ligzdotā - ~100 reizes.
3. uzdevums -
Nosaki sarežģītību svešam kodam
Beigās tu pratīsi paskatīties uz kodu un pateikt tā Big O.
- Uzraksti trīs īsas funkcijas: vienu ar
saraksts[0], vienu ar vienu ciklu, vienu ar diviem ligzdotiem. - Pieraksti katrai savu minējumu: O(1), O(n) vai O(n²).
- Pievieno katrai skaitītāju, cik operāciju tā veic.
- Palaid katru ar 10 un ar 100 elementiem.
- Salīdzini skaitītāja vērtības ar saviem minējumiem.
- Izlabo minējumu, ja skaitļi to neapstiprina.
- Pieraksti vienu secinājumu: kāpēc Big O nemēra sekundēs.
Gatavs, kad: visiem trim gadījumiem tavs minējums sakrīt ar izmērīto operāciju skaita pieaugumu.
Papildu uzdevums - Atrodi O(log n)
Ja pamatdarbs ir gatavs, izmēri bināro meklēšanu no 7.1 stundas.
- Nokopē savu
binary_searchfunkciju no 7.1 stundas. - Pievieno tai soļu skaitītāju, ja tā vēl nav.
- Palaid ar 100, 1000 un 10 000 elementiem.
- Pieraksti soļu skaitu katrā gadījumā.
- Pieraksti, par cik soļiem pieauga rezultāts, kad dati pieauga 10 reizes.
Gatavs, kad: dati pieaugot 10 reizes, soļu skaits pieaug tikai par dažiem - tā izskatās O(log n).
Koda piemērs (O(n) pret O(n²) mērījums - grūtākā daļa)
import time
# Grūtākā vieta: saprast, ka svarīgs nav SEKUNŽU skaits,
# bet tas, CIK REIZES laiks pieaug, kad dati pieaug 10 reizes.
def viens_cikls(dati): # O(n)
kopa = 0
for x in dati:
kopa += x
return kopa
def divi_cikli(dati): # O(n kvadrātā)
skaits = 0
for x in dati:
for y in dati:
skaits += 1
return skaits
for n in (100, 1000):
dati = list(range(n))
t0 = time.time(); viens_cikls(dati); t1 = time.time()
t2 = time.time(); divi_cikli(dati); t3 = time.time()
print(f"n={n:5} O(n)={t1-t0:.5f}s O(n^2)={t3-t2:.5f}s")