Tema
/
Algoritmai ir duomenų struktūros GO
Pamoka 1 / 15 Įvadas ir sudėtingumas — Big-O ~55 min
Kodas

algo find — pirmasis matavimas

Paskutinis gabalas — pati paieška. Sukurk find.go ir find_test.go (žr. dešinėje).

LinearFind eina per biblioteką nuo pradžios ir suskaičiuoja kiekvieną pavadinimo palyginimą. Tai visas algoritmas: trys eilutės ir vienas skaitiklis.

Pirmasis matavimas

$ algo gen -n 1000 -seed 42
wrote 1000 items to library.jsonl (seed=42, order=shuffled, dup-rate=0.00)

$ algo find -title "Gubątėva"
found at index 0: Gubątėva — Gąčuku (2020, rating 20)
n = 1000, comparisons = 1

$ algo find -title "Šibąču"
found at index 999: Šibąču — Žąžame (1940, rating 89)
n = 1000, comparisons = 1000

$ algo find -title "Nėra tokio"
not found: "Nėra tokio"
n = 1000, comparisons = 1000

Trys atvejai, apie kuriuos kalbėjom pirmame žingsnyje, išmatuoti: geriausias — 1, blogiausias — 1000, nepataikymas — irgi 1000. Nepataikymas visada yra blogiausias atvejis: negali pasakyti, kad kažko nėra, neapžiūrėjęs visko.

O(n) reiškia būtent tai

Padidink biblioteką dešimt kartų ir pažiūrėk, kas darosi su skaičiumi:

n=1000     n = 1000,   comparisons = 1000
n=10000    n = 10000,  comparisons = 10000
n=100000   n = 100000, comparisons = 100000

Dešimt kartų daugiau duomenų — dešimt kartų daugiau darbo. Tiesė. Būtent tai sako O(n), ir dabar tu tai matai, o ne tiki.

Testas, kurį turi praeiti

find_test.go tikrina ne tik atsakymą, bet ir kainą:

$ go test -run TestLinearFindCountsAreExact -v .
=== RUN   TestLinearFindCountsAreExact
=== RUN   TestLinearFindCountsAreExact/best_case:_first_item
=== RUN   TestLinearFindCountsAreExact/third_item
=== RUN   TestLinearFindCountsAreExact/worst_case:_last_item
=== RUN   TestLinearFindCountsAreExact/miss:_scans_everything
--- PASS: TestLinearFindCountsAreExact (0.00s)
PASS
ok  	algo	0.417s

Įsidėmėk šitą formą — ji kartosis visą kursą. Testas reikalauja tikslaus palyginimų skaičiaus, ne „maždaug tiek". Vėlesnėse pamokose testas reikalaus „ne daugiau kaip n log n" — ir tokio testo neįmanoma apgauti neteisingu algoritmu, kad ir koks teisingas būtų jo atsakymas.

Tikroje sistemoje

Kad matytum, jog tai ne mokyklinis pratimas: transit yra veikianti Vilniaus viešojo transporto maršrutų programa. Ji tą patį maršrutą suskaičiuoja dviem būdais — su primityvia eile ir su krūva — ir praneša ir laiką, ir veiksmų skaičių. Trys skirtingi maršrutai:

Užklausa Eilės darbas (primityvi ÷ krūva) Laikas (primityvi ÷ krūva)
per miestą 249× 16,0×
lėtoji 257× 18,9×
blogiausias atvejis 324× 16,4×

Ta pati užklausa, tas pats atsakymas, o darbo santykis 15–20 kartų didesnis už laiko santykį. Laikrodis nemeluoja — jis tiesiog matuoja ne tą, ką galvoji: primityvi eilė peržiūri masyvą iš eilės, o procesorius tokį skaitymą atlieka labai pigiai. Šimtas milijonų pigių veiksmų trunka trumpiau nei milijonas brangių.

Būtent todėl šioje pamokoje skaičiuojam veiksmus. Jei būtum matavęs tik laikrodžiu, būtum radęs 16 kartų skirtumą ten, kur darbo skirtumas yra 249.

Riba nėra prognozė

Ta pati programa turi ir antrą pamoką. Naivi maršruto paieška pilname dienos grafe teoriškai gali atlikti 4,28 × 10¹⁰ veiksmų. Išmatuota — 558 ms, ir tai tik 19,6 karto lėčiau už gerąjį variantą, o ne ~1000 kartų, kaip buvo prognozuota.

Riba nesuveikė ne todėl, kad ji neteisinga, o todėl, kad riba yra lubos, o ne prognozė: algoritmas sustoja radęs atsakymą ir niekada neapžiūri viso grafo.

Šitą skirtumą — tarp „kiek daugiausia gali kainuoti" ir „kiek kainavo" — laikyk galvoje visą kursą. Big-O sako pirmą dalyką. Skaitiklis sako antrą.